diff --git a/src/components/ConfigEditor.vue b/src/components/ConfigEditor.vue index e213ee1b7..14e0544c9 100644 --- a/src/components/ConfigEditor.vue +++ b/src/components/ConfigEditor.vue @@ -44,7 +44,6 @@ required: true, }, categories: Array, - descriptions: Object, extendedFields: Object, }, computed: { @@ -76,26 +75,24 @@ }, methods: { componentFromField(field) { + if (field.format === 'flags') return InputFlag; + if (field.enum) return InputEnum; + switch (field.type) { case 'string': - case 'uint64': return InputString; case 'boolean': return InputBoolean; - case 'uint32': - case 'uint16': - case 'byte': + case 'integer': return InputNumber; - case 'flag': - return InputFlag; - case 'enum': - return InputEnum; - case 'hashSet': - case 'list': - if (['enum'].includes(field.values.type)) return field.type === 'list' ? InputList : InputSet; - if (['byte', 'uint16', 'uint32', 'uint64', 'string'].includes(field.values.type)) return InputTag; - return InputUnknown; - case 'dictionary': + case 'array': + if (field.items.enum) { + if (field.uniqueItems) return InputSet; + return InputList; + } + + return InputTag; + case 'object': return InputDictionary; default: return InputUnknown; @@ -150,16 +147,16 @@ diff --git a/src/components/ConfigFields/Input.vue b/src/components/ConfigFields/Input.vue index 3e9b95193..831808f82 100644 --- a/src/components/ConfigFields/Input.vue +++ b/src/components/ConfigFields/Input.vue @@ -13,7 +13,7 @@ currentValue: true, }, data() { - const initialValue = typeof this.currentValue !== 'undefined' ? this.currentValue : this.schema.defaultValue; + const initialValue = typeof this.currentValue !== 'undefined' ? this.currentValue : null; return { value: typeof initialValue === 'object' ? JSON.parse(JSON.stringify(initialValue)) : initialValue, @@ -21,9 +21,6 @@ }; }, computed: { - defaultValue() { - return this.schema.defaultValue; - }, label() { return this.schema.label || this.schema.param || this.schema.paramName; }, @@ -31,7 +28,7 @@ return this.schema.paramName; }, placeholder() { - return this.schema.placeholder || this.schema.defaultValue; + return this.schema.placeholder; }, description() { return this.schema.description; diff --git a/src/components/ConfigFields/InputDictionary.vue b/src/components/ConfigFields/InputDictionary.vue index 2521fef33..5a41b045c 100644 --- a/src/components/ConfigFields/InputDictionary.vue +++ b/src/components/ConfigFields/InputDictionary.vue @@ -4,23 +4,17 @@
- + - + - +
- +
@@ -41,20 +35,18 @@ }; }, computed: { - keyIsString() { - return ['string', 'uint64'].includes(this.schema.key.type); + availableEnumValues() { + return this.enumValues; }, - valueIsEnum() { - return this.schema.value.type === 'enum'; + enumValues() { + return Object.entries(this.schema.additionalProperties['x-definition']).map(([label, value]) => ({ label, value })); }, - valueAvailableEnumValues() { - const availableEnumValues = []; - - for (const key of Object.keys(this.schema.value.values)) { - availableEnumValues.push(this.schema.value.values[key]); - } - - return availableEnumValues; + resolveValue() { + return value => { + const enumValue = this.enumValues.find(({ value: enumValue }) => value === enumValue); + if (!enumValue) return value; + return enumValue.label; + }; }, }, created() { @@ -66,12 +58,7 @@ return null; }, getDefaultValue() { - if (this.valueIsEnum) return this.valueAvailableEnumValues[0]; - return null; - }, - resolveValue(value) { - if (!this.valueIsEnum) return value; - return Object.keys(this.schema.value.values).find(key => this.schema.value.values[key] === value); + return this.availableEnumValues[0].value; }, addElement() { if ((!this.elementValue && this.elementValue !== 0) || (!this.elementKey && this.elementKey !== 0)) return; diff --git a/src/components/ConfigFields/InputEnum.vue b/src/components/ConfigFields/InputEnum.vue index 87cacd89d..10faf0ec7 100644 --- a/src/components/ConfigFields/InputEnum.vue +++ b/src/components/ConfigFields/InputEnum.vue @@ -4,9 +4,7 @@
@@ -21,13 +19,8 @@ name: 'input-enum', mixins: [Input], computed: { - values() { - return this.schema.values; - }, - }, - methods: { - isLastValue(value) { - return value === Math.max(...Object.values(this.values)); + enums() { + return Object.entries(this.schema['x-definition']).map(([label, value]) => ({ label, value })); }, }, }; diff --git a/src/components/ConfigFields/InputFlag.vue b/src/components/ConfigFields/InputFlag.vue index 7d37994a0..04528f684 100644 --- a/src/components/ConfigFields/InputFlag.vue +++ b/src/components/ConfigFields/InputFlag.vue @@ -4,11 +4,11 @@
- + + + @@ -33,27 +33,42 @@ mixins: [Input], data() { return { - flagValue: this.schema.defaultValue, + flagValue: null, }; }, computed: { + availableFlags() { + return this.flags.filter(({ flag }) => (flag & this.value) !== flag); + }, flags() { - return this.schema.values; + return Object.entries(this.schema['x-definition']) + .map(([label, flag]) => ({ label, flag, })) + .filter(definition => !!Number.isInteger(Math.log2(definition.flag))); // The double bang is used to silence invalid IDE error "Type boolean is not assignable to type boolean" }, }, + created() { + this.setDefaultFlag(); + }, methods: { + setDefaultFlag() { + this.flagValue = !this.availableFlags.length ? null : this.availableFlags[0].flag; + }, addFlag() { + if (!this.flagValue) return; if (!this.flagValue && this.flagValue !== 0) return; - if (this.flagValue === 0) this.value = 0; + this.value |= this.flagValue; - this.flagValue = this.schema.defaultValue; + this.setDefaultFlag(); }, removeFlag(value) { this.value &= ~value; + if (!this.flagValue) this.setDefaultFlag(); }, resolveFlagName(value) { - return Object.keys(this.flags).find(key => this.flags[key] === value); + const flag = this.flags.find(({ flag }) => flag === value); + if (!flag) return value; + return flag.label; }, }, }; diff --git a/src/components/ConfigFields/InputList.vue b/src/components/ConfigFields/InputList.vue index 8615131fc..6c1bb0143 100644 --- a/src/components/ConfigFields/InputList.vue +++ b/src/components/ConfigFields/InputList.vue @@ -5,12 +5,8 @@
@@ -42,17 +38,17 @@ }, computed: { availableEnumValues() { - const availableEnumValues = []; - - for (const key of Object.keys(this.enumValues)) { - if (this.value.includes(this.enumValues[key])) continue; - availableEnumValues.push(this.enumValues[key]); - } - - return availableEnumValues; + return this.enumValues.filter(({ value }) => !this.value.includes(value)); }, enumValues() { - return this.schema.values.values; + return Object.entries(this.schema.items['x-definition']).map(([label, value]) => ({ label, value })); + }, + resolveValue() { + return value => { + const enumValue = this.enumValues.find(({ value: enumValue }) => value === enumValue); + if (!enumValue) return value; + return enumValue.label; + }; }, }, created() { @@ -60,7 +56,7 @@ }, methods: { getDefaultElement() { - return this.availableEnumValues[0]; + return this.availableEnumValues[0] ? this.availableEnumValues[0].value : null; }, addElement() { if (!this.element && this.element !== 0) return; @@ -73,9 +69,6 @@ this.value.splice(index, 1); this.element = this.getDefaultElement(); }, - resolveOption(value) { - return Object.keys(this.enumValues).find(key => this.enumValues[key] === value); - }, }, }; diff --git a/src/components/ConfigFields/InputNumber.vue b/src/components/ConfigFields/InputNumber.vue index 1cdd81788..bc143db6e 100644 --- a/src/components/ConfigFields/InputNumber.vue +++ b/src/components/ConfigFields/InputNumber.vue @@ -3,7 +3,7 @@
- + {{ errorText }}
@@ -17,10 +17,5 @@ export default { name: 'input-number', mixins: [Input], - methods: { - onBlur() { - if (this.value === '') this.value = this.defaultValue; - }, - }, }; diff --git a/src/components/ConfigFields/InputSet.vue b/src/components/ConfigFields/InputSet.vue index 57fceb457..f9093f09e 100644 --- a/src/components/ConfigFields/InputSet.vue +++ b/src/components/ConfigFields/InputSet.vue @@ -5,12 +5,8 @@
@@ -42,17 +38,17 @@ }, computed: { availableEnumValues() { - const availableEnumValues = []; - - for (const key of Object.keys(this.enumValues)) { - if (this.value.includes(this.enumValues[key])) continue; - availableEnumValues.push(this.enumValues[key]); - } - - return availableEnumValues; + return this.enumValues.filter(({ value }) => !this.value.includes(value)); }, enumValues() { - return this.schema.values.values; + return Object.entries(this.schema.items['x-definition']).map(([label, value]) => ({ label, value })); + }, + resolveValue() { + return value => { + const enumValue = this.enumValues.find(({ value: enumValue }) => value === enumValue); + if (!enumValue) return value; + return enumValue.label; + }; }, }, created() { @@ -61,7 +57,7 @@ }, methods: { getDefaultElement() { - return this.availableEnumValues[0]; + return this.availableEnumValues[0] ? this.availableEnumValues[0].value : null; }, addElement() { if (!this.element && this.element !== 0) return; @@ -69,15 +65,13 @@ this.value.push(this.element); this.value.sort(); + this.element = this.getDefaultElement(); }, removeElement(index) { this.value.splice(index, 1); this.element = this.getDefaultElement(); }, - resolveOption(value) { - return Object.keys(this.enumValues).find(key => this.enumValues[key] === value); - }, }, }; diff --git a/src/components/ConfigFields/InputString.vue b/src/components/ConfigFields/InputString.vue index 11bb3d095..f529a6b87 100644 --- a/src/components/ConfigFields/InputString.vue +++ b/src/components/ConfigFields/InputString.vue @@ -3,7 +3,7 @@
- + {{ errorText }}
@@ -18,14 +18,11 @@ name: 'input-string', mixins: [Input], methods: { - onBlur() { - if (this.value === '') this.value = this.defaultValue; - }, onKeyPress($event) { if (this.schema.type !== 'uint64') return true; const charCode = $event.which ? $event.which : $event.keyCode; - if ((charCode > 31 && (charCode < 48 || charCode > 57)) && charCode !== 46) return $event.preventDefault(); + if (charCode > 31 && (charCode < 48 || charCode > 57) && charCode !== 46) return $event.preventDefault(); return true; }, }, diff --git a/src/components/ConfigFields/InputTag.vue b/src/components/ConfigFields/InputTag.vue index fe57e01bd..949b7ce52 100644 --- a/src/components/ConfigFields/InputTag.vue +++ b/src/components/ConfigFields/InputTag.vue @@ -9,12 +9,15 @@ {{ item }} +
+
+ {{ errorText }} @@ -37,13 +40,13 @@ }, computed: { isString() { - return ['string', 'uint64'].includes(this.schema.values.type); + return ['string'].includes(this.schema.items.type); }, isNumber() { - return ['uint32', 'uint16'].includes(this.schema.values.type); + return ['integer'].includes(this.schema.items.type); }, errors() { - if (Object.prototype.hasOwnProperty.call(validator, this.schema.values.type)) return validator[this.schema.values.type](this.element); + if (validator.hasOwnProperty(this.schema.items.type)) return validator[this.schema.items.type](this.element); return []; }, isValid() { @@ -72,7 +75,7 @@ this.element = ''; }, onKeyDown($event) { - const charCode = ($event.which) ? $event.which : $event.keyCode; + const charCode = $event.which ? $event.which : $event.keyCode; if ([9, 13, 188, 32].includes(charCode)) { this.addElement(); diff --git a/src/utils/fetchConfigSchema.js b/src/utils/fetchConfigSchema.js deleted file mode 100644 index 69f0b5feb..000000000 --- a/src/utils/fetchConfigSchema.js +++ /dev/null @@ -1,115 +0,0 @@ -import * as http from '../plugins/http'; - -const cachedTypeDefinitions = new Map(); -const cachedStructureDefinitions = new Map(); - -const subtypeRegex = /\[[^\]]+]/g; - -function resolveSubtypes(type) { - const subtypes = type.match(subtypeRegex); - if (!subtypes) return []; - return subtypes.map(subtype => subtype.slice(1, subtype.length - 1)); -} - -async function getStructureDefinition(type) { - if (cachedStructureDefinitions.has(type)) return cachedStructureDefinitions.get(type); - - const structureDefinition = http.get(`structure/${encodeURIComponent(type)}`); - cachedStructureDefinitions.set(type, structureDefinition); - - return structureDefinition; -} - -async function getTypeDefinition(type) { - if (cachedTypeDefinitions.has(type)) return cachedTypeDefinitions.get(type); - - const typeDefinition = http.get(`type/${encodeURIComponent(type)}`); - cachedTypeDefinitions.set(type, typeDefinition); - - return typeDefinition; -} - -async function resolveType(type) { - const subtypes = resolveSubtypes(type); - - switch (type.split('`')[0]) { - case 'System.Boolean': - return { type: 'boolean' }; - case 'System.String': - return { type: 'string' }; - case 'System.Byte': - return { type: 'byte' }; - case 'System.UInt32': - return { type: 'uint32' }; - case 'System.UInt16': - return { type: 'uint16' }; - case 'System.Collections.Generic.HashSet': - case 'System.Collections.Immutable.ImmutableHashSet': - return { type: 'hashSet', values: await resolveType(subtypes[0]) }; - case 'System.Collections.Immutable.ImmutableList': - return { type: 'list', values: await resolveType(subtypes[0]) }; - case 'System.UInt64': - return { type: 'uint64' }; - case 'System.Collections.Generic.Dictionary': - case 'System.Collections.Immutable.ImmutableDictionary': - return { type: 'dictionary', key: await resolveType(subtypes[0]), value: await resolveType(subtypes[1]) }; - default: // Complex type - return unwindType(type); - } -} - -async function unwindObject(type, typeDefinition) { - const resolvedStructure = { - type: 'object', - body: {}, - }; - - const [structureDefinition, resolvedTypes] = await Promise.all([ - getStructureDefinition(type), - Promise.all(Object.keys(typeDefinition.Body).map(async param => ({ param, type: await resolveType(typeDefinition.Body[param]) }))), - ]); - - for (const { param, type } of resolvedTypes) { - const paramName = typeDefinition.Body[param] !== 'System.UInt64' ? param : `s_${param}`; - - resolvedStructure.body[param] = { - defaultValue: structureDefinition[param], - paramName, - param, - ...type, - }; - } - - return resolvedStructure; -} - -function parseEnumValues(rawValues) { - const enumValues = {}; - - for (const key of Object.keys(rawValues)) { - enumValues[key] = parseInt(rawValues[key], 10); - } - - return enumValues; -} - -async function unwindType(type) { - if (type === 'ArchiSteamFarm.BotConfig') getStructureDefinition(type); // Dirty trick, but 30% is 30% - const typeDefinition = await getTypeDefinition(type); - - switch (typeDefinition.Properties.BaseType) { - case 'System.Object': - return unwindObject(type, typeDefinition); - case 'System.Enum': - return { - type: (typeDefinition.Properties.CustomAttributes || []).includes('System.FlagsAttribute') ? 'flag' : 'enum', - values: parseEnumValues(typeDefinition.Body), - }; - default: { - const structureDefinition = await getStructureDefinition(type); - return { type: 'unknown', typeDefinition, structureDefinition }; - } - } -} - -export default unwindType; diff --git a/src/utils/swagger/dereference.js b/src/utils/swagger/dereference.js new file mode 100644 index 000000000..9bd8c378d --- /dev/null +++ b/src/utils/swagger/dereference.js @@ -0,0 +1,23 @@ +import { cloneDeep, get, isObject } from 'lodash-es'; + +function isRef(node) { + return node.$ref; +} + +function resolveRef(path, schema) { + const lodashPath = path.substr(2).replace(/\//g, '.'); + return get(schema, lodashPath); +} + +function resolve(tree, schema) { + for (const key of Object.keys(tree)) { + if (isRef(tree[key])) tree[key] = resolveRef(tree[key].$ref, schema); + if (isObject(tree[key])) resolve(tree[key], schema); + } +} + +export function dereference(schema) { + const localSchema = cloneDeep(schema); + resolve(localSchema, localSchema); + return localSchema; +} diff --git a/src/utils/swagger/parse.js b/src/utils/swagger/parse.js new file mode 100644 index 000000000..73f3d9057 --- /dev/null +++ b/src/utils/swagger/parse.js @@ -0,0 +1,24 @@ +import axios from 'axios'; +import { dereference } from './dereference'; + +const endpoint = 'http://localhost:8080/swagger/ASF/swagger.json'; +let schema; + +async function getSchema() { + if (schema) return schema; + + // We save the PROMISE, not the VALUE, in a variable. + // This is very important, as ALL future calls will retrieve this promise (including those made while promise is still pending). + // Such approach, ensures no duplicated HTTP calls are made. + schema = axios.get(endpoint) + .then(response => response.data) + .then(schema => dereference(schema)); + + return schema +} + +export async function getType(name) { + const schema = await getSchema() + const { [name]: type } = schema.components.schemas; + return type.properties; +} diff --git a/src/views/GlobalConfig.vue b/src/views/GlobalConfig.vue index 10ef3c1f7..4c1b75a51 100644 --- a/src/views/GlobalConfig.vue +++ b/src/views/GlobalConfig.vue @@ -7,8 +7,7 @@