mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 01:29:31 +08:00
feat(editor): edgeless zoom toolbar widget extension (#12037)
Closes: BS-3363
This commit is contained in:
@@ -9,7 +9,6 @@ import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { CommonSpecs } from '../common-specs/index.js';
|
||||
import { AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET } from '../widgets/edgeless-zoom-toolbar/index.js';
|
||||
import { EdgelessClipboardController } from './clipboard/clipboard.js';
|
||||
import { NOTE_SLICER_WIDGET } from './components/note-slicer/index.js';
|
||||
import { EDGELESS_DRAGGING_AREA_WIDGET } from './components/rects/edgeless-dragging-area-rect.js';
|
||||
@@ -17,11 +16,6 @@ import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selec
|
||||
import { quickTools } from './components/toolbar/tools.js';
|
||||
import { EdgelessRootService } from './edgeless-root-service.js';
|
||||
|
||||
export const edgelessZoomToolbarWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET)}`
|
||||
);
|
||||
export const edgelessDraggingAreaWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
EDGELESS_DRAGGING_AREA_WIDGET,
|
||||
@@ -57,7 +51,6 @@ const EdgelessCommonExtension: ExtensionType[] = [
|
||||
export const EdgelessRootBlockSpec: ExtensionType[] = [
|
||||
...EdgelessCommonExtension,
|
||||
BlockViewExtension('affine:page', literal`affine-edgeless-root`),
|
||||
edgelessZoomToolbarWidget,
|
||||
edgelessDraggingAreaWidget,
|
||||
noteSlicerWidget,
|
||||
edgelessSelectedRectWidget,
|
||||
|
||||
@@ -21,12 +21,6 @@ import {
|
||||
PageRootBlockComponent,
|
||||
PreviewRootBlockComponent,
|
||||
} from './index.js';
|
||||
import {
|
||||
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET,
|
||||
AffineEdgelessZoomToolbarWidget,
|
||||
} from './widgets/edgeless-zoom-toolbar/index.js';
|
||||
import { ZoomBarToggleButton } from './widgets/edgeless-zoom-toolbar/zoom-bar-toggle-button.js';
|
||||
import { EdgelessZoomToolbar } from './widgets/edgeless-zoom-toolbar/zoom-toolbar.js';
|
||||
import {
|
||||
AFFINE_PAGE_DRAGGING_AREA_WIDGET,
|
||||
AffinePageDraggingAreaWidget,
|
||||
@@ -55,10 +49,6 @@ function registerWidgets() {
|
||||
AFFINE_PAGE_DRAGGING_AREA_WIDGET,
|
||||
AffinePageDraggingAreaWidget
|
||||
);
|
||||
customElements.define(
|
||||
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET,
|
||||
AffineEdgelessZoomToolbarWidget
|
||||
);
|
||||
}
|
||||
|
||||
function registerEdgelessToolbarComponents() {
|
||||
@@ -73,10 +63,6 @@ function registerEdgelessToolbarComponents() {
|
||||
}
|
||||
|
||||
function registerMiscComponents() {
|
||||
// Toolbar and UI components
|
||||
customElements.define('edgeless-zoom-toolbar', EdgelessZoomToolbar);
|
||||
customElements.define('zoom-bar-toggle-button', ZoomBarToggleButton);
|
||||
|
||||
// Auto-complete components
|
||||
customElements.define(
|
||||
'edgeless-auto-complete-panel',
|
||||
@@ -111,9 +97,5 @@ declare global {
|
||||
'toolbar-arrow-up-icon': ToolbarArrowUpIcon;
|
||||
'edgeless-link-tool-button': EdgelessLinkToolButton;
|
||||
'affine-page-root': PageRootBlockComponent;
|
||||
'zoom-bar-toggle-button': ZoomBarToggleButton;
|
||||
'edgeless-zoom-toolbar': EdgelessZoomToolbar;
|
||||
|
||||
[AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET]: AffineEdgelessZoomToolbarWidget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import { WidgetComponent } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
|
||||
export const AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET =
|
||||
'affine-edgeless-zoom-toolbar-widget';
|
||||
|
||||
export class AffineEdgelessZoomToolbarWidget extends WidgetComponent<RootBlockModel> {
|
||||
static override styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 12px;
|
||||
z-index: var(--affine-z-index-popover);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@container viewport (width <= 1200px) {
|
||||
edgeless-zoom-toolbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container viewport (width > 1200px) {
|
||||
zoom-bar-toggle-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
get edgeless() {
|
||||
return this.block;
|
||||
}
|
||||
|
||||
get gfx() {
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
const currentTool = this.gfx.tool.currentToolName$.value;
|
||||
|
||||
if (currentTool !== 'frameNavigator') {
|
||||
this._hide = false;
|
||||
}
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
const { disposables, std } = this;
|
||||
const slots = std.get(EdgelessLegacySlotIdentifier);
|
||||
|
||||
disposables.add(
|
||||
slots.navigatorSettingUpdated.subscribe(({ hideToolbar }) => {
|
||||
if (hideToolbar !== undefined) {
|
||||
this._hide = hideToolbar;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this._hide || !this.edgeless) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<edgeless-zoom-toolbar .std=${this.std}></edgeless-zoom-toolbar>
|
||||
<zoom-bar-toggle-button .std=${this.std}></zoom-bar-toggle-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _hide = false;
|
||||
}
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||
import { stopPropagation } from '@blocksuite/affine-shared/utils';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { MoreHorizontalIcon } from '@blocksuite/icons/lit';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import { offset } from '@floating-ui/dom';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
|
||||
export class ZoomBarToggleButton extends WithDisposable(LitElement) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
.toggle-button {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
edgeless-zoom-toolbar {
|
||||
position: absolute;
|
||||
bottom: initial;
|
||||
}
|
||||
`;
|
||||
|
||||
private _abortController: AbortController | null = null;
|
||||
|
||||
private _closeZoomMenu() {
|
||||
if (this._abortController && !this._abortController.signal.aborted) {
|
||||
this._abortController.abort();
|
||||
this._abortController = null;
|
||||
this._showPopper = false;
|
||||
}
|
||||
}
|
||||
|
||||
private get _slots() {
|
||||
return this.std.get(EdgelessLegacySlotIdentifier);
|
||||
}
|
||||
|
||||
private _toggleZoomMenu() {
|
||||
if (this._abortController && !this._abortController.signal.aborted) {
|
||||
this._closeZoomMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
this._abortController = new AbortController();
|
||||
this._abortController.signal.addEventListener('abort', () => {
|
||||
this._showPopper = false;
|
||||
});
|
||||
createLitPortal({
|
||||
template: html`<edgeless-zoom-toolbar
|
||||
.std=${this.std}
|
||||
.layout=${'vertical'}
|
||||
></edgeless-zoom-toolbar>`,
|
||||
container: this._toggleButton,
|
||||
computePosition: {
|
||||
referenceElement: this._toggleButton,
|
||||
placement: 'top',
|
||||
middleware: [offset(4)],
|
||||
autoUpdate: true,
|
||||
},
|
||||
abortController: this._abortController,
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
this._showPopper = true;
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._closeZoomMenu();
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
const { disposables } = this;
|
||||
disposables.add(
|
||||
this._slots.readonlyUpdated.subscribe(() => {
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.std.store.readonly) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="toggle-button" @pointerdown=${stopPropagation}>
|
||||
<edgeless-tool-icon-button
|
||||
.tooltip=${'Toggle Zoom Tool Bar'}
|
||||
.tipPosition=${'right'}
|
||||
.active=${this._showPopper}
|
||||
.arrow=${false}
|
||||
.activeMode=${'background'}
|
||||
.iconContainerPadding=${6}
|
||||
.iconSize=${'24px'}
|
||||
@click=${() => this._toggleZoomMenu()}
|
||||
>
|
||||
${MoreHorizontalIcon()}
|
||||
</edgeless-tool-icon-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _showPopper = false;
|
||||
|
||||
@query('.toggle-button')
|
||||
private accessor _toggleButton!: HTMLElement;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor std!: BlockStdScope;
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import { stopPropagation } from '@blocksuite/affine-shared/utils';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { MinusIcon, PlusIcon, ViewBarIcon } from '@blocksuite/icons/lit';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import {
|
||||
GfxControllerIdentifier,
|
||||
ZOOM_MAX,
|
||||
ZOOM_MIN,
|
||||
ZOOM_STEP,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import clamp from 'lodash-es/clamp';
|
||||
|
||||
export class EdgelessZoomToolbar extends WithDisposable(LitElement) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edgeless-zoom-toolbar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
fill: currentcolor;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.edgeless-zoom-toolbar-container.horizantal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.edgeless-zoom-toolbar-container.vertical {
|
||||
flex-direction: column;
|
||||
width: 40px;
|
||||
background-color: var(--affine-background-overlay-panel-color);
|
||||
box-shadow: var(--affine-shadow-2);
|
||||
border: 1px solid var(--affine-border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.edgeless-zoom-toolbar-container[level='second'] {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.edgeless-zoom-toolbar-container[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.zoom-percent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
color: var(--affine-icon-color);
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
}
|
||||
|
||||
.zoom-percent:hover {
|
||||
color: var(--affine-primary-color);
|
||||
background-color: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.zoom-percent[disabled] {
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
color: var(--affine-text-disable-color);
|
||||
}
|
||||
`;
|
||||
|
||||
get slots() {
|
||||
return this.std.get(EdgelessLegacySlotIdentifier);
|
||||
}
|
||||
|
||||
get gfx() {
|
||||
return this.std.get(GfxControllerIdentifier);
|
||||
}
|
||||
|
||||
get edgelessTool() {
|
||||
return this.gfx.tool.currentToolOption$.peek();
|
||||
}
|
||||
|
||||
get locked() {
|
||||
return this.viewport.locked;
|
||||
}
|
||||
|
||||
get viewport() {
|
||||
return this.gfx.viewport;
|
||||
}
|
||||
|
||||
setZoomByStep = (step: number) => {
|
||||
this.viewport.smoothZoom(clamp(this.zoom + step, ZOOM_MIN, ZOOM_MAX));
|
||||
};
|
||||
|
||||
get zoom() {
|
||||
if (!this.viewport) {
|
||||
console.error('Something went wrong, viewport is not available');
|
||||
return 1;
|
||||
}
|
||||
return this.viewport.zoom;
|
||||
}
|
||||
|
||||
private _isVerticalBar() {
|
||||
return this.layout === 'vertical';
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
this.gfx.tool.currentToolName$.value;
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
const { disposables } = this;
|
||||
disposables.add(
|
||||
this.viewport.viewportUpdated.subscribe(() => this.requestUpdate())
|
||||
);
|
||||
disposables.add(
|
||||
this.slots.readonlyUpdated.subscribe(() => {
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.std.store.readonly) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const formattedZoom = `${Math.round(this.zoom * 100)}%`;
|
||||
const classes = `edgeless-zoom-toolbar-container ${this.layout}`;
|
||||
const locked = this.locked;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classes}
|
||||
@dblclick=${stopPropagation}
|
||||
@mousedown=${stopPropagation}
|
||||
@mouseup=${stopPropagation}
|
||||
@pointerdown=${stopPropagation}
|
||||
>
|
||||
<edgeless-tool-icon-button
|
||||
.tooltip=${'Fit to screen'}
|
||||
.tipPosition=${this._isVerticalBar() ? 'right' : 'top-end'}
|
||||
.arrow=${!this._isVerticalBar()}
|
||||
@click=${() => this.gfx.fitToScreen()}
|
||||
.iconContainerPadding=${4}
|
||||
.iconSize=${'24px'}
|
||||
.disabled=${locked}
|
||||
>
|
||||
${ViewBarIcon()}
|
||||
</edgeless-tool-icon-button>
|
||||
<edgeless-tool-icon-button
|
||||
.tooltip=${'Zoom out'}
|
||||
.tipPosition=${this._isVerticalBar() ? 'right' : 'top'}
|
||||
.arrow=${!this._isVerticalBar()}
|
||||
@click=${() => this.setZoomByStep(-ZOOM_STEP)}
|
||||
.iconContainerPadding=${4}
|
||||
.iconSize=${'24px'}
|
||||
.disabled=${locked}
|
||||
>
|
||||
${MinusIcon()}
|
||||
</edgeless-tool-icon-button>
|
||||
<button
|
||||
class="zoom-percent"
|
||||
@click=${() => this.viewport.smoothZoom(1)}
|
||||
.disabled=${locked}
|
||||
>
|
||||
${formattedZoom}
|
||||
</button>
|
||||
<edgeless-tool-icon-button
|
||||
.tooltip=${'Zoom in'}
|
||||
.tipPosition=${this._isVerticalBar() ? 'right' : 'top'}
|
||||
.arrow=${!this._isVerticalBar()}
|
||||
@click=${() => this.setZoomByStep(ZOOM_STEP)}
|
||||
.iconContainerPadding=${4}
|
||||
.iconSize=${'24px'}
|
||||
.disabled=${locked}
|
||||
>
|
||||
${PlusIcon()}
|
||||
</edgeless-tool-icon-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor layout: 'horizontal' | 'vertical' = 'horizontal';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor std!: BlockStdScope;
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export { AffineEdgelessZoomToolbarWidget } from './edgeless-zoom-toolbar/index.js';
|
||||
export { AffinePageDraggingAreaWidget } from './page-dragging-area/page-dragging-area.js';
|
||||
|
||||
Reference in New Issue
Block a user