@infinite-canvas-tutorial/webcomponents
Version:
WebComponents UI implementation
297 lines (290 loc) • 12.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LayersPanel = void 0;
const lit_1 = require("lit");
const context_1 = require("@lit/context");
const map_js_1 = require("lit/directives/map.js");
const when_js_1 = require("lit/directives/when.js");
const decorators_js_1 = require("lit/decorators.js");
const ecs_1 = require("@infinite-canvas-tutorial/ecs");
const context_2 = require("../context");
const event_1 = require("../event");
const localize_1 = require("@lit/localize");
let LayersPanel = class LayersPanel extends lit_1.LitElement {
connectedCallback() {
super.connectedCallback();
this.api.element.addEventListener(event_1.Event.SELECTED_NODES_CHANGED, (e) => {
const { selected } = e.detail;
// Scroll to the selected layer
if (selected.length > 0) {
const scrollToId = this.generateLayersPanelItemId(selected[0]);
const scrollToElement = this.shadowRoot.querySelector(`#${scrollToId}`);
const container = this.shadowRoot.querySelector('.container');
if (scrollToElement && container) {
// 计算元素相对于容器的位置
const elementTop = scrollToElement.offsetTop;
const elementHeight = scrollToElement.offsetHeight;
const containerHeight = container.clientHeight;
// 如果元素不在视口内,则滚动到合适位置
const currentScrollTop = container.scrollTop;
const elementBottom = elementTop + elementHeight;
const visibleTop = currentScrollTop;
const visibleBottom = currentScrollTop + containerHeight;
if (elementTop < visibleTop || elementBottom > visibleBottom) {
// 计算目标滚动位置,让元素在视口中央
const targetScrollTop = elementTop - (containerHeight - elementHeight) / 2;
container.scrollTo({
top: Math.max(0, targetScrollTop),
behavior: 'smooth',
});
}
}
}
});
}
generateLayersPanelItemId(node) {
return `layers-panel-item-${node.id}`;
}
handleClose() {
this.api.setAppState({
taskbarSelected: this.appState.taskbarSelected.filter((task) => task !== ecs_1.Task.SHOW_LAYERS_PANEL),
});
}
handleDelete() {
const { layersSelected } = this.api.getAppState();
this.api.deleteNodesById(layersSelected);
// Try to select the next layer
const nextLayer = this.nodes.find((node) => !layersSelected.includes(node.id));
if (nextLayer) {
this.api.selectNodes([nextLayer]);
}
this.api.record();
}
handleAdd() {
// TODO: add new layer
// this.api.addNodes(this.nodes);
}
handleSelect(e, id) {
const { layersSelected } = this.api.getAppState();
if (layersSelected.length === 1 && layersSelected.includes(id)) {
return;
}
const node = this.api.getNodeById(id);
if (!node.locked) {
this.api.selectNodes([node], e.shiftKey);
this.api.record();
}
}
handleBringToFront() {
const { layersSelected } = this.api.getAppState();
const node = this.api.getNodeById(layersSelected[0]);
this.api.bringToFront(node);
this.api.record();
}
handleBringForward() {
const { layersSelected } = this.api.getAppState();
const node = this.api.getNodeById(layersSelected[0]);
this.api.bringForward(node);
this.api.record();
}
handleSendBackward() {
const { layersSelected } = this.api.getAppState();
const node = this.api.getNodeById(layersSelected[0]);
this.api.sendBackward(node);
this.api.record();
}
handleSendToBack() {
const { layersSelected } = this.api.getAppState();
const node = this.api.getNodeById(layersSelected[0]);
this.api.sendToBack(node);
this.api.record();
}
render() {
const { layersSelected, taskbarSelected } = this.appState;
const sortedNodes = this.nodes
.filter((node) => node.parentId === undefined)
.map((node) => {
return this.api.getEntity(node);
})
.sort(ecs_1.sortByFractionalIndex)
.map((entity) => {
return this.api.getNodeByEntity(entity);
});
const isSelectedEmpty = layersSelected.length === 0;
let bringForwardDisabled = false;
let sendBackwardDisabled = false;
if (layersSelected.length === 1) {
const node = this.api.getNodeById(layersSelected[0]);
if (node) {
const children = this.api
.getSiblings(node)
.filter((child) => !child.has(ecs_1.UI));
const maxZIndex = Math.max(...children.map((child) => child.read(ecs_1.ZIndex).value));
const minZIndex = Math.min(...children.map((child) => child.read(ecs_1.ZIndex).value));
if (node.zIndex === maxZIndex) {
bringForwardDisabled = true;
}
if (node.zIndex === minZIndex) {
sendBackwardDisabled = true;
}
}
}
return taskbarSelected.includes(ecs_1.Task.SHOW_LAYERS_PANEL)
? (0, lit_1.html) `<section>
<h4>
${(0, localize_1.msg)((0, localize_1.str) `Layers`)}
<sp-action-button quiet size="s" =${this.handleClose}>
<sp-icon-close slot="icon"></sp-icon-close>
</sp-action-button>
</h4>
<sp-action-group class="actions">
<sp-action-button quiet size="s" disabled =${this.handleAdd}>
<sp-tooltip self-managed placement="bottom">
${(0, localize_1.msg)((0, localize_1.str) `Add new layer`)}
</sp-tooltip>
<sp-icon-add slot="icon"></sp-icon-add>
</sp-action-button>
<sp-action-menu
label=${(0, localize_1.msg)((0, localize_1.str) `Arrange layers`)}
quiet
size="s"
.disabled=${layersSelected.length === 0}
>
<sp-icon-show-all-layers slot="icon"></sp-icon-show-all-layers>
<sp-menu-group>
<span slot="header">${(0, localize_1.msg)((0, localize_1.str) `Arrange layers`)}</span>
<sp-menu-item
?disabled=${isSelectedEmpty || bringForwardDisabled}
=${this.handleBringToFront}
>
<sp-icon-layers-bring-to-front
slot="icon"
></sp-icon-layers-bring-to-front>
${(0, localize_1.msg)((0, localize_1.str) `Bring to front`)}
</sp-menu-item>
<sp-menu-item
?disabled=${isSelectedEmpty || bringForwardDisabled}
=${this.handleBringForward}
>
<sp-icon-layers-forward slot="icon"></sp-icon-layers-forward>
${(0, localize_1.msg)((0, localize_1.str) `Bring forward`)}
</sp-menu-item>
<sp-menu-item
?disabled=${isSelectedEmpty || sendBackwardDisabled}
=${this.handleSendBackward}
>
<sp-icon-layers-backward
slot="icon"
></sp-icon-layers-backward>
${(0, localize_1.msg)((0, localize_1.str) `Send backward`)}
</sp-menu-item>
<sp-menu-item
?disabled=${isSelectedEmpty || sendBackwardDisabled}
=${this.handleSendToBack}
>
<sp-icon-layers-send-to-back
slot="icon"
></sp-icon-layers-send-to-back>
${(0, localize_1.msg)((0, localize_1.str) `Send to back`)}
</sp-menu-item>
</sp-menu-group>
</sp-action-menu>
<sp-action-button
quiet
size="s"
=${this.handleDelete}
.disabled=${layersSelected.length === 0}
>
<sp-tooltip self-managed placement="bottom">
${(0, localize_1.msg)((0, localize_1.str) `Delete`)}
</sp-tooltip>
<sp-icon-delete slot="icon"></sp-icon-delete>
</sp-action-button>
</sp-action-group>
<div class="container">
${(0, map_js_1.map)(sortedNodes, (node) => {
return this.renderParentNode(node);
})}
</div>
</section>`
: null;
}
renderParentNode(node, depth = 0) {
const children = this.api.getChildren(node);
const sortedNodes = children.sort(ecs_1.sortByFractionalIndex).map((entity) => {
return this.api.getNodeByEntity(entity);
});
const { layersSelected, layersHighlighted, layersExpanded } = this.appState;
const hasChildren = sortedNodes.length > 0;
const isExpanded = layersExpanded.includes(node.id);
return (0, lit_1.html) `<ic-spectrum-layers-panel-item
id=${this.generateLayersPanelItemId(node)}
.node=${node}
.depth=${depth}
.hasChildren=${hasChildren}
draggable
=${(e) => this.handleSelect(e, node.id)}
?selected=${layersSelected.includes(node.id)}
?highlighted=${layersHighlighted.includes(node.id)}
></ic-spectrum-layers-panel-item>
${(0, when_js_1.when)(hasChildren && isExpanded, () => (0, lit_1.html) `
${(0, map_js_1.map)(sortedNodes, (node) => {
// TODO: virtual scroll for better performance
return this.renderParentNode(node, depth + 1);
})}
`)}`;
}
};
exports.LayersPanel = LayersPanel;
LayersPanel.styles = (0, lit_1.css) `
section {
display: flex;
flex-direction: column;
background: var(--spectrum-gray-100);
border-radius: var(--spectrum-corner-radius-200);
margin: 4px;
filter: drop-shadow(
var(--spectrum-drop-shadow-color) 0px var(--spectrum-drop-shadow-y)
var(--spectrum-drop-shadow-blur)
);
}
h4 {
padding: var(--spectrum-global-dimension-size-100);
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
color: canvastext;
}
.actions {
padding: 4px;
padding-top: 0;
}
.container {
height: 300px;
overflow: hidden;
overflow-y: auto;
scroll-behavior: smooth;
scroll-padding: 8px;
}
`;
__decorate([
(0, context_1.consume)({ context: context_2.appStateContext, subscribe: true })
], LayersPanel.prototype, "appState", void 0);
__decorate([
(0, context_1.consume)({ context: context_2.nodesContext, subscribe: true })
], LayersPanel.prototype, "nodes", void 0);
__decorate([
(0, context_1.consume)({ context: context_2.apiContext, subscribe: true })
], LayersPanel.prototype, "api", void 0);
exports.LayersPanel = LayersPanel = __decorate([
(0, decorators_js_1.customElement)('ic-spectrum-layers-panel'),
(0, localize_1.localized)()
], LayersPanel);
//# sourceMappingURL=layers-panel.js.map