@formicoidea/labre-core 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
- package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
- package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
- package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-attachment/index.js +1 -0
- package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
- package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
- package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
- package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
- package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
- package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
- package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
- package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
- package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
- package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
- package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-frame/index.js +1 -0
- package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
- package/dist/_pkgs/affine-block-frame/translations.js +16 -1
- package/dist/_pkgs/affine-block-frame/view.js +6 -0
- package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
- package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
- package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
- package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
- package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
- package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
- package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
- package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
- package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
- package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
- package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
- package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
- package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
- package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
- package/dist/_pkgs/affine-block-image/effects.js +2 -0
- package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/image-block.js +2 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
- package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
- package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-image/index.js +1 -0
- package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
- package/dist/_pkgs/affine-block-image/translations.js +43 -0
- package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
- package/dist/_pkgs/affine-block-image/utils.js +10 -1
- package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
- package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
- package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
- package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-root/index.js +1 -0
- package/dist/_pkgs/affine-block-root/view.js +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
- package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
- package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
- package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
- package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
- package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
- package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
- package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
- package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
- package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
- package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
- package/dist/_pkgs/affine-block-surface/view.js +22 -2
- package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
- package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
- package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
- package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
- package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
- package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
- package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
- package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
- package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
- package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
- package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
- package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
- package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
- package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
- package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
- package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
- package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
- package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
- package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
- package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
- package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
- package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
- package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
- package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
- package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
- package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
- package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
- package/dist/_pkgs/affine-model/consts/shape.js +5 -0
- package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
- package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
- package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
- package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
- package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
- package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
- package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
- package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
- package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
- package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
- package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
- package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
- package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
- package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
- package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
- package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
- package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
- package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
- package/dist/_pkgs/affine-model/headless.d.ts +19 -0
- package/dist/_pkgs/affine-model/headless.js +19 -0
- package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
- package/dist/_pkgs/affine-shared/headless.js +10 -0
- package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
- package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
- package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
- package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
- package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
- package/dist/_pkgs/affine-shared/services/index.js +2 -0
- package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
- package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
- package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
- package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
- package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
- package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
- package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
- package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
- package/dist/commands.js +5 -2
- package/dist/host-panels.d.ts +21 -1
- package/dist/host-panels.js +50 -10
- package/dist/interchange.d.ts +1 -0
- package/dist/interchange.js +1 -0
- package/dist/model/headless.d.ts +1 -0
- package/dist/model/headless.js +1 -0
- package/dist/shared/headless.d.ts +1 -0
- package/dist/shared/headless.js +1 -0
- package/dist/translations.js +8 -1
- package/package.json +13 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TOOLBAR_INSERT_INTO_PAGE, TOOLBAR_UNGROUP, } from '../affine-shared/services/index.js';
|
|
1
|
+
import { SAVE_AS_TEMPLATE, TOOLBAR_INSERT_INTO_PAGE, TOOLBAR_UNGROUP, } from '../affine-shared/services/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* The frame's own seed: the default title a newly created frame is stamped
|
|
4
4
|
* with (`Frame 3`), written into the document at creation
|
|
@@ -48,6 +48,19 @@ export const FRAME_TOAST_INSERTED_MESSAGE = [
|
|
|
48
48
|
'Frame has been inserted into page',
|
|
49
49
|
];
|
|
50
50
|
export const FRAME_TOOLBAR_UNGROUP = TOOLBAR_UNGROUP;
|
|
51
|
+
/**
|
|
52
|
+
* "Save as template" (the `FrameTemplateProvider` seam): the button, then
|
|
53
|
+
* the notification that says how the host's save went.
|
|
54
|
+
*/
|
|
55
|
+
export const FRAME_TOOLBAR_SAVE_AS_TEMPLATE = SAVE_AS_TEMPLATE;
|
|
56
|
+
export const FRAME_TOAST_TEMPLATE_SAVED = [
|
|
57
|
+
'com.labre.frame.toast.template-saved',
|
|
58
|
+
'Frame saved as a template',
|
|
59
|
+
];
|
|
60
|
+
export const FRAME_TOAST_TEMPLATE_SAVE_FAILED = [
|
|
61
|
+
'com.labre.frame.toast.template-save-failed',
|
|
62
|
+
'The frame could not be saved as a template',
|
|
63
|
+
];
|
|
51
64
|
export const FRAME_TOOLBAR_BACKGROUND = [
|
|
52
65
|
'com.labre.frame.toolbar.background',
|
|
53
66
|
'Background',
|
|
@@ -117,6 +130,8 @@ export const FRAME_CHROME_WORDINGS = [
|
|
|
117
130
|
FRAME_DENSE_MENU_SLIDE,
|
|
118
131
|
FRAME_TOAST_INSERTED_MESSAGE,
|
|
119
132
|
FRAME_TOOLBAR_BACKGROUND,
|
|
133
|
+
FRAME_TOAST_TEMPLATE_SAVED,
|
|
134
|
+
FRAME_TOAST_TEMPLATE_SAVE_FAILED,
|
|
120
135
|
FRAME_PRESENT_TOOL_TOOLTIP,
|
|
121
136
|
FRAME_PRESENT_PREVIOUS,
|
|
122
137
|
FRAME_PRESENT_NEXT,
|
|
@@ -8,6 +8,7 @@ import { effects } from './effects.js';
|
|
|
8
8
|
import { FrameBlockInteraction } from './frame-block.js';
|
|
9
9
|
import { FrameHighlightManager } from './frame-highlight-manager.js';
|
|
10
10
|
import { FrameBlockSpec } from './frame-spec.js';
|
|
11
|
+
import { FrameTemplateWatcher } from './frame-template.js';
|
|
11
12
|
import { FrameTool } from './frame-tool.js';
|
|
12
13
|
import { frameToolbarExtension } from './frame-toolbar.js';
|
|
13
14
|
import { edgelessNavigatorBgWidget } from './present/navigator-bg-widget.js';
|
|
@@ -33,6 +34,11 @@ export class FrameViewExtension extends ViewExtensionProvider {
|
|
|
33
34
|
context.register(edgelessNavigatorBgWidget);
|
|
34
35
|
context.register(EdgelessClipboardFrameConfig);
|
|
35
36
|
context.register(FrameBlockInteraction);
|
|
37
|
+
// One watcher per editor a viewer edits in: a preview mounted on the
|
|
38
|
+
// same store would report the same deletion a second time.
|
|
39
|
+
if (!this.isPreview(context.scope)) {
|
|
40
|
+
context.register(FrameTemplateWatcher);
|
|
41
|
+
}
|
|
36
42
|
// The presentation order's one write (ADR 0034). The frame panel also
|
|
37
43
|
// mounts in page mode: it runs the imported descriptor, not this
|
|
38
44
|
// registration, so its reorder works in both modes. `canvas.present`
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ImageBlockModel, type ImageBlockProps } from '../affine-model/index.js';
|
|
2
|
+
import { type AssetRef } from '../affine-shared/services/index.js';
|
|
3
|
+
import { type IVec } from '../global/gfx/index.js';
|
|
4
|
+
import type { BlockStdScope } from '../std/index.js';
|
|
5
|
+
import type { BlockModel } from '../store/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Images from the host's asset library (`AssetPickerProvider`, labre P4): the
|
|
8
|
+
* block references a blob the host already stores, by its `sourceId`. Nothing
|
|
9
|
+
* here calls `BlobSource.set`; the bytes are read once, through the store's
|
|
10
|
+
* blob sources, only to measure an image whose ref carries no size.
|
|
11
|
+
*
|
|
12
|
+
* Without a host every entry point keeps the system file chooser and the
|
|
13
|
+
* upload path of `utils.ts`. Pasting and dropping never come here.
|
|
14
|
+
*/
|
|
15
|
+
type Size = {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Adds one image block per image ref next to `targetModel`, as one undo
|
|
21
|
+
* step. Refs that are not `image/*`, or whose size cannot be read, are
|
|
22
|
+
* skipped. Nothing is written on a read-only document.
|
|
23
|
+
*/
|
|
24
|
+
export declare function addSiblingImageBlocksFromAssets(std: BlockStdScope, assets: readonly AssetRef[], targetModel: BlockModel, placement?: 'after' | 'before'): Promise<string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* The canvas counterpart of {@link addSiblingImageBlocksFromAssets}: places
|
|
27
|
+
* the images like `addImages` does (around `point`, active layer, selected).
|
|
28
|
+
*/
|
|
29
|
+
export declare function addImagesFromAssets(std: BlockStdScope, assets: readonly AssetRef[], options: {
|
|
30
|
+
point?: IVec;
|
|
31
|
+
maxWidth?: number;
|
|
32
|
+
shouldTransformPoint?: boolean;
|
|
33
|
+
}): Promise<string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* The props that swap `model`'s picture for one of `natural` size while it
|
|
36
|
+
* keeps its displayed width and follows the new ratio. On the canvas the
|
|
37
|
+
* frame keeps its top-left corner and width; in a page the block keeps its
|
|
38
|
+
* `width` (an image still at its natural size, `width` unset, stays so).
|
|
39
|
+
*/
|
|
40
|
+
export declare function replacementProps(model: ImageBlockModel, asset: Pick<AssetRef, 'sourceId' | 'size'>, natural: Size): Partial<ImageBlockProps>;
|
|
41
|
+
/**
|
|
42
|
+
* Swaps the picture of one image block for `asset`, keeping its displayed
|
|
43
|
+
* width and its caption, in one undo step. `false` when nothing was written:
|
|
44
|
+
* a read-only document, the same blob, a ref that is not an image, a size
|
|
45
|
+
* that cannot be read, or a block deleted meanwhile.
|
|
46
|
+
*/
|
|
47
|
+
export declare function replaceImage(std: BlockStdScope, model: ImageBlockModel, asset: AssetRef): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* The image entries of the slash menu and of the mobile keyboard toolbar:
|
|
50
|
+
* the host's asset dialog when there is one, the system file chooser
|
|
51
|
+
* otherwise (the upload path, unchanged).
|
|
52
|
+
*/
|
|
53
|
+
export declare function pickAndAddSiblingImageBlocks(std: BlockStdScope, targetModel: BlockModel, placement?: 'after' | 'before'): Promise<string[]>;
|
|
54
|
+
/** The canvas's image button: the host's asset dialog, or the file chooser. */
|
|
55
|
+
export declare function pickAndAddImages(std: BlockStdScope, options: {
|
|
56
|
+
maxWidth?: number;
|
|
57
|
+
}): Promise<string[]>;
|
|
58
|
+
/**
|
|
59
|
+
* The image toolbar's "Replace": only with a host (`false` otherwise, and
|
|
60
|
+
* the toolbar does not offer it), one ref, `mode: 'replace'`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function pickAndReplaceImage(std: BlockStdScope, model: ImageBlockModel): Promise<boolean>;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { toast } from '../affine-components/toast/index.js';
|
|
2
|
+
import { ImageBlockSchema, } from '../affine-model/index.js';
|
|
3
|
+
import { AssetPickerProvider, translateKey, } from '../affine-shared/services/index.js';
|
|
4
|
+
import { getImageFilesFromLocal, readImageSize, } from '../affine-shared/utils/index.js';
|
|
5
|
+
import { Bound } from '../global/gfx/index.js';
|
|
6
|
+
import { IMAGE_TOAST_READ_SIZE_FAILED } from './translations.js';
|
|
7
|
+
import { addImages, addSiblingImageBlocks, placeImagesOnCanvas, } from './utils.js';
|
|
8
|
+
function isSize(width = 0, height = 0) {
|
|
9
|
+
return width > 0 && height > 0;
|
|
10
|
+
}
|
|
11
|
+
async function naturalSize(store, asset) {
|
|
12
|
+
const { width = 0, height = 0 } = asset;
|
|
13
|
+
if (isSize(width, height))
|
|
14
|
+
return { width, height };
|
|
15
|
+
const blob = await store.blobSync.get(asset.sourceId);
|
|
16
|
+
if (!blob)
|
|
17
|
+
return null;
|
|
18
|
+
// `readImageSize` trusts the MIME type; a blob source may not keep it.
|
|
19
|
+
const typed = blob.type ? blob : new Blob([blob], { type: asset.type });
|
|
20
|
+
const size = await readImageSize(typed);
|
|
21
|
+
return isSize(size.width, size.height) ? size : null;
|
|
22
|
+
}
|
|
23
|
+
async function imagePropsFrom(std, assets) {
|
|
24
|
+
const images = assets.filter(asset => asset.type.startsWith('image/'));
|
|
25
|
+
const sized = await Promise.all(images.map(async (asset) => {
|
|
26
|
+
const size = await naturalSize(std.store, asset);
|
|
27
|
+
return size
|
|
28
|
+
? { sourceId: asset.sourceId, size: asset.size, ...size }
|
|
29
|
+
: null;
|
|
30
|
+
}));
|
|
31
|
+
const props = sized.filter((p) => p !== null);
|
|
32
|
+
if (props.length < images.length) {
|
|
33
|
+
toast(std.host, translateKey(std, ...IMAGE_TOAST_READ_SIZE_FAILED));
|
|
34
|
+
}
|
|
35
|
+
return props;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Adds one image block per image ref next to `targetModel`, as one undo
|
|
39
|
+
* step. Refs that are not `image/*`, or whose size cannot be read, are
|
|
40
|
+
* skipped. Nothing is written on a read-only document.
|
|
41
|
+
*/
|
|
42
|
+
export async function addSiblingImageBlocksFromAssets(std, assets, targetModel, placement = 'after') {
|
|
43
|
+
if (std.store.readonly)
|
|
44
|
+
return [];
|
|
45
|
+
const props = await imagePropsFrom(std, assets);
|
|
46
|
+
// Re-checked: the document may have turned read-only while measuring.
|
|
47
|
+
if (!props.length || std.store.readonly)
|
|
48
|
+
return [];
|
|
49
|
+
const flavour = ImageBlockSchema.model.flavour;
|
|
50
|
+
std.store.captureSync();
|
|
51
|
+
return std.store.addSiblingBlocks(targetModel, props.map(p => ({ ...p, flavour })), placement);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The canvas counterpart of {@link addSiblingImageBlocksFromAssets}: places
|
|
55
|
+
* the images like `addImages` does (around `point`, active layer, selected).
|
|
56
|
+
*/
|
|
57
|
+
export async function addImagesFromAssets(std, assets, options) {
|
|
58
|
+
if (std.store.readonly)
|
|
59
|
+
return [];
|
|
60
|
+
const props = await imagePropsFrom(std, assets);
|
|
61
|
+
if (!props.length || std.store.readonly)
|
|
62
|
+
return [];
|
|
63
|
+
std.store.captureSync();
|
|
64
|
+
return placeImagesOnCanvas(std, props, options);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The props that swap `model`'s picture for one of `natural` size while it
|
|
68
|
+
* keeps its displayed width and follows the new ratio. On the canvas the
|
|
69
|
+
* frame keeps its top-left corner and width; in a page the block keeps its
|
|
70
|
+
* `width` (an image still at its natural size, `width` unset, stays so).
|
|
71
|
+
*/
|
|
72
|
+
export function replacementProps(model, asset, natural) {
|
|
73
|
+
const ratio = natural.height / natural.width;
|
|
74
|
+
const props = {
|
|
75
|
+
sourceId: asset.sourceId,
|
|
76
|
+
size: asset.size,
|
|
77
|
+
};
|
|
78
|
+
if (model.parent?.flavour === 'affine:surface') {
|
|
79
|
+
const bound = Bound.deserialize(model.props.xywh);
|
|
80
|
+
bound.h = bound.w * ratio;
|
|
81
|
+
props.xywh = bound.serialize();
|
|
82
|
+
props.width = natural.width;
|
|
83
|
+
props.height = natural.height;
|
|
84
|
+
return props;
|
|
85
|
+
}
|
|
86
|
+
const width = model.props.width ?? 0;
|
|
87
|
+
if (width > 0) {
|
|
88
|
+
props.width = width;
|
|
89
|
+
props.height = width * ratio;
|
|
90
|
+
}
|
|
91
|
+
return props;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Swaps the picture of one image block for `asset`, keeping its displayed
|
|
95
|
+
* width and its caption, in one undo step. `false` when nothing was written:
|
|
96
|
+
* a read-only document, the same blob, a ref that is not an image, a size
|
|
97
|
+
* that cannot be read, or a block deleted meanwhile.
|
|
98
|
+
*/
|
|
99
|
+
export async function replaceImage(std, model, asset) {
|
|
100
|
+
const { store } = model;
|
|
101
|
+
if (store.readonly)
|
|
102
|
+
return false;
|
|
103
|
+
if (!asset.type.startsWith('image/'))
|
|
104
|
+
return false;
|
|
105
|
+
if (asset.sourceId === model.props.sourceId$.peek())
|
|
106
|
+
return false;
|
|
107
|
+
const natural = await naturalSize(store, asset);
|
|
108
|
+
if (!natural) {
|
|
109
|
+
toast(std.host, translateKey(std, ...IMAGE_TOAST_READ_SIZE_FAILED));
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (store.readonly || !store.getBlock(model.id))
|
|
113
|
+
return false;
|
|
114
|
+
store.captureSync();
|
|
115
|
+
store.transact(() => {
|
|
116
|
+
store.updateBlock(model, replacementProps(model, asset, natural));
|
|
117
|
+
// A crop is a window on the picture it was drawn on (ADR 0037).
|
|
118
|
+
if (model.props.crop)
|
|
119
|
+
delete model.props.crop;
|
|
120
|
+
});
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
function pickRequest(std, request) {
|
|
124
|
+
const picker = std.getOptional(AssetPickerProvider);
|
|
125
|
+
if (!picker)
|
|
126
|
+
return null;
|
|
127
|
+
return picker.pick({ ...request, docId: std.store.id });
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The image entries of the slash menu and of the mobile keyboard toolbar:
|
|
131
|
+
* the host's asset dialog when there is one, the system file chooser
|
|
132
|
+
* otherwise (the upload path, unchanged).
|
|
133
|
+
*/
|
|
134
|
+
export async function pickAndAddSiblingImageBlocks(std, targetModel, placement = 'after') {
|
|
135
|
+
const picked = pickRequest(std, {
|
|
136
|
+
kind: 'image',
|
|
137
|
+
mode: 'insert',
|
|
138
|
+
multiple: true,
|
|
139
|
+
});
|
|
140
|
+
if (!picked) {
|
|
141
|
+
const files = await getImageFilesFromLocal();
|
|
142
|
+
return addSiblingImageBlocks(std, files, targetModel, placement);
|
|
143
|
+
}
|
|
144
|
+
const assets = await picked;
|
|
145
|
+
if (!assets?.length)
|
|
146
|
+
return [];
|
|
147
|
+
return addSiblingImageBlocksFromAssets(std, assets, targetModel, placement);
|
|
148
|
+
}
|
|
149
|
+
/** The canvas's image button: the host's asset dialog, or the file chooser. */
|
|
150
|
+
export async function pickAndAddImages(std, options) {
|
|
151
|
+
const picked = pickRequest(std, {
|
|
152
|
+
kind: 'image',
|
|
153
|
+
mode: 'insert',
|
|
154
|
+
multiple: true,
|
|
155
|
+
});
|
|
156
|
+
if (!picked) {
|
|
157
|
+
const files = await getImageFilesFromLocal();
|
|
158
|
+
return addImages(std, files, options);
|
|
159
|
+
}
|
|
160
|
+
const assets = await picked;
|
|
161
|
+
if (!assets?.length)
|
|
162
|
+
return [];
|
|
163
|
+
return addImagesFromAssets(std, assets, options);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The image toolbar's "Replace": only with a host (`false` otherwise, and
|
|
167
|
+
* the toolbar does not offer it), one ref, `mode: 'replace'`.
|
|
168
|
+
*/
|
|
169
|
+
export async function pickAndReplaceImage(std, model) {
|
|
170
|
+
if (model.store.readonly)
|
|
171
|
+
return false;
|
|
172
|
+
const picked = pickRequest(std, {
|
|
173
|
+
kind: 'image',
|
|
174
|
+
mode: 'replace',
|
|
175
|
+
multiple: false,
|
|
176
|
+
});
|
|
177
|
+
if (!picked)
|
|
178
|
+
return false;
|
|
179
|
+
const [asset] = (await picked) ?? [];
|
|
180
|
+
if (!asset)
|
|
181
|
+
return false;
|
|
182
|
+
return replaceImage(std, model, asset);
|
|
183
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Command } from '../../std/index.js';
|
|
2
2
|
import type { BlockModel } from '../../store/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Inserts images next to the selection: from the host's asset dialog
|
|
5
|
+
* (`AssetPickerProvider`) when there is one, from the system file chooser
|
|
6
|
+
* otherwise.
|
|
7
|
+
*/
|
|
3
8
|
export declare const insertImagesCommand: Command<{
|
|
4
9
|
selectedModels?: BlockModel[];
|
|
5
10
|
removeEmptyLine?: boolean;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { pickAndAddSiblingImageBlocks } from '../asset-picker.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inserts images next to the selection: from the host's asset dialog
|
|
4
|
+
* (`AssetPickerProvider`) when there is one, from the system file chooser
|
|
5
|
+
* otherwise.
|
|
6
|
+
*/
|
|
3
7
|
export const insertImagesCommand = (ctx, next) => {
|
|
4
8
|
const { selectedModels, placement, removeEmptyLine, std } = ctx;
|
|
5
9
|
if (!selectedModels?.length)
|
|
@@ -8,12 +12,8 @@ export const insertImagesCommand = (ctx, next) => {
|
|
|
8
12
|
? selectedModels[0]
|
|
9
13
|
: selectedModels[selectedModels.length - 1];
|
|
10
14
|
return next({
|
|
11
|
-
insertedImageIds:
|
|
12
|
-
.
|
|
13
|
-
.then(result => {
|
|
14
|
-
if (result.length &&
|
|
15
|
-
removeEmptyLine &&
|
|
16
|
-
targetModel.text?.length === 0) {
|
|
15
|
+
insertedImageIds: pickAndAddSiblingImageBlocks(std, targetModel, placement).then(result => {
|
|
16
|
+
if (result.length && removeEmptyLine && targetModel.text?.length === 0) {
|
|
17
17
|
std.store.deleteBlock(targetModel);
|
|
18
18
|
}
|
|
19
19
|
return result;
|
|
@@ -7,6 +7,17 @@ export declare class ImageBlockPageComponent extends ImageBlockPageComponent_bas
|
|
|
7
7
|
static styles: import("lit").CSSResult;
|
|
8
8
|
resizeable$: import("@preact/signals-core").ReadonlySignal<boolean>;
|
|
9
9
|
private _isDragging;
|
|
10
|
+
/** The source's pixel size once loaded: the crop's aspect needs it. */
|
|
11
|
+
private accessor _loadedNatural;
|
|
12
|
+
private readonly _onLoad;
|
|
13
|
+
/**
|
|
14
|
+
* The box of a cropped page image (ADR 0037). `width`/`height` stay the
|
|
15
|
+
* displayed size of the WHOLE picture, so the box is the crop's share of it
|
|
16
|
+
* and cropping keeps the picture's scale. While the size is unknown (no
|
|
17
|
+
* stored size, image not loaded yet) the image is drawn whole: an empty
|
|
18
|
+
* box would never load a lazy image.
|
|
19
|
+
*/
|
|
20
|
+
private _croppedBox;
|
|
10
21
|
private get _doc();
|
|
11
22
|
private get _host();
|
|
12
23
|
private get _model();
|
|
@@ -40,16 +40,20 @@ import { SignalWatcher, WithDisposable } from '../../global/lit/index.js';
|
|
|
40
40
|
import { BlockSelection, ShadowlessElement, TextSelection } from '../../std/index.js';
|
|
41
41
|
import { computed } from '@preact/signals-core';
|
|
42
42
|
import { css, html } from 'lit';
|
|
43
|
-
import { property, query } from 'lit/decorators.js';
|
|
43
|
+
import { property, query, state } from 'lit/decorators.js';
|
|
44
44
|
import { classMap } from 'lit/directives/class-map.js';
|
|
45
45
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
46
46
|
import { when } from 'lit/directives/when.js';
|
|
47
|
+
import { croppedImageStyle } from '../crop/crop-controller.js';
|
|
47
48
|
import { ImageResizeManager } from '../image-resize-manager.js';
|
|
48
49
|
import { IMAGE_LABEL, IMAGE_TOAST_DOWNLOAD_FAILED } from '../translations.js';
|
|
49
50
|
import { shouldResizeImage } from '../utils.js';
|
|
50
51
|
import { ImageSelectedRect } from './image-selected-rect.js';
|
|
51
52
|
let ImageBlockPageComponent = (() => {
|
|
52
53
|
let _classSuper = SignalWatcher(WithDisposable(ShadowlessElement));
|
|
54
|
+
let __loadedNatural_decorators;
|
|
55
|
+
let __loadedNatural_initializers = [];
|
|
56
|
+
let __loadedNatural_extraInitializers = [];
|
|
53
57
|
let _block_decorators;
|
|
54
58
|
let _block_initializers = [];
|
|
55
59
|
let _block_extraInitializers = [];
|
|
@@ -62,9 +66,11 @@ let ImageBlockPageComponent = (() => {
|
|
|
62
66
|
return class ImageBlockPageComponent extends _classSuper {
|
|
63
67
|
static {
|
|
64
68
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
69
|
+
__loadedNatural_decorators = [state()];
|
|
65
70
|
_block_decorators = [property({ attribute: false })];
|
|
66
71
|
_state_decorators = [property({ attribute: false })];
|
|
67
72
|
_resizeImg_decorators = [query('.resizable-img')];
|
|
73
|
+
__esDecorate(this, null, __loadedNatural_decorators, { kind: "accessor", name: "_loadedNatural", static: false, private: false, access: { has: obj => "_loadedNatural" in obj, get: obj => obj._loadedNatural, set: (obj, value) => { obj._loadedNatural = value; } }, metadata: _metadata }, __loadedNatural_initializers, __loadedNatural_extraInitializers);
|
|
68
74
|
__esDecorate(this, null, _block_decorators, { kind: "accessor", name: "block", static: false, private: false, access: { has: obj => "block" in obj, get: obj => obj.block, set: (obj, value) => { obj.block = value; } }, metadata: _metadata }, _block_initializers, _block_extraInitializers);
|
|
69
75
|
__esDecorate(this, null, _state_decorators, { kind: "accessor", name: "state", static: false, private: false, access: { has: obj => "state" in obj, get: obj => obj.state, set: (obj, value) => { obj.state = value; } }, metadata: _metadata }, _state_initializers, _state_extraInitializers);
|
|
70
76
|
__esDecorate(this, null, _resizeImg_decorators, { kind: "accessor", name: "resizeImg", static: false, private: false, access: { has: obj => "resizeImg" in obj, get: obj => obj.resizeImg, set: (obj, value) => { obj.resizeImg = value; } }, metadata: _metadata }, _resizeImg_initializers, _resizeImg_extraInitializers);
|
|
@@ -115,10 +121,44 @@ let ImageBlockPageComponent = (() => {
|
|
|
115
121
|
height: 100%;
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
affine-page-image .labre-image-crop-window {
|
|
125
|
+
position: absolute;
|
|
126
|
+
inset: 0;
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
}
|
|
129
|
+
|
|
118
130
|
affine-page-image .comment-highlighted {
|
|
119
131
|
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
|
|
120
132
|
}
|
|
121
133
|
`; }
|
|
134
|
+
#_loadedNatural_accessor_storage;
|
|
135
|
+
/** The source's pixel size once loaded: the crop's aspect needs it. */
|
|
136
|
+
get _loadedNatural() { return this.#_loadedNatural_accessor_storage; }
|
|
137
|
+
set _loadedNatural(value) { this.#_loadedNatural_accessor_storage = value; }
|
|
138
|
+
/**
|
|
139
|
+
* The box of a cropped page image (ADR 0037). `width`/`height` stay the
|
|
140
|
+
* displayed size of the WHOLE picture, so the box is the crop's share of it
|
|
141
|
+
* and cropping keeps the picture's scale. While the size is unknown (no
|
|
142
|
+
* stored size, image not loaded yet) the image is drawn whole: an empty
|
|
143
|
+
* box would never load a lazy image.
|
|
144
|
+
*/
|
|
145
|
+
_croppedBox(imageSize) {
|
|
146
|
+
const crop = this.block.cropController.crop$.value;
|
|
147
|
+
if (!crop)
|
|
148
|
+
return null;
|
|
149
|
+
const { width, height } = this._model.props;
|
|
150
|
+
const natural = width && height ? { width, height } : this._loadedNatural;
|
|
151
|
+
if (!natural)
|
|
152
|
+
return null;
|
|
153
|
+
const aspect = (crop.w * natural.width) / (crop.h * natural.height);
|
|
154
|
+
const boxWidth = this._isDragging
|
|
155
|
+
? imageSize.width
|
|
156
|
+
: `${natural.width * crop.w}px`;
|
|
157
|
+
return {
|
|
158
|
+
crop,
|
|
159
|
+
style: { width: boxWidth, aspectRatio: `${aspect}` },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
122
162
|
get _doc() {
|
|
123
163
|
return this.block.store;
|
|
124
164
|
}
|
|
@@ -326,6 +366,7 @@ let ImageBlockPageComponent = (() => {
|
|
|
326
366
|
}
|
|
327
367
|
render() {
|
|
328
368
|
const imageSize = this._normalizeImageSize();
|
|
369
|
+
const cropped = this._croppedBox(imageSize);
|
|
329
370
|
const imageSelectedRect = this.resizeable$.value
|
|
330
371
|
? ImageSelectedRect(this._doc.readonly)
|
|
331
372
|
: null;
|
|
@@ -339,18 +380,31 @@ let ImageBlockPageComponent = (() => {
|
|
|
339
380
|
'resizable-img': true,
|
|
340
381
|
'comment-highlighted': this.block.isCommentHighlighted,
|
|
341
382
|
})}
|
|
342
|
-
style=${styleMap(imageSize)}
|
|
383
|
+
style=${styleMap(cropped ? cropped.style : imageSize)}
|
|
343
384
|
>
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
385
|
+
${cropped
|
|
386
|
+
? html `<div class="labre-image-crop-window">
|
|
387
|
+
<img
|
|
388
|
+
class="drag-target"
|
|
389
|
+
draggable="false"
|
|
390
|
+
loading="lazy"
|
|
391
|
+
src=${blobUrl}
|
|
392
|
+
alt=${caption}
|
|
393
|
+
style=${croppedImageStyle(cropped.crop)}
|
|
394
|
+
@load=${this._onLoad}
|
|
395
|
+
@error=${this._handleError}
|
|
396
|
+
/>
|
|
397
|
+
</div>`
|
|
398
|
+
: html `<img
|
|
399
|
+
class="drag-target"
|
|
400
|
+
draggable="false"
|
|
401
|
+
loading="lazy"
|
|
402
|
+
src=${blobUrl}
|
|
403
|
+
alt=${caption}
|
|
404
|
+
@load=${this._onLoad}
|
|
405
|
+
@error=${this._handleError}
|
|
406
|
+
/>`}
|
|
407
|
+
${imageSelectedRect} ${this.block.cropController.renderOverlay(blobUrl)}
|
|
354
408
|
</div>
|
|
355
409
|
|
|
356
410
|
${when(loading, () => html `<div class="loading">${icon}</div>`)}
|
|
@@ -377,6 +431,21 @@ let ImageBlockPageComponent = (() => {
|
|
|
377
431
|
super(...arguments);
|
|
378
432
|
this.resizeable$ = computed(() => this.block.resizeable$.value);
|
|
379
433
|
this._isDragging = false;
|
|
434
|
+
this.#_loadedNatural_accessor_storage = __runInitializers(this, __loadedNatural_initializers, null);
|
|
435
|
+
this._onLoad = (__runInitializers(this, __loadedNatural_extraInitializers), (event) => {
|
|
436
|
+
const img = event.currentTarget;
|
|
437
|
+
if (!img.naturalWidth || !img.naturalHeight)
|
|
438
|
+
return;
|
|
439
|
+
const loaded = this._loadedNatural;
|
|
440
|
+
if (loaded?.width === img.naturalWidth &&
|
|
441
|
+
loaded.height === img.naturalHeight) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
this._loadedNatural = {
|
|
445
|
+
width: img.naturalWidth,
|
|
446
|
+
height: img.naturalHeight,
|
|
447
|
+
};
|
|
448
|
+
});
|
|
380
449
|
this.#block_accessor_storage = __runInitializers(this, _block_initializers, void 0);
|
|
381
450
|
this.#state_accessor_storage = (__runInitializers(this, _block_extraInitializers), __runInitializers(this, _state_initializers, void 0));
|
|
382
451
|
this.#resizeImg_accessor_storage = (__runInitializers(this, _state_extraInitializers), __runInitializers(this, _resizeImg_initializers, void 0));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImageBlockModel } from '../../affine-model/index.js';
|
|
2
|
+
import { type ToolbarAction, type ToolbarContext } from '../../affine-shared/services/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* "Replace" on the image toolbar, page and canvas alike: opens the host's
|
|
5
|
+
* asset dialog in `replace` mode and swaps the picture in place
|
|
6
|
+
* (`replaceImage`). It exists only with an `AssetPickerProvider` and on a
|
|
7
|
+
* writable document: without a host there is no library to pick from.
|
|
8
|
+
*/
|
|
9
|
+
export declare function imageReplaceToolbarAction(getModel: (ctx: ToolbarContext) => ImageBlockModel | null): ToolbarAction;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AssetPickerProvider, } from '../../affine-shared/services/index.js';
|
|
2
|
+
import { ReplaceIcon } from '@blocksuite/icons/lit';
|
|
3
|
+
import { pickAndReplaceImage } from '../asset-picker.js';
|
|
4
|
+
import { IMAGE_TOOLBAR_REPLACE } from '../translations.js';
|
|
5
|
+
/**
|
|
6
|
+
* "Replace" on the image toolbar, page and canvas alike: opens the host's
|
|
7
|
+
* asset dialog in `replace` mode and swaps the picture in place
|
|
8
|
+
* (`replaceImage`). It exists only with an `AssetPickerProvider` and on a
|
|
9
|
+
* writable document: without a host there is no library to pick from.
|
|
10
|
+
*/
|
|
11
|
+
export function imageReplaceToolbarAction(getModel) {
|
|
12
|
+
return {
|
|
13
|
+
id: 'a.replace',
|
|
14
|
+
tooltip: 'Replace',
|
|
15
|
+
tooltipWording: IMAGE_TOOLBAR_REPLACE,
|
|
16
|
+
icon: ReplaceIcon(),
|
|
17
|
+
when: ctx => !ctx.readonly &&
|
|
18
|
+
!!ctx.std.getOptional(AssetPickerProvider) &&
|
|
19
|
+
getModel(ctx) !== null,
|
|
20
|
+
run(ctx) {
|
|
21
|
+
const model = getModel(ctx);
|
|
22
|
+
if (!model)
|
|
23
|
+
return;
|
|
24
|
+
pickAndReplaceImage(ctx.std, model).catch(console.error);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -2,10 +2,12 @@ import { ImageBlockModel } from '../../affine-model/index.js';
|
|
|
2
2
|
import { ActionPlacement, blockCommentToolbarButton, TOOLBAR_CAPTION, TOOLBAR_COPY, TOOLBAR_DELETE, TOOLBAR_DOWNLOAD, TOOLBAR_DUPLICATE, ToolbarModuleExtension, } from '../../affine-shared/services/index.js';
|
|
3
3
|
import { BookmarkIcon, CaptionIcon, CopyIcon, DeleteIcon, DownloadIcon, DuplicateIcon, } from '@blocksuite/icons/lit';
|
|
4
4
|
import { BlockFlavourIdentifier } from '../../std/index.js';
|
|
5
|
+
import { imageCropToolbarAction } from '../crop/crop-toolbar.js';
|
|
5
6
|
import { ImageBlockComponent } from '../image-block.js';
|
|
6
7
|
import { ImageEdgelessBlockComponent } from '../image-edgeless-block.js';
|
|
7
8
|
import { IMAGE_TOOLBAR_TURN_INTO_CARD_VIEW } from '../translations.js';
|
|
8
9
|
import { duplicate } from '../utils.js';
|
|
10
|
+
import { imageReplaceToolbarAction } from './replace-action.js';
|
|
9
11
|
const trackBaseProps = {
|
|
10
12
|
category: 'image',
|
|
11
13
|
type: 'card view',
|
|
@@ -22,6 +24,7 @@ const builtinToolbarConfig = {
|
|
|
22
24
|
block?.download();
|
|
23
25
|
},
|
|
24
26
|
},
|
|
27
|
+
imageReplaceToolbarAction(ctx => ctx.getCurrentBlockByType(ImageBlockComponent)?.model ?? null),
|
|
25
28
|
{
|
|
26
29
|
id: 'b.caption',
|
|
27
30
|
tooltip: 'Caption',
|
|
@@ -36,6 +39,7 @@ const builtinToolbarConfig = {
|
|
|
36
39
|
});
|
|
37
40
|
},
|
|
38
41
|
},
|
|
42
|
+
imageCropToolbarAction(ctx => ctx.getCurrentBlockByType(ImageBlockComponent)),
|
|
39
43
|
{
|
|
40
44
|
id: 'c.comment',
|
|
41
45
|
...blockCommentToolbarButton,
|
|
@@ -117,6 +121,7 @@ const builtinSurfaceToolbarConfig = {
|
|
|
117
121
|
block?.download();
|
|
118
122
|
},
|
|
119
123
|
},
|
|
124
|
+
imageReplaceToolbarAction(ctx => ctx.getCurrentBlockByType(ImageEdgelessBlockComponent)?.model ?? null),
|
|
120
125
|
{
|
|
121
126
|
id: 'b.caption',
|
|
122
127
|
tooltip: 'Caption',
|
|
@@ -131,6 +136,7 @@ const builtinSurfaceToolbarConfig = {
|
|
|
131
136
|
});
|
|
132
137
|
},
|
|
133
138
|
},
|
|
139
|
+
imageCropToolbarAction(ctx => ctx.getCurrentBlockByType(ImageEdgelessBlockComponent)),
|
|
134
140
|
{
|
|
135
141
|
id: 'c.comment',
|
|
136
142
|
...blockCommentToolbarButton,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ImageBlockModel, type ImageCrop, type ImageCropMask } from '../../affine-model/index.js';
|
|
2
|
+
import { nothing } from 'lit';
|
|
3
|
+
import { type CropRect } from './crop-math.js';
|
|
4
|
+
/** What the controller needs from the block it serves, page or canvas. */
|
|
5
|
+
export interface CroppableImageBlock {
|
|
6
|
+
readonly model: ImageBlockModel;
|
|
7
|
+
querySelector<E extends Element = Element>(selectors: string): E | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The « Rogner » state of one image block (ADR 0037), shared by the page and
|
|
11
|
+
* the canvas components: which preset the crop frame is open on, if any, and
|
|
12
|
+
* the gestures that write. The frame is local UI and never stored.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ImageCropController {
|
|
15
|
+
private readonly _block;
|
|
16
|
+
private readonly _onCanvas;
|
|
17
|
+
/** The mask the crop frame is open on, `null` when it is closed. */
|
|
18
|
+
readonly editing$: import("@preact/signals-core").Signal<"free" | "16:9" | "4:3" | "1:1" | null>;
|
|
19
|
+
readonly crop$: import("@preact/signals-core").ReadonlySignal<ImageCrop | undefined>;
|
|
20
|
+
constructor(_block: CroppableImageBlock, _onCanvas: boolean);
|
|
21
|
+
/**
|
|
22
|
+
* The source's proportions: the loaded `<img>`'s pixel size, else what the
|
|
23
|
+
* block stores. Only the ratio matters to the crop, and an SVG without an
|
|
24
|
+
* intrinsic size loads with none: a canvas box is the cropped share of the
|
|
25
|
+
* whole picture, a page `width`/`height` is the whole picture.
|
|
26
|
+
*/
|
|
27
|
+
natural(): {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* Open the frame on a preset. A ratio preset first writes its largest
|
|
33
|
+
* centred window — unless the crop already uses that preset, which keeps
|
|
34
|
+
* the author's adjustment. Free opens on the current crop and writes
|
|
35
|
+
* nothing until a drag.
|
|
36
|
+
*/
|
|
37
|
+
choose(mask: ImageCropMask): void;
|
|
38
|
+
commit(rect: CropRect, mask?: "free" | "16:9" | "4:3" | "1:1"): boolean;
|
|
39
|
+
/** « Image d'origine »: removes the crop; the bytes never changed. */
|
|
40
|
+
restoreOriginal(): boolean;
|
|
41
|
+
stop(): void;
|
|
42
|
+
/** The frame, or nothing when it is closed or the page is readonly. */
|
|
43
|
+
renderOverlay(src: string | null | undefined, rotate?: number): import("lit-html").TemplateResult<1> | typeof nothing;
|
|
44
|
+
}
|
|
45
|
+
/** Inline styles of an `<img>` showing `crop` of its source in its box. */
|
|
46
|
+
export declare function croppedImageStyle(crop: ImageCrop): import("lit-html/directive.js").DirectiveResult<typeof import("lit-html/directives/style-map.js").StyleMapDirective>;
|