diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 8db5d2fe59..a9170864c2 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -474,7 +474,7 @@ export function valueListFieldConfig(field: string): AplHelpers.APLPickerBuilder index: number, config: ListItemPickerConfig, APLValue | undefined>, ) => new APLValuePicker(parent, player, config), - allowedActions: ['create', 'delete'], + allowedActions: ['copy', 'create', 'delete', 'move'], actions: { create: { useIcon: true, diff --git a/ui/core/components/pickers/list_picker.tsx b/ui/core/components/pickers/list_picker.tsx index 5143996f8c..6ff5ffa7b4 100644 --- a/ui/core/components/pickers/list_picker.tsx +++ b/ui/core/components/pickers/list_picker.tsx @@ -206,6 +206,11 @@ export class ListPicker extends Input = { elem: itemContainer, picker: itemPicker, idx: index }; if (this.actionEnabled('move')) { + itemContainer.classList.add('draggable'); + if (this.config.itemLabel) { + itemContainer.classList.add(this.config.itemLabel.toLowerCase().replace(' ', '-')); + } + const moveButton = ListPicker.makeActionElem('list-picker-item-move', 'fa-arrows-up-down'); itemHeader.appendChild(moveButton); @@ -225,11 +230,42 @@ export class ListPicker extends Input { + moveButton.classList.add('hover'); + }, + { signal: this.signal }, + ); + + moveButton.addEventListener( + 'mouseleave', + () => { + moveButton.classList.remove('hover'); + }, + { signal: this.signal }, + ); + + moveButton.addEventListener( + 'mousedown', + () => { + itemContainer.setAttribute('draggable', 'true'); + }, + { signal: this.signal }, + ); + + moveButton.addEventListener( + 'mouseup', + () => { + itemContainer.removeAttribute('draggable'); + }, + { signal: this.signal }, + ); + + itemContainer.addEventListener( 'dragstart', event => { - if (event.target == moveButton) { + if (event.target == itemContainer) { event.dataTransfer!.dropEffect = 'move'; event.dataTransfer!.effectAllowed = 'move'; itemContainer.classList.add('dragfrom'); @@ -246,10 +282,14 @@ export class ListPicker extends Input { - if (!curDragData || curDragData.listPicker != this) { + if ( + !curDragData || + curDragData.listPicker.config.itemLabel !== this.config.itemLabel || + (this.config.itemLabel === 'Action' && curDragData.listPicker !== this) + ) { return; } - event.preventDefault(); + event.stopPropagation(); dragEnterCounter++; itemContainer.classList.add('dragto'); }, @@ -259,7 +299,11 @@ export class ListPicker extends Input { - if (!curDragData || curDragData.listPicker != this) { + if ( + !curDragData || + curDragData.listPicker.config.itemLabel !== this.config.itemLabel || + (this.config.itemLabel === 'Action' && curDragData.listPicker !== this) + ) { return; } event.preventDefault(); @@ -274,30 +318,73 @@ export class ListPicker extends Input { - if (!curDragData || curDragData.listPicker != this) { + if (!curDragData || curDragData.listPicker.config.itemLabel !== this.config.itemLabel) { + return; + } + if (this.config.itemLabel === 'Action' && curDragData.listPicker !== this) { + event.dataTransfer!.dropEffect = 'none'; return; } + event.dataTransfer!.dropEffect = 'move'; + event.stopPropagation(); event.preventDefault(); }, { signal: this.signal }, ); + itemContainer.addEventListener( + 'dragend', + event => { + if ( + !curDragData || + curDragData.listPicker.config.itemLabel !== this.config.itemLabel || + (this.config.itemLabel === 'Action' && curDragData.listPicker !== this) + ) { + return; + } + event.stopPropagation(); + itemContainer.removeAttribute('draggable'); + curDragData.item.elem.removeAttribute('draggable'); + curDragData.item.elem.classList.remove('dragfrom'); + [...document.querySelectorAll('.dragto,.hover')].forEach(elem => { + elem.classList.remove('dragto'); + elem.classList.remove('hover'); + }); + curDragData = null; + }, + { signal: this.signal }, + ); + itemContainer.addEventListener( 'drop', event => { - if (!curDragData || curDragData.listPicker != this) { + if (!curDragData || curDragData.listPicker.config.itemLabel !== this.config.itemLabel) { return; } - event.preventDefault(); - dragEnterCounter = 0; - itemContainer.classList.remove('dragto'); + event.stopPropagation(); + itemContainer.removeAttribute('draggable'); + curDragData.item.elem.removeAttribute('draggable'); curDragData.item.elem.classList.remove('dragfrom'); + [...document.querySelectorAll('.dragto,.hover')].forEach(elem => { + elem.classList.remove('dragto'); + elem.classList.remove('hover'); + }); const srcIdx = curDragData.item.idx; const dstIdx = index; const newList = this.config.getValue(this.modObject); - const arrElem = newList[srcIdx]; - newList.splice(srcIdx, 1); + let arrElem; + + if (curDragData.listPicker !== this) { + const oldList = curDragData.listPicker.config.getValue(curDragData.listPicker.modObject); + arrElem = oldList[srcIdx]; + oldList.splice(srcIdx, 1); + curDragData.listPicker.config.setValue(TypedEvent.nextEventID(), curDragData.listPicker.modObject, oldList); + } else { + arrElem = newList[srcIdx]; + newList.splice(srcIdx, 1); + } + newList.splice(dstIdx, 0, arrElem); this.config.setValue(TypedEvent.nextEventID(), this.modObject, newList); @@ -326,6 +413,22 @@ export class ListPicker extends Input copyButtonTooltip?.destroy()); + + copyButton.addEventListener( + 'mouseenter', + () => { + copyButton.classList.add('hover'); + }, + { signal: this.signal }, + ); + + copyButton.addEventListener( + 'mouseleave', + () => { + copyButton.classList.remove('hover'); + }, + { signal: this.signal }, + ); } if (this.actionEnabled('delete')) { @@ -350,6 +453,22 @@ export class ListPicker extends Input deleteButtonTooltip?.destroy()); + + deleteButton.addEventListener( + 'mouseenter', + () => { + deleteButton.classList.add('hover'); + }, + { signal: this.signal }, + ); + + deleteButton.addEventListener( + 'mouseleave', + () => { + deleteButton.classList.remove('hover'); + }, + { signal: this.signal }, + ); } } diff --git a/ui/scss/core/components/_list_picker.scss b/ui/scss/core/components/_list_picker.scss index fde58d5573..fcacc0e5ed 100644 --- a/ui/scss/core/components/_list_picker.scss +++ b/ui/scss/core/components/_list_picker.scss @@ -21,6 +21,12 @@ .list-picker-item-container { border: var(--border-default); + &.draggable:has(> .list-picker-item-header .list-picker-item-move.hover), + &.draggable.value:has(> .list-picker-item-header .list-picker-item-copy.hover), + &.draggable.value:has(> .list-picker-item-header .list-picker-item-delete.hover) { + filter: drop-shadow(-1px 1px 1px rgba(255, 255, 255, 0.45)) drop-shadow(1px -1px 1px rgba(255, 255, 255, 0.45)); + } + &:not(:last-child) { margin-bottom: var(--spacer-3); } @@ -32,7 +38,6 @@ padding: 0; border: 0; margin: 0; - flex: 0; } } diff --git a/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss b/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss index 7a0629abd6..30caa9924e 100644 --- a/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss +++ b/ui/scss/core/components/individual_sim_ui/_apl_rotation_picker.scss @@ -25,6 +25,7 @@ .list-picker-item-header { align-items: flex-start; + justify-content: flex-end; } & > .list-picker-item {