@infinite-canvas-tutorial/webcomponents
Version:
WebComponents UI implementation
333 lines (330 loc) • 12.4 kB
JavaScript
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;
};
import { localized, msg, str } from '@lit/localize';
import { html, css, LitElement } from 'lit';
import { customElement, query, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import { consume } from '@lit/context';
import { CheckboardStyle, readSystemClipboard, ThemeMode, } from '@infinite-canvas-tutorial/ecs';
import { apiContext, appStateContext } from '../context';
import { executeCopy, executeCut, executePaste } from './context-menu';
export var ExportFormat;
(function (ExportFormat) {
ExportFormat["SVG"] = "svg";
ExportFormat["PNG"] = "png";
ExportFormat["JPEG"] = "jpeg";
})(ExportFormat || (ExportFormat = {}));
let TopNavbar = class TopNavbar extends LitElement {
constructor() {
super(...arguments);
this.isClipboardEmpty = true;
this.binded = false;
this.handleKeyDown = (e) => {
// Canvas is focused
if (!this.api || document.activeElement !== this.api.element) {
return;
}
if (e.key === 'z' && e.metaKey) {
e.preventDefault();
if (e.shiftKey) {
this.handleRedo();
}
else {
this.handleUndo();
}
}
};
}
firstUpdated(_changedProperties) {
var _a;
(_a = this.actionMenu) === null || _a === void 0 ? void 0 : _a.addEventListener('sp-opened', this.handleOpen);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener('keydown', this.handleKeyDown);
}
handleExport(event) {
const format = event.target.value;
this.api.export(format);
}
handleEdit(event) {
const value = event.target.value;
if (value === 'undo') {
this.handleUndo();
}
else if (value === 'redo') {
this.handleRedo();
}
else if (value === 'cut') {
this.handleCut();
}
else if (value === 'copy') {
this.handleCopy();
}
else if (value === 'paste') {
this.handlePaste();
}
}
handleConfigView(event) {
const value = event.target.value;
if (value === 'grid') {
this.api.setAppState({
checkboardStyle: CheckboardStyle.GRID,
});
}
else {
this.api.setAppState({
checkboardStyle: CheckboardStyle.NONE,
});
}
}
handleConfigPreferences(event) {
const selected = event.target.selected;
this.api.setAppState({
snapToPixelGridEnabled: selected.includes('snapToPixelGrid'),
snapToObjectsEnabled: selected.includes('snapToObjects'),
});
}
handleConfigTheme(event) {
const selected = event.target.selected[0];
let themeMode = selected === 'dark' ? ThemeMode.DARK : ThemeMode.LIGHT;
if (selected === 'system') {
themeMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeMode.DARK : ThemeMode.LIGHT;
}
this.api.setThemeMode(themeMode);
}
handleUndo() {
this.api.undo();
}
handleRedo() {
this.api.redo();
}
handleCut() {
executeCut(this.api, this.appState);
}
handleCopy() {
executeCopy(this.api, this.appState);
}
handlePaste() {
executePaste(this.api, this.appState);
}
handleOpen() {
readSystemClipboard().then((clipboard) => {
this.isClipboardEmpty = Object.keys(clipboard).length === 0;
});
}
handleConfigLanguage(event) {
const selected = event.target.selected[0];
this.api.setAppState({
language: selected,
});
this.api.setLocale(selected);
}
render() {
var _a;
// FIXME: wait for the element to be ready.
if (((_a = this.api) === null || _a === void 0 ? void 0 : _a.element) && !this.binded) {
this.api.element.addEventListener('keydown', this.handleKeyDown);
this.binded = true;
}
const isSelectedEmpty = this.appState.layersSelected.length === 0;
return when(this.appState.topbarVisible, () => {
var _a, _b, _c, _d;
return html `
<div class="top-navbar">
<sp-action-menu size="m" label="Main menu" quiet>
<sp-tooltip slot="tooltip" self-managed placement="bottom">
${msg(str `Main menu`)}
</sp-tooltip>
<sp-icon-show-menu slot="icon" size="l"></sp-icon-show-menu>
<sp-menu-item>
${msg(str `Edit`)}
<sp-menu slot="submenu" =${this.handleEdit}>
<sp-menu-item
value="undo"
?disabled=${(_a = this.api) === null || _a === void 0 ? void 0 : _a.isUndoStackEmpty()}
>
${msg(str `Undo`)}
<kbd slot="value">⌘Z</kbd>
</sp-menu-item>
<sp-menu-item
value="redo"
?disabled=${(_b = this.api) === null || _b === void 0 ? void 0 : _b.isRedoStackEmpty()}
>
${msg(str `Redo`)}
<kbd slot="value">⇧⌘Z</kbd>
</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item value="cut" ?disabled=${isSelectedEmpty}>
${msg(str `Cut`)}
<kbd slot="value">⌘X</kbd>
</sp-menu-item>
<sp-menu-item value="copy" ?disabled=${isSelectedEmpty}>
${msg(str `Copy`)}
<kbd slot="value">⌘C</kbd>
</sp-menu-item>
<sp-menu-item
value="paste"
?disabled=${this.isClipboardEmpty}
>
${msg(str `Paste`)}
<kbd slot="value">⌘V</kbd>
</sp-menu-item>
</sp-menu>
</sp-menu-item>
<sp-menu-item>
${msg(str `View`)}
<sp-menu
slot="submenu"
selects="multiple"
.selected=${this.appState.checkboardStyle ===
CheckboardStyle.GRID
? ['grid']
: []}
=${this.handleConfigView}
>
<sp-menu-item value="grid"> ${msg(str `Grid`)} </sp-menu-item>
</sp-menu>
</sp-menu-item>
<sp-menu-item>
${msg(str `Preferences`)}
<sp-menu
slot="submenu"
selects="multiple"
.selected=${[
this.appState.snapToPixelGridEnabled
? 'snapToPixelGrid'
: undefined,
this.appState.snapToObjectsEnabled
? 'snapToObjects'
: undefined,
].filter(Boolean)}
=${this.handleConfigPreferences}
>
<sp-menu-item value="snapToPixelGrid">
${msg(str `Snap to pixel grid`)}
</sp-menu-item>
<sp-menu-item value="snapToObjects">
${msg(str `Snap to objects`)}
</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item>
${msg(str `Theme`)}
<sp-menu
slot="submenu"
selects="single"
.selected=${[this.appState.themeMode]}
=${this.handleConfigTheme}
>
<sp-menu-item value="light">
${msg(str `Light`)}
</sp-menu-item>
<sp-menu-item value="dark">
${msg(str `Dark`)}
</sp-menu-item>
<sp-menu-item value="system">
${msg(str `System`)}
</sp-menu-item>
</sp-menu>
</sp-menu-item>
<sp-menu-item>
${msg(str `Language`)}
<sp-menu
slot="submenu"
selects="single"
.selected=${[this.appState.language]}
=${this.handleConfigLanguage}
>
<sp-menu-item value="en"> English </sp-menu-item>
<sp-menu-item value="es-419"> Español </sp-menu-item>
<sp-menu-item value="zh-Hans"> 中文 (简体) </sp-menu-item>
</sp-menu>
</sp-menu-item>
</sp-menu>
</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item>
${msg(str `Export as...`)}
<sp-menu slot="submenu" =${this.handleExport}>
<sp-menu-item value=${ExportFormat.SVG}>SVG</sp-menu-item>
<sp-menu-item value=${ExportFormat.PNG}>PNG</sp-menu-item>
<sp-menu-item value=${ExportFormat.JPEG}>JPEG</sp-menu-item>
</sp-menu>
</sp-menu-item>
</sp-action-menu>
<div class="actions">
<sp-action-button
quiet
=${this.handleUndo}
?disabled=${(_c = this.api) === null || _c === void 0 ? void 0 : _c.isUndoStackEmpty()}
>
<sp-icon-undo slot="icon"></sp-icon-undo>
<sp-tooltip self-managed placement="bottom">
${msg(str `Undo`)}
</sp-tooltip>
</sp-action-button>
<sp-action-button
quiet
=${this.handleRedo}
?disabled=${(_d = this.api) === null || _d === void 0 ? void 0 : _d.isRedoStackEmpty()}
>
<sp-icon-redo slot="icon"></sp-icon-redo>
<sp-tooltip self-managed placement="bottom">
${msg(str `Redo`)}
</sp-tooltip>
</sp-action-button>
<ic-spectrum-zoom-toolbar />
</div>
</div>
`;
});
}
};
TopNavbar.styles = css `
.top-navbar {
position: relative;
display: flex;
justify-content: space-between;
padding: var(--spectrum-global-dimension-size-100);
background: var(--spectrum-gray-100);
}
sp-menu-item {
width: 200px;
}
kbd {
font-family: var(--spectrum-alias-body-text-font-family);
letter-spacing: 0.1em;
white-space: nowrap;
border: none;
padding: none;
padding: 0;
line-height: normal;
}
.actions {
display: flex;
align-items: center;
gap: var(--spectrum-global-dimension-size-100);
}
`;
__decorate([
consume({ context: appStateContext, subscribe: true })
], TopNavbar.prototype, "appState", void 0);
__decorate([
consume({ context: apiContext, subscribe: true })
], TopNavbar.prototype, "api", void 0);
__decorate([
state()
], TopNavbar.prototype, "isClipboardEmpty", void 0);
__decorate([
query('sp-action-menu', true)
], TopNavbar.prototype, "actionMenu", void 0);
TopNavbar = __decorate([
customElement('ic-spectrum-top-navbar'),
localized()
], TopNavbar);
export { TopNavbar };
//# sourceMappingURL=top-navbar.js.map