diff --git a/client/dive-common/components/ActionEditors/ActionShortcuts.vue b/client/dive-common/components/ActionEditors/ActionShortcuts.vue
index 0dd20c58..f08b620b 100644
--- a/client/dive-common/components/ActionEditors/ActionShortcuts.vue
+++ b/client/dive-common/components/ActionEditors/ActionShortcuts.vue
@@ -4,9 +4,11 @@ import {
computed,
defineComponent, ref, Ref,
} from 'vue';
+import draggable from 'vuedraggable';
import {
DIVEAction, DIVEActionShortcut, TrackSelectAction,
} from 'dive-common/use/useActions';
+import { ButtonShortcut } from 'vue-media-annotator/use/AttributeTypes';
import {
useAttributes, useConfiguration, useTrackStyleManager,
} from 'vue-media-annotator/provides';
@@ -22,6 +24,7 @@ export default defineComponent({
GetShortcut,
ActionEditor,
ButtonShortcutEditor,
+ draggable,
},
props: {
disabled: {
@@ -40,6 +43,8 @@ export default defineComponent({
tempList.push(item);
});
shortcutList.value = tempList;
+ } else {
+ shortcutList.value = [];
}
};
updateShortCutList();
@@ -55,6 +60,31 @@ export default defineComponent({
actionList.value = editingShortcut.value?.actions || [];
};
+ const getShortcutDisplay = (item: DIVEActionShortcut) => {
+ const { key, modifiers } = item.shortcut;
+ if (!key) {
+ return '';
+ }
+ if (modifiers?.length) {
+ return `${key}+${modifiers.join('+')}`;
+ }
+ return key;
+ };
+
+ const hasKeyboardShortcut = (item: DIVEActionShortcut) => !!(item.shortcut.key && item.shortcut.key.length);
+
+ const isIconOnlyButton = (button: ButtonShortcut) => {
+ const hasText = !!(button.buttonText && button.buttonText.trim());
+ return !hasText && !!(button.iconPrepend || button.iconAppend);
+ };
+
+ const onShortcutOrderEnd = () => {
+ if (!configMan.configuration.value) {
+ configMan.configuration.value = {};
+ }
+ configMan.configuration.value.shortcuts = [...shortcutList.value];
+ };
+
const editShortcut = (index?: number) => {
addEditShortcut.value = true;
if (index !== undefined) {
@@ -188,6 +218,10 @@ export default defineComponent({
addEditActionindex,
saveAction,
saveShortcutDisabled,
+ getShortcutDisplay,
+ hasKeyboardShortcut,
+ isIconOnlyButton,
+ onShortcutOrderEnd,
};
},
@@ -220,78 +254,140 @@ export default defineComponent({
Add Shortcut
-
-
-
- mdi-keyboard-outlineKeyboard
-
-
-
-
- mdi-button-cursorButton
-
-
-
-
- Description
-
-
-
-
- Actions
-
-
-
-
- Edit
-
-
-
-
-
-
- mdi-keyboard-outline{{ item.shortcut.key }}{{ item.shortcut.modifiers && item.shortcut.modifiers.length ? `+${item.shortcut.modifiers.join('+')}` : '' }}
-
+
+ Drag
-
-
- mdi-button-cursor
- {{ item.button.iconPrepend }}
- {{ item.button.buttonText }}
- {{ item.button.iconAppend }}
-
+
+ Shortcut
-
- {{ item.description }}
+
+ Button
-
-
- {{ action.action.type }}
-
+
+ Description
-
-
- mdi-pencil
-
-
- mdi-delete
-
+
+ Actions
+
+
+ Edit
+
+
+
+
+ mdi-drag
+
+
+
+
+
+
+
+ mdi-keyboard-outline
+
+
+ Keyboard shortcut in use
+
+
+ {{ getShortcutDisplay(item) }}
+
+
+
+
+
+ mdi-keyboard-off-outline
+
+
+ No keyboard shortcut
+
+
+
+
+
+
+
+ {{ item.button.iconPrepend }}
+
+
+ {{ item.button.buttonText }}
+
+
+ {{ item.button.iconAppend }}
+
+
+
+ Custom UI button
+
+ —
+
+
+ {{ item.description || '—' }}
+
+
+
+ {{ action.action.type }}
+
+
+
+
+
+ mdi-pencil
+
+
+
+
+ mdi-delete
+
+
+
+
+
@@ -477,4 +573,22 @@ export default defineComponent({
min-height: 15px;
max-height: 15px;
}
+
+ .drag-handle {
+ cursor: grab;
+ }
+
+ .action-shortcut-list-row {
+ border-bottom: 1px solid rgba(128, 128, 128, 0.2);
+ }
+
+ .shortcut-button-preview--icon-only {
+ min-width: 28px !important;
+ padding: 0 6px !important;
+
+ .v-icon {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ }
+ }
diff --git a/client/dive-common/components/Attributes/AttributeEditor.vue b/client/dive-common/components/Attributes/AttributeEditor.vue
index 3cf0414a..1459108d 100644
--- a/client/dive-common/components/Attributes/AttributeEditor.vue
+++ b/client/dive-common/components/Attributes/AttributeEditor.vue
@@ -535,6 +535,8 @@ export default defineComponent({
diff --git a/client/dive-common/components/Attributes/AttributeShortcuts.vue b/client/dive-common/components/Attributes/AttributeShortcuts.vue
index 8ad107d4..bfc194b3 100644
--- a/client/dive-common/components/Attributes/AttributeShortcuts.vue
+++ b/client/dive-common/components/Attributes/AttributeShortcuts.vue
@@ -3,6 +3,7 @@ import {
computed, defineComponent, ref, PropType, Ref,
watch,
} from 'vue';
+import draggable from 'vuedraggable';
import { AttributeShortcut, ButtonShortcut } from 'vue-media-annotator/use/AttributeTypes';
import usedShortcuts from 'dive-common/use/usedShortcuts';
import { useAttributes } from 'vue-media-annotator/provides';
@@ -13,6 +14,7 @@ export default defineComponent({
name: 'AttributeShortcuts',
components: {
ButtonShortcutEditor,
+ draggable,
},
props: {
value: {
@@ -23,6 +25,14 @@ export default defineComponent({
type: String as PropType<'text' | 'number' | 'boolean'>,
required: true,
},
+ attributeName: {
+ type: String,
+ default: '',
+ },
+ attributeColor: {
+ type: String,
+ default: '',
+ },
},
setup(props, { emit }) {
const editShortcutDialog = ref(false);
@@ -74,10 +84,31 @@ export default defineComponent({
return `${base}${shorcut.key}`;
};
+ const defaultDescription = (type: AttributeShortcut['type']) => {
+ if (type === 'remove') {
+ return 'Remove the value';
+ }
+ return 'Set the Value';
+ };
+
+ const isDefaultDescription = (description: string) => [
+ 'Set the Value',
+ 'Remove the value',
+ 'Enter a Description',
+ '',
+ ].includes(description);
+
+ const hasKeyboardShortcut = (shortcut: AttributeShortcut) => !!(shortcut.key && shortcut.key.length);
+
+ const isIconOnlyButton = (button: ButtonShortcut) => {
+ const hasText = !!(button.buttonText && button.buttonText.trim());
+ return !hasText && !!(button.iconPrepend || button.iconAppend);
+ };
+
const editShortcut = (shortcut: AttributeShortcut, index: number) => {
selectedShortcut.value = index;
selectedShortcutType.value = shortcut.type;
- selectedShortcutDescription.value = shortcut.description || 'Enter a Description';
+ selectedShortcutDescription.value = shortcut.description || defaultDescription(shortcut.type);
selectedShortcutValue.value = shortcut.value || 0;
selectedShortcutKey.value = getShortcutDisplay(shortcut);
selectedShortcutButton.value = shortcut.button || undefined;
@@ -125,7 +156,7 @@ export default defineComponent({
const addShortcut = () => {
selectedShortcut.value = props.value.length;
selectedShortcutType.value = 'set';
- selectedShortcutDescription.value = 'Enter a Description';
+ selectedShortcutDescription.value = defaultDescription('set');
if (props.valueType === 'boolean') {
selectedShortcutValue.value = false;
}
@@ -223,6 +254,15 @@ export default defineComponent({
}
});
+ watch(selectedShortcutType, (type) => {
+ if (!editShortcutDialog.value) {
+ return;
+ }
+ if (isDefaultDescription(selectedShortcutDescription.value)) {
+ selectedShortcutDescription.value = defaultDescription(type);
+ }
+ });
+
const selectedDisplayKey = computed(() => {
let base = '';
if (selectedShortcutModifiers.value.length) {
@@ -231,6 +271,15 @@ export default defineComponent({
}
return `${base}${selectedShortcutKey.value}`;
});
+
+ const onShortcutOrderEnd = () => {
+ emit('input', copy.value);
+ };
+
+ watch(() => props.value, (val) => {
+ copy.value = val;
+ });
+
return {
editShortcutDialog,
selectedShortcutType,
@@ -247,6 +296,8 @@ export default defineComponent({
awaitingKeyPress,
shortcutError,
getShortcutDisplay,
+ hasKeyboardShortcut,
+ isIconOnlyButton,
cancel,
save,
addShortcut,
@@ -254,54 +305,166 @@ export default defineComponent({
editShortcut,
editKeyPress,
copy,
+ onShortcutOrderEnd,
};
},
});
-awaitingKeyPress
+
-
+
Add Shortcut
-
-
+
+ Drag
+
+
+ Shortcut
+
+
+ Type
+
+
+ Value
+
+
+ Button
+
+
+ Info
+
+
+ Edit
+
+
+
+
- Key:{{ getShortcutDisplay(shortcut) }}
-
- Type:{{ shortcut.type }}
-
-
- Value: {{ shortcut.value }}
-
-
-
-
-
- mdi-card-text-outline
-
+
+
+ mdi-drag
+
+
+
+
+
+
+
+ mdi-keyboard-outline
+
+
+ Keyboard shortcut in use
+
+
+ {{ getShortcutDisplay(shortcut) }}
+
- {{ shortcut.description }}
-
-
-
- mdi-pencil
-
-
-
- mdi-delete
-
-
-
+
+
+
+ mdi-keyboard-off-outline
+
+
+ No keyboard shortcut
+
+
+
+
+ {{ shortcut.type }}
+
+
+
+
+ {{ shortcut.value }}
+
+ —
+
+
+
+
+
+
+ {{ shortcut.button.iconPrepend }}
+
+
+ {{ shortcut.button.buttonText }}
+
+
+ {{ shortcut.button.iconAppend }}
+
+
+
+ Custom UI button
+
+ —
+
+
+
+
+
+ mdi-card-text-outline
+
+
+ {{ shortcut.description || 'No description' }}
+
+
+
+
+
+ mdi-pencil
+
+
+
+
+ mdi-delete
+
+
+
+
+
@@ -473,4 +639,21 @@ awaitingKeyPress
diff --git a/client/dive-common/components/CustomUI/ButtonShortcutEditor.vue b/client/dive-common/components/CustomUI/ButtonShortcutEditor.vue
index 23932fdc..6469b019 100644
--- a/client/dive-common/components/CustomUI/ButtonShortcutEditor.vue
+++ b/client/dive-common/components/CustomUI/ButtonShortcutEditor.vue
@@ -2,7 +2,25 @@
import {
defineComponent, PropType, ref, watch,
} from 'vue';
-import { ButtonShortcut } from 'vue-media-annotator/use/AttributeTypes';
+import { AttributeShortcut, ButtonShortcut } from 'vue-media-annotator/use/AttributeTypes';
+
+function defaultButtonForType(
+ shortcutType: AttributeShortcut['type'] | undefined,
+ attributeName: string | undefined,
+ attributeColor: string | undefined,
+): ButtonShortcut {
+ if (shortcutType === 'remove') {
+ return {
+ buttonText: '',
+ iconPrepend: 'mdi-delete',
+ buttonColor: '#F44336',
+ };
+ }
+ return {
+ buttonText: attributeName || 'Button Name',
+ buttonColor: attributeColor || '#FF00FF',
+ };
+}
export default defineComponent({
name: 'ButtonShortcutEditor',
@@ -15,16 +33,34 @@ export default defineComponent({
type: Boolean,
default: true,
},
+ attributeName: {
+ type: String,
+ default: '',
+ },
+ attributeColor: {
+ type: String,
+ default: '',
+ },
+ shortcutType: {
+ type: String as PropType,
+ default: undefined,
+ },
},
setup(props, { emit }) {
const buttonShortcutEnabled = ref(!!props.value);
- const buttonShortcut = ref(props.value || { buttonText: 'Button Name', buttonColor: '#FF00FF' });
+ const buttonShortcut = ref(
+ props.value || defaultButtonForType(
+ props.shortcutType,
+ props.attributeName,
+ props.attributeColor,
+ ),
+ );
+ let syncingFromProps = false;
- watch(() => props.value, (newValue) => {
- buttonShortcutEnabled.value = !!newValue;
- buttonShortcut.value = newValue || { buttonText: 'Button Name', buttonColor: '#FF00FF' };
- });
const updateButtonShortcut = () => {
+ if (syncingFromProps) {
+ return;
+ }
if (buttonShortcutEnabled.value) {
emit('input', buttonShortcut.value);
} else {
@@ -32,19 +68,58 @@ export default defineComponent({
}
};
+ const applyTypeDefaults = () => {
+ syncingFromProps = true;
+ buttonShortcut.value = defaultButtonForType(
+ props.shortcutType,
+ props.attributeName,
+ props.attributeColor,
+ );
+ syncingFromProps = false;
+ updateButtonShortcut();
+ };
+
+ watch(() => props.value, (newValue) => {
+ syncingFromProps = true;
+ buttonShortcutEnabled.value = !!newValue;
+ buttonShortcut.value = newValue
+ || defaultButtonForType(
+ props.shortcutType,
+ props.attributeName,
+ props.attributeColor,
+ );
+ syncingFromProps = false;
+ });
+
+ watch(buttonShortcutEnabled, (enabled, wasEnabled) => {
+ if (syncingFromProps) {
+ return;
+ }
+ if (enabled && !wasEnabled) {
+ applyTypeDefaults();
+ } else {
+ updateButtonShortcut();
+ }
+ });
+
+ watch(() => props.shortcutType, (type, prevType) => {
+ if (syncingFromProps) {
+ return;
+ }
+ if (buttonShortcutEnabled.value && type && prevType && type !== prevType) {
+ applyTypeDefaults();
+ }
+ });
+
+ watch(buttonShortcut, () => {
+ updateButtonShortcut();
+ }, { deep: true });
+
return {
buttonShortcutEnabled,
buttonShortcut,
- updateButtonShortcut,
};
},
- watch: {
- buttonShortcutEnabled: 'updateButtonShortcut',
- buttonShortcut: {
- handler: 'updateButtonShortcut',
- deep: true,
- },
- },
});
@@ -54,8 +129,42 @@ export default defineComponent({
-
-
+
+
+
+
+
+
+ {{ buttonShortcut.iconPrepend }}
+
+
+
+
+
+
+
+
+
+ {{ buttonShortcut.iconAppend }}
+
+
+
diff --git a/client/dive-common/components/CustomUI/CustomUIBase.vue b/client/dive-common/components/CustomUI/CustomUIBase.vue
index 1bc34c9e..2f72bcfd 100644
--- a/client/dive-common/components/CustomUI/CustomUIBase.vue
+++ b/client/dive-common/components/CustomUI/CustomUIBase.vue
@@ -440,6 +440,18 @@ export default defineComponent({
}
}
});
+ const order = configMan.configuration.value?.customUI?.attributeButtonOrder || [];
+ if (order.length) {
+ attributeButtonList.sort((a, b) => {
+ const keyA = `${a.type}_${a.attrName}`;
+ const keyB = `${b.type}_${b.attrName}`;
+ const idxA = order.indexOf(keyA);
+ const idxB = order.indexOf(keyB);
+ const sortA = idxA === -1 ? Number.MAX_SAFE_INTEGER : idxA;
+ const sortB = idxB === -1 ? Number.MAX_SAFE_INTEGER : idxB;
+ return sortA - sortB;
+ });
+ }
return attributeButtonList;
});
diff --git a/client/dive-common/components/configurationEditors/UISettings/UIContextBar.vue b/client/dive-common/components/configurationEditors/UISettings/UIContextBar.vue
index f1855c0f..07002cae 100644
--- a/client/dive-common/components/configurationEditors/UISettings/UIContextBar.vue
+++ b/client/dive-common/components/configurationEditors/UISettings/UIContextBar.vue
@@ -1,15 +1,23 @@