import { LitElement, html, css, TemplateResult } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { Styles } from './types/types'; import { applyDrag } from './utils/drag'; import ColorComponent from './components/ColorComponent'; import MoreComponent from './components/MoreComponent'; import SemiSelectorComponent from './components/SemiSelectorComponent'; import { useStore, ProviderStore } from './state/store'; import { STYLES } from './styles'; /** * Ol Toolbar element */ export const INNER_RADIUS = 50; export const OUTER_RADIUS = 180; @customElement('ol-toolbar') export class OlToolbar extends LitElement { static override styles = [ css` :host { position: fixed; top: 50%; left: 90%; display: flex; justify-content: flex-end; align-items: center; z-index: 10000; } #ol-toolbar { position: relative; flex-direction: column; align-items: center; justify-content: 'center'; transform: translateY(-50%) translateX(-100%); } #radial-buttons { position: absolute; display: flex; top: 50%; right: 50%; justify-content: center; align-items: center; z-index: 100; transform: scale(1); transition: all .2s ease-in-out; } .buttons { display: flex; z-index: 1000; } .buttons--collapsed { display: none; } .container { display: flex; justify-content: center; align-items: center; } .more-container { display: flex; justify-content: center; align-items: center; } ::-webkit-scrollbar { display: none; } button { background: white; border-radius: 50%; /* Text Styles */ color: gray; outline: none; border: none; background-color: white; } button:hover { opacity: .8; cursor: pointer; } /* Icon Styles */ .material-icons { font-family: 'Material Icons'; font-weight: normal; font-style: normal; font-size: 20px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; font-feature-settings: 'liga'; -webkit-font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; } .radial-button { border: none; border-radius: 0; background: none; } #drag { display: flex; justify-content: center; align-items: center; cursor: move; aspect-ratio: 1; align-self: stretch; text-align: center; color: black; background: white !important; z-index: 1000; vertical-align: center; background: white; transition: all .1s ease-in-out; } #drag:hover { transform: rotate(45deg); } #toolbar-bg { position: absolute; border-radius: 50%; z-index: 0; height: ${INNER_RADIUS * 2}px; width: ${INNER_RADIUS * 2}px; background: white; box-shadow: 0 0 2px 2px rgba(0,0,0, .2); transition: all .2s ease-in-out; } #radial-more { position: absolute; } ` ]; @property() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Lit does not support functions by default updateStyles: (key: K, value: Styles[K]) => void; @state() moreToolbar: ProviderStore['moreToolbar'] = useStore(this as any).moreToolbar; @state() scroll_: ProviderStore['scroll_'] = useStore(this as any).scroll_; @state() currentComponent: string | null = null; @state() additionalComponent: TemplateResult<1> | null = null; private collapseAndExpand(host: HTMLElement): void { const visibility = host.shadowRoot?.getElementById("radial-buttons")?.style.transform == "scale(1)" ? 1 : 0; const buttons = host.shadowRoot?.getElementById("radial-buttons"); const more = host.shadowRoot?.getElementById("radial-more"); const background = host.shadowRoot?.getElementById("toolbar-bg"); if (buttons && background) { buttons.style.transform = visibility ? 'scale(.3)' : 'scale(1)'; background.style.transform = visibility ? 'scale(.3)' : 'scale(1)'; if (more) more.style.transform = visibility ? 'scale(0)' : 'scale(1)'; } } // Function that shows a component if it is not already showing // otherwise it will change it to the new component or if the // component is the same it will hide the component private showComponent(id: keyof typeof this.components): void { //const more = this.shadowRoot?.getElementById("radial-more"); //if (!more) return; if (this.currentComponent == id) { this.currentComponent = null; } else { this.currentComponent = id; } } // The components that are spawned by the buttons. private components = { fontSize: { icon: 'title', component: new SemiSelectorComponent("font-size", (fs) => { this.updateStyles("font-size", fs as Styles["font-size"]) }, "font-size: ?; line-height: ?", STYLES["font-size"].reduce((acc, key) => ({ ...acc, [key]: "Abc"}), {})), }, color: { icon: 'format_color_text', component: new ColorComponent((color: Styles["color"]) => { this.updateStyles("color", color as Styles["color"]) }), }, fontFamily: { icon: 'text_rotation_none', component: new SemiSelectorComponent("font-family", (lh: Styles['font-family']) => { this.updateStyles("font-family", lh as Styles["font-family"]) }, "font-family: ?; font-size: 18px;", STYLES["font-family"].reduce((acc, key) => ({ ...acc, [key]: "Abc"}), {})), }, more: { icon: 'more_horiz', component: new MoreComponent((key: K, value: Styles[K]) => { this.updateStyles(key, value); }) } } renderComponents() { const numComponents = Object.keys(this.components).length; const angle = 360 / numComponents; const rotations = Object.keys(this.components).map((_, i) => angle * (i + 1)); return html` ${Object.keys(this.components).map((key, i) => { const icon = this.components[key as keyof typeof this.components].icon; const fontSize = 30; return html` this.showComponent(key as keyof typeof this.components)} style="transform: rotate(${rotations[i]}deg) translateY(${INNER_RADIUS - fontSize / 2}px); position: absolute;" part="button" class="radial-button" > { /* To fix linter bug */}} class="material-icons" style="transform: rotate(-${rotations[i]}deg);" >${icon} ` }) } ` } override render() { // Get the current wordSpacingStylescomponent const curComponent = this.currentComponent ? this.components[this.currentComponent as keyof typeof this.components].component.render() : ''; // Apply the drag listeners to the host element const host: HTMLElement = (this.renderRoot as (HTMLElement & { host: HTMLElement })).host; applyDrag(host, () => this.collapseAndExpand(host)); return html` ${curComponent} ${this.renderComponents()} fullscreen `; } } declare global { interface HTMLElementTagNameMap { 'ol-toolbar': OlToolbar; } }