UNPKG

@webwriter/block-based-code

Version:

Write block-based code (e.g. Scratch) and run it.

230 lines (210 loc) 7.43 kB
import { property, query } from "lit/decorators.js"; import { LitElementWw } from "@webwriter/lit"; import { CSSResult, html, LitElement, TemplateResult, } from "lit"; import HelpCircleIcon from "@tabler/icons/outline/help-circle.svg"; import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"; import SlCheckbox from "@shoelace-style/shoelace/dist/components/checkbox/checkbox.component.js"; import SlOption from "@shoelace-style/shoelace/dist/components/option/option.component.js"; import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js"; import SlTooltip from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.component.js"; import SlTree from "@shoelace-style/shoelace/dist/components/tree/tree.component.js"; import SlTreeItem from "@shoelace-style/shoelace/dist/components/tree-item/tree-item.component.js"; import { styles } from "./options.styles"; import { msg } from "../../locales"; import { OptionsChangeEvent, StageType } from "../../types"; import { BlockTypes, SelectedBlocks } from "../../lib/blockly"; enum TreeItemState { UNSELECTED, INDETERMINATE, SELECTED, } /** * The options component. */ export class Options extends LitElementWw { /** * Whether the widget are readonly. */ @property({ type: Number }) public accessor readonly: 0 | 1; /** * The selected stage type. */ @property({ type: String }) public accessor stageType: StageType; /** * The selected blocks. */ @property({ type: Array }) public accessor selectedBlocks: SelectedBlocks; /** * The available blocks. */ @property({ type: Array }) public accessor availableBlocks: BlockTypes[]; /** * @inheritDoc */ public static get scopedElements(): Record<string, typeof LitElement> { return { "sl-checkbox": SlCheckbox, "sl-select": SlSelect, "sl-option": SlOption, "sl-tree": SlTree, "sl-tree-item": SlTreeItem, "sl-tooltip": SlTooltip, "sl-icon": SlIcon, }; } /** * @inheritDoc */ public static get styles(): CSSResult[] { return [ styles, ]; } /** * @inheritDoc */ public connectedCallback(): void { super.connectedCallback(); } /** * Determines in which state a tree item folder should be. * @param category The category of the tree item. * @param blocks The blocks under the category. * @param selectedBlocksSet The set of selected blocks. * @returns True if the tree item should be indeterminate, false otherwise. * @private */ private getTreeItemFolderState(category: string, blocks: string[], selectedBlocksSet: Set<BlockTypes>): TreeItemState { const totalBlocks = blocks.length; const selectedBlocks = blocks.filter((name) => selectedBlocksSet.has(`${category}:${name}` as BlockTypes)).length; if (selectedBlocks === 0) { return TreeItemState.UNSELECTED; } if (selectedBlocks === totalBlocks) { return TreeItemState.SELECTED; } return TreeItemState.INDETERMINATE; } /** * @inheritDoc */ public render(): TemplateResult { const selectedBlocksSet = new Set(this.selectedBlocks); const availableBlocksMap = new Map<string, string[]>(); this.availableBlocks.forEach((block: BlockTypes) => { const [category, name] = block.split(":") as [string, string]; if (!availableBlocksMap.has(category)) { availableBlocksMap.set(category, []); } if (name) { availableBlocksMap.get(category)!.push(name); } }); return html` <div class="group"> <sl-checkbox .checked=${this.readonly === 1} @sl-change=${this.handleReadonlyChange}> <span class="label"> ${msg("OPTIONS.READONLY")} <sl-tooltip content=${msg("OPTIONS.READONLY_TOOLTIP")}> <sl-icon src="${HelpCircleIcon}"></sl-icon> </sl-tooltip> </span> </sl-checkbox> </div> <div class="group"> <span class="label"> ${msg("OPTIONS.STAGE")} <sl-tooltip content=${msg("OPTIONS.STAGE_TOOLTIP")}> <sl-icon src="${HelpCircleIcon}"></sl-icon> </sl-tooltip> </span> <sl-select value=${this.stageType} @sl-change=${this.handleStageTypeChange} hoist> ${Object.values(StageType).map((type) => html` <sl-option value=${type}> ${msg(`OPTIONS.STAGE_TYPES.${type.toUpperCase() as Uppercase<StageType>}`)} </sl-option> `)} </sl-select> </div> <div class="group"> <span class="label"> ${msg("OPTIONS.AVAILABLE_BLOCKS")} <sl-tooltip content=${msg("OPTIONS.AVAILABLE_BLOCKS_TOOLTIP")}> <sl-icon src="${HelpCircleIcon}"></sl-icon> </sl-tooltip> </span> <sl-tree selection="multiple" @sl-selection-change=${this.handleSelectedBlocksChange}> <sl-tree-item expanded> all ${Array.from(availableBlocksMap.entries()).map(([category, blocks]) => { if (blocks.length === 0) { return html` <sl-tree-item ?selected=${selectedBlocksSet.has(category as BlockTypes)} data-block-key=${`${category}`}> ${category} </sl-tree-item> `; } const folderState = this.getTreeItemFolderState(category, blocks, selectedBlocksSet); return html` <sl-tree-item .indeterminate=${folderState === TreeItemState.INDETERMINATE} ?selected=${folderState === TreeItemState.SELECTED}> ${category} ${blocks.map((name) => html` <sl-tree-item ?selected=${selectedBlocksSet.has(`${category}:${name}` as BlockTypes)} data-block-key=${`${category}:${name}`}> ${name} </sl-tree-item> `)} </sl-tree-item> `; })} </sl-tree-item> </sl-tree> </div> `; } /** * Handles the readonly change event. * @param event The change event. * @private */ private handleReadonlyChange(event): void { const changeEvent = new OptionsChangeEvent({ readonly: event.target.checked ? 1 : 0, }); this.dispatchEvent(changeEvent); } /** * Handles the stage type change event. * @param event The change event. * @private */ private handleStageTypeChange(event): void { const changeEvent = new OptionsChangeEvent({ stageType: event.target.value, }); this.dispatchEvent(changeEvent); } /** * Handles the selected blocks change event. * @param event The change event. * @private */ private handleSelectedBlocksChange(event: CustomEvent<{ selection: SlTreeItem[] }>): void { const selectedBlocks = event.detail.selection .filter((item) => item.getAttribute("data-block-key")) .map((item) => item.getAttribute("data-block-key") as BlockTypes); const changeEvent = new OptionsChangeEvent({ selectedBlocks, }); this.dispatchEvent(changeEvent); } }