diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index ff404bbd0..1e11827b7 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -35,6 +35,10 @@ jobs: name: Lint templates if: ${{ matrix.target == 'web' }} run: npm run lint:templates + - + name: Typecheck + if: ${{ matrix.target == 'web' }} + run: npm run typecheck - name: Run tests if: ${{ matrix.target == 'web' }} diff --git a/client/dive-common/apispec.ts b/client/dive-common/apispec.ts index 762d45283..ded784c5d 100644 --- a/client/dive-common/apispec.ts +++ b/client/dive-common/apispec.ts @@ -8,7 +8,7 @@ import { Attribute } from 'vue-media-annotator/use/AttributeTypes'; import { CustomStyle } from 'vue-media-annotator/StyleManager'; import { AttributeTrackFilter } from 'vue-media-annotator/AttributeTrackFilterControls'; import { ImageEnhancements } from 'vue-media-annotator/use/useImageEnhancements'; -import { +import type { CameraHomographies, CameraCorrespondences, CameraTransformTypes, RegistrationSource, } from 'vue-media-annotator/alignedView/CameraRegistrationStore'; import type { PercentileStretch } from 'vue-media-annotator/use/useImageEnhancements'; @@ -659,13 +659,16 @@ export { }; export { + DatasetConfigMutableKeys, + MulticamSharedMutableKeys, +}; + +export type { AnnotationSchema, Api, DatasetConfig, DatasetConfigMutable, - DatasetConfigMutableKeys, DatasetInfoFields, - MulticamSharedMutableKeys, GlobalStyleSettings, DatasetType, DiveParam, @@ -694,4 +697,4 @@ export { MediaImportResponse, }; -export type { PercentileStretch }; +export type { PercentileStretch, CameraCorrespondences }; diff --git a/client/dive-common/components/Attributes/AttributeEditor.vue b/client/dive-common/components/Attributes/AttributeEditor.vue index fdf98c8a5..c33dba6f2 100644 --- a/client/dive-common/components/Attributes/AttributeEditor.vue +++ b/client/dive-common/components/Attributes/AttributeEditor.vue @@ -131,6 +131,38 @@ export default defineComponent({ } baseSettings.datatype = type; }; + const nameRules = computed(() => [ + (v: string) => !!v || 'Name is required', + (v: string) => !v.includes(' ') || 'No spaces', + (v: string) => v !== 'userAttributes' || 'Reserved Name', + (v: string) => !isReservedAttributeName(v, baseSettings.belongs) + || `Reserved name. ${RESERVED_ATTRIBUTES[baseSettings.belongs].join(', ')} are not allowed.`, + ]); + const isValidNumberString = (v: string) => !Number.isNaN(parseFloat(v)) || 'Number is required'; + const rangeMinRules = computed(() => [ + isValidNumberString, + (v: string) => (baseSettings.editor + && baseSettings.editor.type === 'slider' + && baseSettings.editor.range + && parseFloat(v) < baseSettings.editor.range[1]) + || 'Min needs to be smaller than the Max', + ]); + const rangeMaxRules = computed(() => [ + isValidNumberString, + (v: string) => (baseSettings.editor + && baseSettings.editor.type === 'slider' + && baseSettings.editor.range + && parseFloat(v) > baseSettings.editor.range[0]) + || 'Max needs to be larger than the Min', + ]); + const stepsRules = computed(() => [ + isValidNumberString, + (v: string) => (baseSettings.editor + && baseSettings.editor.type === 'slider' + && baseSettings.editor.range + && parseFloat(v) < (baseSettings.editor.range[1] - baseSettings.editor.range[0])) + || 'Steps should be smaller than the range', + ]); const numericChange = (type: 'combo' | 'slider') => { if (type === 'combo') { baseSettings.editor = { @@ -210,6 +242,10 @@ export default defineComponent({ typeChange, numericChange, launchColorEditor, + nameRules, + rangeMinRules, + rangeMaxRules, + stepsRules, //utils isReservedAttributeName, RESERVED_ATTRIBUTES, @@ -253,13 +289,7 @@ export default defineComponent({ - -
- -
import { + computed, defineComponent, ref, PropType, @@ -8,6 +9,8 @@ import { } from 'vue'; import { NumericAttributeEditorOptions, StringAttributeEditorOptions } from 'vue-media-annotator/use/AttributeTypes'; +let attributeInputUidCounter = 0; + export default defineComponent({ props: { name: { @@ -23,7 +26,7 @@ export default defineComponent({ required: true, }, values: { - type: Array, + type: Array as PropType, default: () => [], }, focus: { @@ -40,8 +43,22 @@ export default defineComponent({ }, }, setup(props, { emit }) { + attributeInputUidCounter += 1; + const uid = attributeInputUidCounter; const tempVal = ref(props.value as null | boolean | number | string); const inputBoxRef = ref(undefined as undefined | HTMLInputElement); + const numberValue = computed(() => ( + typeof props.value === 'number' ? props.value : undefined)); + const sliderConfig = computed(() => { + const { typeSettings } = props; + const range = ( + typeSettings && 'range' in typeSettings && typeSettings.range + ) ? typeSettings.range : [0, 1]; + const steps = ( + typeSettings && 'steps' in typeSettings && typeSettings.steps + ) ? typeSettings.steps : (range[1] - range[0]) / 2.0; + return { range, steps }; + }); const boolOpts = [ { text: '', value: undefined }, { text: 'true', value: true }, @@ -84,8 +101,8 @@ export default defineComponent({ } } - function change(event: InputEvent): void { - const target = event.target as HTMLInputElement; + function change(event: Event): void { + const target = event.target as HTMLInputElement | HTMLSelectElement; const { name } = props; const value = target.value.trim(); if (value) { @@ -101,8 +118,11 @@ export default defineComponent({ } return { + uid, inputBoxRef, tempVal, + numberValue, + sliderConfig, boolOpts, blurType, onFocus, @@ -118,7 +138,7 @@ export default defineComponent({