Implement item layers#78
Conversation
zmarinov-astea
commented
Jul 2, 2026
- Added a layer column for the Items.
- Every item strives to have a unique non-negative layer. Items with equal layers should be avoided, since their order is not determined, but they don't break the layering logic.
- Items in pixi use zIndex now, since changing the snapshot resolution redraws the item on top otherwise.
- Layers are normalized on the client every time the user clicks "Bring to top" or "Bring to back".
- Refactored export schemas, so the V7 export schema can now handle the layers.
| tapestryId, | ||
| } as MediaItemCreateDto & { type: T } | ||
| layer: DEFAULT_LAYER, | ||
| } satisfies MediaItemCreateDto as MediaItemCreateDto & { type: T } |
There was a problem hiding this comment.
Why the satisfies operator?
There was a problem hiding this comment.
To enforce type constraint on the object. Otherwise a missing layer prop would not result a compile time error.
| ) | ||
|
|
||
| sortBy(frontItems, ['layer']) | ||
| sortBy(backItems, ['layer']) |
There was a problem hiding this comment.
Doesn't sortBy return a new array and not sort it in-place? Moreover it might be a bit cleaner to sort the array returned by idMapToArray(store.get('items'))
There was a problem hiding this comment.
Hm, sortBy really is not used properly. But I won't sort the initial array, since it is not guaranteed that lodash' partition will preserve the ordering.
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| export function getMaxLayer(store: Store<EditableTapestryViewModel, any>) { | ||
| const elements = idMapToArray(store.get('items')).map((element) => element.dto.layer) | ||
| return elements.length === 0 ? DEFAULT_LAYER : Math.max(...elements) |
There was a problem hiding this comment.
You could probably also use maxBy(idMapToArray(store.get('items')), 'dto.layer')?.dto.layer ?? DEFAULT_LAYER
| }), | ||
| ), | ||
| ) | ||
| } |
There was a problem hiding this comment.
Why is this (and isDraggingItems) no longer needed?
There was a problem hiding this comment.
This logic is being handled in the core-client item controller, and this function here was not being called at all. Also in the editor-item-controller the drag handler is set so that it is impossible to start a drag operation without dragging items.
| this.getGroups().forEach((group) => this.renderViewModel(group, selection, interactiveElement)) | ||
| this.getRels().forEach((rel) => this.renderViewModel(rel, selection, interactiveElement)) | ||
| this.getItems().forEach((item) => this.renderViewModel(item, selection, interactiveElement)) | ||
| sortBy(this.getItems(), 'dto.layer').forEach((item, zIndex) => |
There was a problem hiding this comment.
I don't know how likely is this to cause performance issue, but isn't there an easy way to make sure we keep the items sorted and not sort them on every render?
There was a problem hiding this comment.
Yes, sorting is only managed by the zIndex, I put this additional sortBy to normalize the values, but we probably don't need it
| sortBy(frontItems, ['layer']) | ||
| sortBy(backItems, ['layer']) | ||
| const allItems = [...backItems, ...frontItems] | ||
| store.dispatch(...allItems.map((item, layer) => updateItem(item.dto.id, { dto: { layer } }))) |
There was a problem hiding this comment.
This will make a single batch-mutation request instead of N updates, right?
There was a problem hiding this comment.
Yes, this updateItem command is also used when manipulating a selection - deleting, arranging, grouping....