Skip to content

Commit

Permalink
test(edgeless): frame title should be render on the top
Browse files Browse the repository at this point in the history
  • Loading branch information
L-Sun committed Nov 18, 2024
1 parent 2c62965 commit ccb962b
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 331 deletions.
10 changes: 5 additions & 5 deletions packages/blocks/src/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,8 @@ import {
} from './database-block/properties/title/text.js';
import { DividerBlockComponent } from './divider-block/index.js';
import { EdgelessTextBlockComponent } from './edgeless-text-block/index.js';
import {
EdgelessFrameTitle,
FrameBlockComponent,
} from './frame-block/index.js';
// TODO(@L-Sun)
import { FrameBlockComponent } from './frame-block/index.js';
import { ImageBlockFallbackCard } from './image-block/components/image-block-fallback.js';
import { ImageBlockPageComponent } from './image-block/components/page-image-block.js';
import { effects as blockImageEffects } from './image-block/effects.js';
Expand Down Expand Up @@ -155,6 +153,7 @@ import {
EdgelessSelectedRectWidget,
} from './root-block/edgeless/components/rects/edgeless-selected-rect.js';
import { EdgelessConnectorLabelEditor } from './root-block/edgeless/components/text/edgeless-connector-label-editor.js';
// TODO
import { EdgelessFrameTitleEditor } from './root-block/edgeless/components/text/edgeless-frame-title-editor.js';
import { EdgelessGroupTitleEditor } from './root-block/edgeless/components/text/edgeless-group-title-editor.js';
import { EdgelessShapeTextEditor } from './root-block/edgeless/components/text/edgeless-shape-text-editor.js';
Expand Down Expand Up @@ -273,6 +272,7 @@ import { EdgelessChangeShapeButton } from './root-block/widgets/element-toolbar/
import { EdgelessChangeTextMenu } from './root-block/widgets/element-toolbar/change-text-menu.js';
import { EdgelessMoreButton } from './root-block/widgets/element-toolbar/more-menu/button.js';
import { EdgelessReleaseFromGroupButton } from './root-block/widgets/element-toolbar/release-from-group-button.js';
import { effects as widgetFrameTitleEffects } from './root-block/widgets/frame-title/effects.js';
import { AffineImageToolbar } from './root-block/widgets/image-toolbar/components/image-toolbar.js';
import { AFFINE_IMAGE_TOOLBAR_WIDGET } from './root-block/widgets/image-toolbar/index.js';
import { AFFINE_INNER_MODAL_WIDGET } from './root-block/widgets/inner-modal/inner-modal.js';
Expand Down Expand Up @@ -339,6 +339,7 @@ export function effects() {
widgetScrollAnchoringEffects();
widgetMobileToolbarEffects();
widgetLinkedDocEffects();
widgetFrameTitleEffects();

customElements.define('affine-database-title', DatabaseTitle);
customElements.define(
Expand Down Expand Up @@ -380,7 +381,6 @@ export function effects() {
customElements.define('affine-code', CodeBlockComponent);
customElements.define('affine-image-fallback-card', ImageBlockFallbackCard);
customElements.define('mini-mindmap-preview', MiniMindmapPreview);
customElements.define('edgeless-frame-title', EdgelessFrameTitle);
customElements.define('affine-frame', FrameBlockComponent);
customElements.define('mini-mindmap-surface-block', MindmapSurfaceBlock);
customElements.define('affine-data-view', DataViewBlockComponent);
Expand Down
329 changes: 8 additions & 321 deletions packages/blocks/src/frame-block/frame-block.ts
Original file line number Diff line number Diff line change
@@ -1,328 +1,14 @@
import type { BlockStdScope } from '@blocksuite/block-std';
import type { Doc } from '@blocksuite/store';
import type { FrameBlockModel } from '@blocksuite/affine-model';

import { ColorScheme, FrameBlockModel } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import {
docContext,
GfxBlockComponent,
modelContext,
ShadowlessElement,
stdContext,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
import { Bound, type SerializedXYWH } from '@blocksuite/global/utils';
import { consume } from '@lit/context';
import { cssVarV2, themeToVar } from '@toeverything/theme/v2';
import { css, html, nothing } from 'lit';
import { query, state } from 'lit/decorators.js';
import { GfxBlockComponent } from '@blocksuite/block-std';
import { cssVarV2 } from '@toeverything/theme/v2';
import { html } from 'lit';
import { state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';

import type { EdgelessRootService } from '../root-block/index.js';

import { parseStringToRgba } from '../root-block/edgeless/components/color-picker/utils.js';
import { isTransparent } from '../root-block/edgeless/components/panel/color-panel.js';

export const frameTitleStyleVars = {
nestedFrameOffset: 4,
height: 22,
fontSize: 14,
};

export class EdgelessFrameTitle extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
edgeless-frame-title {
position: relative;
}
.affine-frame-title {
position: absolute;
display: flex;
align-items: center;
z-index: 1;
left: 0px;
top: 0px;
border: 1px solid ${unsafeCSSVarV2('edgeless/frame/border/default')};
border-radius: 4px;
width: fit-content;
height: ${frameTitleStyleVars.height}px;
padding: 0px 4px;
transform-origin: left bottom;
background-color: var(--bg-color);
span {
font-family: var(--affine-font-family);
font-size: ${frameTitleStyleVars.fontSize}px;
cursor: default;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.affine-frame-title:hover {
background-color: color-mix(in srgb, var(--bg-color), #000000 7%);
}
`;

private _cachedHeight = 0;

private _cachedWidth = 0;

get colors() {
let backgroundColor = this.std
.get(ThemeProvider)
.getColorValue(this.model.background, undefined, true);
if (isTransparent(backgroundColor)) {
backgroundColor = this.std
.get(ThemeProvider)
.getCssVariableColor(themeToVar('edgeless/frame/background/white'));
}

const { r, g, b, a } = parseStringToRgba(backgroundColor);

const theme = this.std.get(ThemeProvider).theme;
let textColor: string;
{
let rPrime, gPrime, bPrime;
if (theme === ColorScheme.Light) {
rPrime = 1 - a + a * r;
gPrime = 1 - a + a * g;
bPrime = 1 - a + a * b;
} else {
rPrime = a * r;
gPrime = a * g;
bPrime = a * b;
}

// light
const L = 0.299 * rPrime + 0.587 * gPrime + 0.114 * bPrime;
textColor = L > 0.5 ? 'black' : 'white';
}

return {
background: backgroundColor,
text: textColor,
};
}

get gfx() {
return this.std.get(GfxControllerIdentifier);
}

get rootService() {
return this.std.getService('affine:page') as EdgelessRootService;
}

private _isInsideFrame() {
return this.gfx.grid.has(
this.model.elementBound,
true,
true,
model => model !== this.model && model instanceof FrameBlockModel
);
}

private _updateFrameTitleSize() {
const { _nestedFrame, _zoom: zoom } = this;
const { elementBound } = this.model;
const width = this._cachedWidth / zoom;
const height = this._cachedHeight / zoom;

const { nestedFrameOffset } = frameTitleStyleVars;

if (width && height) {
this.model.externalXYWH = `[${
elementBound.x + (_nestedFrame ? nestedFrameOffset / zoom : 0)
},${
elementBound.y +
(_nestedFrame
? nestedFrameOffset / zoom
: -(height + nestedFrameOffset / zoom))
},${width},${height}]`;

this.gfx.grid.update(this.model);
}
}

override connectedCallback() {
super.connectedCallback();

const { _disposables, doc, gfx, rootService } = this;

this._nestedFrame = this._isInsideFrame();

_disposables.add(
doc.slots.blockUpdated.on(payload => {
if (
(payload.type === 'update' &&
payload.props.key === 'xywh' &&
doc.getBlock(payload.id)?.model instanceof FrameBlockModel) ||
(payload.type === 'add' && payload.flavour === 'affine:frame')
) {
this._nestedFrame = this._isInsideFrame();
}

if (
payload.type === 'delete' &&
payload.model instanceof FrameBlockModel &&
payload.model !== this.model
) {
this._nestedFrame = this._isInsideFrame();
}
})
);

_disposables.add(
this.model.propsUpdated.on(() => {
this._xywh = this.model.xywh;
this.requestUpdate();
})
);

_disposables.add(
rootService.selection.slots.updated.on(() => {
this._editing =
rootService.selection.selectedIds[0] === this.model.id &&
rootService.selection.editing;
})
);

_disposables.add(
gfx.viewport.viewportUpdated.on(({ zoom }) => {
this._zoom = zoom;
})
);

this._zoom = gfx.viewport.zoom;

const updateTitle = () => {
this._frameTitle = this.model.title.toString().trim();
};
_disposables.add(() => {
this.model.title.yText.unobserve(updateTitle);
});
this.model.title.yText.observe(updateTitle);

this._frameTitle = this.model.title.toString().trim();
this._xywh = this.model.xywh;
}

override firstUpdated() {
if (!this._frameTitleEl) return;
this._cachedWidth = this._frameTitleEl.clientWidth;
this._cachedHeight = this._frameTitleEl.clientHeight;
this._updateFrameTitleSize();
}

override render() {
const model = this.model;
const bound = Bound.deserialize(model.xywh);

const { _editing, _zoom: zoom } = this;
const { nestedFrameOffset, height } = frameTitleStyleVars;
const _isNavigator =
this.gfx.tool.currentToolName$.value === 'frameNavigator';

const nestedFrame = this._nestedFrame;
const maxWidth = nestedFrame
? bound.w * zoom - nestedFrameOffset / zoom
: bound.w * zoom;
const hidden = height / zoom >= bound.h;
const transformOperation = [
`translate(0%, ${nestedFrame ? 0 : -100}%)`,
`scale(${1 / zoom})`,
`translate(${nestedFrame ? nestedFrameOffset : 0}px, ${
nestedFrame ? nestedFrameOffset : -nestedFrameOffset
}px)`,
];

return html`
${this._frameTitle &&
this._frameTitle.length !== 0 &&
!_isNavigator &&
!_editing
? html`
<div
style=${styleMap({
'--bg-color': this.colors.background,
display: hidden ? 'none' : 'flex',
transform: transformOperation.join(' '),
maxWidth: maxWidth + 'px',
transformOrigin: nestedFrame ? 'top left' : 'bottom left',
color: this.colors.text,
})}
class="affine-frame-title"
>
<span>${this._frameTitle}</span>
</div>
`
: nothing}
`;
}

override updated(_changedProperties: Map<string, unknown>) {
if (
(!this.gfx.viewport.viewportBounds.contains(this.model.elementBound) &&
!this.gfx.viewport.viewportBounds.isIntersectWithBound(
this.model.elementBound
)) ||
!this._frameTitleEl
) {
return;
}

let sizeChanged = false;
if (
this._cachedWidth === 0 ||
this._cachedHeight === 0 ||
_changedProperties.has('_frameTitle') ||
_changedProperties.has('_nestedFrame') ||
_changedProperties.has('_xywh')
) {
this._cachedWidth = this._frameTitleEl.clientWidth;
this._cachedHeight = this._frameTitleEl.clientHeight;
sizeChanged = true;
}
if (sizeChanged || _changedProperties.has('_zoom')) {
this._updateFrameTitleSize();
}
}

@state()
private accessor _editing = false;

@state()
private accessor _frameTitle = '';

@query('.affine-frame-title')
private accessor _frameTitleEl!: HTMLDivElement;

@state()
private accessor _nestedFrame = false;

@state()
private accessor _xywh: SerializedXYWH | null = null;

@state()
private accessor _zoom!: number;

@consume({ context: docContext })
accessor doc!: Doc;

@consume({ context: modelContext })
accessor model!: FrameBlockModel;

@consume({
context: stdContext,
})
accessor std!: BlockStdScope;
}

export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
get rootService() {
return this.std.getService('affine:page') as EdgelessRootService;
Expand Down Expand Up @@ -375,8 +61,9 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
@state()
accessor showBorder = true;

@query('edgeless-frame-title')
accessor titleElement: EdgelessFrameTitle | null = null;
// TODO(@L-Sun) used by frame title editor
// @query('edgeless-frame-title')
// accessor titleElement: EdgelessFrameTitle | null = null;
}

declare global {
Expand Down
Loading

0 comments on commit ccb962b

Please sign in to comment.