mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-24 13:17:10 +08:00
feat(editor): gfx template package (#11480)
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/affine-components/icons';
|
||||
import { once } from '@blocksuite/affine-shared/utils';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
|
||||
import {
|
||||
arrow,
|
||||
autoUpdate,
|
||||
computePosition,
|
||||
offset,
|
||||
shift,
|
||||
} from '@floating-ui/dom';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import { TemplateCard1, TemplateCard2, TemplateCard3 } from './cards.js';
|
||||
import type { EdgelessTemplatePanel } from './template-panel.js';
|
||||
|
||||
export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
|
||||
LitElement
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
edgeless-template-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.template-cards {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
.template-card,
|
||||
.arrow-icon {
|
||||
--x: 0;
|
||||
--y: 0;
|
||||
--r: 0;
|
||||
--s: 1;
|
||||
position: absolute;
|
||||
transform: translate(var(--x), var(--y)) rotate(var(--r)) scale(var(--s));
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
--y: 17px;
|
||||
background: var(--affine-black-10);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.arrow-icon > svg {
|
||||
color: var(--affine-icon-color);
|
||||
fill: currentColor;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.template-card.card1 {
|
||||
transform-origin: 100% 50%;
|
||||
--x: 15px;
|
||||
--y: 8px;
|
||||
}
|
||||
.template-card.card2 {
|
||||
transform-origin: 0% 50%;
|
||||
--x: -17px;
|
||||
}
|
||||
.template-card.card3 {
|
||||
--y: 27px;
|
||||
}
|
||||
|
||||
/* hover */
|
||||
.template-cards:not(.expanded):hover .card1 {
|
||||
--r: 8.69deg;
|
||||
}
|
||||
.template-cards:not(.expanded):hover .card2 {
|
||||
--r: -10.93deg;
|
||||
}
|
||||
.template-cards:not(.expanded):hover .card3 {
|
||||
--y: 22px;
|
||||
--r: 5.19deg;
|
||||
}
|
||||
|
||||
/* expanded */
|
||||
.template-cards.expanded .card1 {
|
||||
--x: 17px;
|
||||
--y: -5px;
|
||||
--r: 8.69deg;
|
||||
--s: 0.64;
|
||||
}
|
||||
.template-cards.expanded .card2 {
|
||||
--x: -19px;
|
||||
--y: -6px;
|
||||
--r: -10.93deg;
|
||||
--s: 0.64;
|
||||
}
|
||||
.template-cards.expanded .card3 {
|
||||
--y: -10px;
|
||||
--s: 0.599;
|
||||
--r: 5.19deg;
|
||||
}
|
||||
`;
|
||||
|
||||
private _cleanup: (() => void) | null = null;
|
||||
|
||||
private _prevTool: GfxToolsFullOptionValue | null = null;
|
||||
|
||||
override enableActiveBackground = true;
|
||||
|
||||
override type: GfxToolsFullOptionValue['type'] = 'template';
|
||||
|
||||
get cards() {
|
||||
const { theme } = this;
|
||||
return [TemplateCard1[theme], TemplateCard2[theme], TemplateCard3[theme]];
|
||||
}
|
||||
|
||||
private _closePanel() {
|
||||
if (this._openedPanel) {
|
||||
this._openedPanel.remove();
|
||||
this._openedPanel = null;
|
||||
this._cleanup?.();
|
||||
this._cleanup = null;
|
||||
this.requestUpdate();
|
||||
|
||||
if (this._prevTool && this._prevTool.type !== 'template') {
|
||||
this.setEdgelessTool(this._prevTool);
|
||||
this._prevTool = null;
|
||||
} else {
|
||||
// @ts-expect-error FIXME: resolve after gfx tool refactor
|
||||
this.setEdgelessTool('default');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _togglePanel() {
|
||||
if (this._openedPanel) {
|
||||
this._closePanel();
|
||||
if (this._prevTool) {
|
||||
this.setEdgelessTool(this._prevTool);
|
||||
this._prevTool = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._prevTool = this.edgelessTool ? { ...this.edgelessTool } : null;
|
||||
|
||||
this.setEdgelessTool('template');
|
||||
|
||||
const panel = document.createElement('edgeless-templates-panel');
|
||||
panel.edgeless = this.edgeless;
|
||||
|
||||
this._cleanup = once(panel, 'closepanel', () => {
|
||||
this._closePanel();
|
||||
});
|
||||
this._openedPanel = panel;
|
||||
|
||||
this.renderRoot.append(panel);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const arrowEl = panel.renderRoot.querySelector('.arrow') as HTMLElement;
|
||||
|
||||
autoUpdate(this, panel, () => {
|
||||
computePosition(this, panel, {
|
||||
placement: 'top',
|
||||
middleware: [offset(20), arrow({ element: arrowEl }), shift()],
|
||||
})
|
||||
.then(({ x, y, middlewareData }) => {
|
||||
panel.style.left = `${x}px`;
|
||||
panel.style.top = `${y}px`;
|
||||
|
||||
arrowEl.style.left = `${
|
||||
(middlewareData.arrow?.x ?? 0) - (middlewareData.shift?.x ?? 0)
|
||||
}px`;
|
||||
})
|
||||
.catch(e => {
|
||||
console.warn("Can't compute position", e);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { cards, _openedPanel } = this;
|
||||
const expanded = _openedPanel !== null;
|
||||
|
||||
return html`<edgeless-toolbar-button @click=${this._togglePanel}>
|
||||
<div class="template-cards ${expanded ? 'expanded' : ''}">
|
||||
<div class="arrow-icon">${ArrowDownSmallIcon}</div>
|
||||
${repeat(
|
||||
cards,
|
||||
(card, n) => html`
|
||||
<div
|
||||
class=${classMap({
|
||||
'template-card': true,
|
||||
[`card${n + 1}`]: true,
|
||||
})}
|
||||
>
|
||||
${card}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</edgeless-toolbar-button>`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _openedPanel: EdgelessTemplatePanel | null = null;
|
||||
}
|
||||
Reference in New Issue
Block a user