Skip to content

Commit

Permalink
feat: support edgeless tidy up
Browse files Browse the repository at this point in the history
  • Loading branch information
akumatus committed Oct 15, 2024
1 parent af66304 commit 42bf9d8
Show file tree
Hide file tree
Showing 9 changed files with 639 additions and 45 deletions.
2 changes: 1 addition & 1 deletion packages/affine/block-embed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.1.67",
"@blocksuite/icons": "^2.1.68",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
Expand Down
5 changes: 3 additions & 2 deletions packages/affine/block-surface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ export {
} from './surface-spec.js';
export { SurfaceBlockTransformer } from './surface-transformer.js';
export { AStarRunner } from './utils/a-star.js';
export { LayoutableMindmapElementModel } from './utils/mindmap/utils.js';
export { RoughCanvas } from './utils/rough/canvas.js';
export type { Options } from './utils/rough/core.js';

export { sortIndex } from './utils/sort.js';
export type { Options } from './utils/rough/core.js';

import {
almostEqual,
Expand Down Expand Up @@ -92,6 +92,7 @@ import {
moveMindMapSubtree,
showMergeIndicator,
} from './utils/mindmap/utils.js';
export { sortIndex } from './utils/sort.js';

export const ConnectorUtils = {
isConnectorAndBindingsAllSelected,
Expand Down
2 changes: 1 addition & 1 deletion packages/affine/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.1.67",
"@blocksuite/icons": "^2.1.68",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
Expand Down
2 changes: 1 addition & 1 deletion packages/affine/data-view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.1.67",
"@blocksuite/icons": "^2.1.68",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
"@lit/context": "^1.1.2",
Expand Down
6 changes: 4 additions & 2 deletions packages/blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@blocksuite/block-std": "workspace:*",
"@blocksuite/data-view": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.1.67",
"@blocksuite/icons": "^2.1.68",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.10",
Expand All @@ -50,6 +50,7 @@
"html2canvas": "^1.4.1",
"katex": "^0.16.11",
"lit": "^3.2.0",
"lodash.chunk": "^4.2.0",
"mdast-util-gfm-autolink-literal": "^2.0.1",
"mdast-util-gfm-strikethrough": "^2.0.0",
"mdast-util-gfm-table": "^2.0.0",
Expand Down Expand Up @@ -107,6 +108,7 @@
],
"devDependencies": {
"@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7"
"@types/katex": "^0.16.7",
"@types/lodash.chunk": "^4.2.9"
}
}
208 changes: 176 additions & 32 deletions packages/blocks/src/root-block/widgets/element-toolbar/align-button.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LayoutableMindmapElementModel } from '@blocksuite/affine-block-surface';
import {
AlignBottomIcon,
AlignDistributeHorizontallyIcon,
Expand All @@ -11,17 +12,26 @@ import {
} from '@blocksuite/affine-components/icons';
import {
ConnectorElementModel,
GroupElementModel,
EdgelessTextBlockModel,
MindmapElementModel,
} from '@blocksuite/affine-model';
import {
type GfxContainerElement,
type GfxModel,
isGfxContainerElm,
} from '@blocksuite/block-std/gfx';
import { Bound, WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement, nothing } from 'lit';
import { AutoTidyUpIcon, ResizeTidyUpIcon } from '@blocksuite/icons/lit';
import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import chunk from 'lodash.chunk';

import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';

const enum Alignment {
AutoArrange = 'Auto arrange',
AutoResize = 'Resize & Align',
Bottom = 'Align bottom',
DistributeHorizontally = 'Distribute horizontally',
DistributeVertically = 'Distribute vertically',
Expand All @@ -32,7 +42,15 @@ const enum Alignment {
Vertically = 'Align vertically',
}

const ALIGNMENT_LIST = [
const ALIGN_HEIGHT = 200;
const ALIGN_PADDING = 20;

interface AlignmentIcon {
name: Alignment;
content: TemplateResult<1>;
}

const HORIZONTAL_ALIGNMENT: AlignmentIcon[] = [
{
name: Alignment.Left,
content: AlignLeftIcon,
Expand All @@ -49,10 +67,9 @@ const ALIGNMENT_LIST = [
name: Alignment.DistributeHorizontally,
content: AlignDistributeHorizontallyIcon,
},
{
name: 'separator',
content: html`<editor-toolbar-separator></editor-toolbar-separator>`,
},
];

const VERTICAL_ALIGNMENT: AlignmentIcon[] = [
{
name: Alignment.Top,
content: AlignTopIcon,
Expand All @@ -69,9 +86,33 @@ const ALIGNMENT_LIST = [
name: Alignment.DistributeVertically,
content: AlignDistributeVerticallyIcon,
},
] as const;
];

const AUTO_ALIGNMENT: AlignmentIcon[] = [
{
name: Alignment.AutoArrange,
content: AutoTidyUpIcon({ width: '20px', height: '20px' }),
},
{
name: Alignment.AutoResize,
content: ResizeTidyUpIcon({ width: '20px', height: '20px' }),
},
];

export class EdgelessAlignButton extends WithDisposable(LitElement) {
static override styles = css`
.align-menu-content {
max-width: 120px;
flex-wrap: wrap;
padding: 8px 2px;
}
.align-menu-separator {
width: 120px;
height: 1px;
background-color: var(--affine-background-tertiary-color);
}
`;

private get elements() {
return this.edgeless.service.selection.selectedElements;
}
Expand Down Expand Up @@ -102,9 +143,60 @@ export class EdgelessAlignButton extends WithDisposable(LitElement) {
case Alignment.DistributeVertically:
this._alignDistributeVertically();
break;
case Alignment.AutoArrange:
this._alignAutoArrange();
break;
case Alignment.AutoResize:
this._alignAutoResize();
break;
}
}

private _alignAutoArrange() {
const chunks = this._splitElementsToChunks(this.elements);
// update element XY
const startX: number = chunks[0][0].elementBound.x;
let startY: number = chunks[0][0].elementBound.y;
chunks.forEach(items => {
let posX = startX;
let maxHeight = 0;
items.forEach(ele => {
const { x: eleX, y: eleY } = ele.elementBound;
const bound = Bound.deserialize(ele.xywh);
const xOffset = bound.x - eleX;
const yOffset = bound.y - eleY;
bound.x = posX + xOffset;
bound.y = startY + yOffset;
this._updateXYWH(ele, bound);
if (ele.elementBound.h > maxHeight) {
maxHeight = ele.elementBound.h;
}
posX += ele.elementBound.w + ALIGN_PADDING;
});
startY += maxHeight + ALIGN_PADDING;
});
}

private _alignAutoResize() {
// resize to fixed height
this.elements.forEach(ele => {
if (
ele instanceof ConnectorElementModel ||
ele instanceof EdgelessTextBlockModel ||
ele instanceof LayoutableMindmapElementModel
) {
return;
}
const bound = Bound.deserialize(ele.xywh);
const scale = ALIGN_HEIGHT / ele.elementBound.h;
bound.h = scale * bound.h;
bound.w = scale * bound.w;
this._updateXYWH(ele, bound);
});
// arrange
this._alignAutoArrange();
}

private _alignBottom() {
const { elements } = this;
const bounds = elements.map(a => a.elementBound);
Expand Down Expand Up @@ -232,24 +324,88 @@ export class EdgelessAlignButton extends WithDisposable(LitElement) {
});
}

private _splitElementsToChunks(models: GfxModel[]) {
const sortByCenterX = (a: GfxModel, b: GfxModel) =>
a.elementBound.center[0] - b.elementBound.center[0];
const sortByCenterY = (a: GfxModel, b: GfxModel) =>
a.elementBound.center[1] - b.elementBound.center[1];
const elements = models.filter(ele => {
if (
ele instanceof ConnectorElementModel &&
(ele.target.id || ele.source.id)
) {
return false;
}
return true;
});
elements.sort(sortByCenterY);
const chunks = chunk(elements, 4);
chunks.forEach(items => items.sort(sortByCenterX));
return chunks;
}

private _updatChildElementsXYWH(
container: GfxContainerElement,
targetBound: Bound
) {
const containerBound = Bound.deserialize(container.xywh);
const scaleX = targetBound.w / containerBound.w;
const scaleY = targetBound.h / containerBound.h;
container.childElements.forEach(child => {
const childBound = Bound.deserialize(child.xywh);
childBound.x = targetBound.x + scaleX * (childBound.x - containerBound.x);
childBound.y = targetBound.y + scaleY * (childBound.y - containerBound.y);
childBound.w = scaleX * childBound.w;
childBound.h = scaleY * childBound.h;
this._updateXYWH(child, childBound);
});
}

private _updateXYWH(ele: BlockSuite.EdgelessModel, bound: Bound) {
if (ele instanceof ConnectorElementModel) {
ele.moveTo(bound);
} else if (ele instanceof GroupElementModel) {
const groupBound = Bound.deserialize(ele.xywh);
ele.childElements.forEach(child => {
const newBound = Bound.deserialize(child.xywh);
newBound.x += bound.x - groupBound.x;
newBound.y += bound.y - groupBound.y;
this._updateXYWH(child, newBound);
} else if (isGfxContainerElm(ele)) {
this._updatChildElementsXYWH(ele, bound);
this.edgeless.service.updateElement(ele.id, {
xywh: bound.serialize(),
});
} else if (ele instanceof LayoutableMindmapElementModel) {
const rootId = ele.tree.id;
const rootEle = ele.childElements.find(child => child.id === rootId);
if (rootEle) {
const rootBound = Bound.deserialize(rootEle.xywh);
rootBound.x += bound.x - ele.x;
rootBound.y += bound.y - ele.y;
this._updateXYWH(rootEle, rootBound);
}
ele.layout();
} else {
this.edgeless.service.updateElement(ele.id, {
xywh: bound.serialize(),
});
}
}

private renderIcons(icons: AlignmentIcon[]) {
return html`
${repeat(
icons,
(item, index) => item.name + index,
({ name, content }) => {
return html`
<editor-icon-button
aria-label=${name}
.tooltip=${name}
@click=${() => this._align(name)}
>
${content}
</editor-icon-button>
`;
}
)}
`;
}

override firstUpdated() {
this._disposables.add(
this.edgeless.service.selection.slots.updated.on(() =>
Expand All @@ -270,23 +426,11 @@ export class EdgelessAlignButton extends WithDisposable(LitElement) {
</editor-icon-button>
`}
>
<div>
${repeat(
ALIGNMENT_LIST,
(item, index) => item.name + index,
({ name, content }) => {
if (name === 'separator') return content;
return html`
<editor-icon-button
aria-label=${name}
.tooltip=${name}
@click=${() => this._align(name)}
>
${content}
</editor-icon-button>
`;
}
)}
<div class="align-menu-content">
${this.renderIcons(HORIZONTAL_ALIGNMENT)}
${this.renderIcons(VERTICAL_ALIGNMENT)}
<div class="align-menu-separator"></div>
${this.renderIcons(AUTO_ALIGNMENT)}
</div>
</editor-menu-button>
`;
Expand Down
Loading

0 comments on commit 42bf9d8

Please sign in to comment.