@@ -68,6 +75,10 @@
margin-left: 4px;
padding-left: 16px;
}
+ .selected {
+ background: var(--color-accent);
+ color: var(--color-f-white);
+ }
& + .layer-row {
margin-top: 2px;
@@ -118,6 +129,65 @@ export default defineComponent({
const { toggle_layer_visibility } = await wasm;
toggle_layer_visibility(path);
},
+ async handleControlClick(clickedLayer: LayerPanelEntry) {
+ const index = this.layers.indexOf(clickedLayer);
+ clickedLayer.layer_data.selected = !clickedLayer.layer_data.selected;
+ this.selectionRangeEndLayer = undefined;
+ this.selectionRangeStartLayer =
+ this.layers.slice(index).filter((layer) => layer.layer_data.selected)[0] ||
+ this.layers
+ .slice(0, index)
+ .reverse()
+ .filter((layer) => layer.layer_data.selected)[0];
+ this.updateSelection();
+ },
+ async handleShiftClick(clickedLayer: LayerPanelEntry) {
+ // The two paths of the range are stored in selectionRangeStartLayer and selectionRangeEndLayer
+ // So for a new Shift+Click, select all layers between selectionRangeStartLayer and selectionRangeEndLayer(stored in prev Sft+C)
+ this.selectionRangeEndLayer = clickedLayer;
+ this.selectionRangeStartLayer = (this.selectionRangeStartLayer as LayerPanelEntry) || clickedLayer;
+ this.clearSelection();
+ this.fillSelectionRange(this.selectionRangeStartLayer, this.selectionRangeEndLayer, true);
+ this.updateSelection();
+ },
+
+ async handleClick(clickedLayer: LayerPanelEntry) {
+ this.selectionRangeStartLayer = clickedLayer;
+ this.selectionRangeEndLayer = clickedLayer;
+ this.clearSelection();
+ clickedLayer.layer_data.selected = true;
+ this.updateSelection();
+ },
+ async fillSelectionRange(start: LayerPanelEntry, end: LayerPanelEntry, selected = true) {
+ const startIndex = this.layers.indexOf(start);
+ const endIndex = this.layers.indexOf(end);
+ const [min, max] = [startIndex, endIndex].sort();
+ for (let i = min; i <= max; i += 1) {
+ this.layers[i].layer_data.selected = selected;
+ }
+ },
+ async clearSelection() {
+ this.layers.forEach((layer) => {
+ layer.layer_data.selected = false;
+ });
+ },
+ async updateSelection() {
+ const paths = this.layers.filter((layer) => layer.layer_data.selected).map((layer) => layer.path);
+ const length = paths.reduce((acc, cur) => acc + cur.length, 0) + paths.length - 1;
+ const output = new BigUint64Array(length);
+ let i = 0;
+ paths.forEach((path, index) => {
+ output.set(path, i);
+ i += path.length;
+ if (index < paths.length) {
+ // eslint-disable-next-line no-bitwise
+ output[i] = (1n << 64n) - 1n;
+ }
+ i += 1;
+ });
+ const { select_layers } = await wasm;
+ select_layers(output);
+ },
},
mounted() {
registerResponseHandler(ResponseType.ExpandFolder, (responseData: Response) => {
@@ -140,6 +210,8 @@ export default defineComponent({
MenuDirection,
SeparatorType,
layers: [] as Array,
+ selectionRangeStartLayer: undefined as LayerPanelEntry | undefined,
+ selectionRangeEndLayer: undefined as LayerPanelEntry | undefined,
};
},
components: {
diff --git a/client/web/src/response-handler.ts b/client/web/src/response-handler.ts
index ea78365ea..528d7730e 100644
--- a/client/web/src/response-handler.ts
+++ b/client/web/src/response-handler.ts
@@ -118,19 +118,30 @@ export interface LayerPanelEntry {
name: string;
visible: boolean;
layer_type: LayerType;
- collapsed: boolean;
path: BigUint64Array;
+ layer_data: LayerData;
}
function newLayerPanelEntry(input: any): LayerPanelEntry {
return {
name: input.name,
visible: input.visible,
layer_type: newLayerType(input.layer_type),
- collapsed: input.collapsed,
+ layer_data: newLayerData(input.layer_data),
path: new BigUint64Array(input.path.map((n: number) => BigInt(n))),
};
}
+export interface LayerData {
+ expanded: boolean;
+ selected: boolean;
+}
+function newLayerData(input: any): LayerData {
+ return {
+ expanded: input.expanded,
+ selected: input.selected,
+ };
+}
+
export enum LayerType {
Folder = "Folder",
Shape = "Shape",