@gradio/imageeditor 0.11.11 → 0.12.1
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/CHANGELOG.md +23 -0
- package/Index.svelte +3 -0
- package/dist/Index.svelte +3 -0
- package/dist/Index.svelte.d.ts +4 -0
- package/dist/shared/ImageEditor.svelte +14 -0
- package/dist/shared/ImageEditor.svelte.d.ts +3 -1
- package/dist/shared/InteractiveImageEditor.svelte +4 -0
- package/dist/shared/InteractiveImageEditor.svelte.d.ts +3 -0
- package/dist/shared/layers/Layers.svelte +28 -14
- package/dist/shared/layers/utils.d.ts +6 -2
- package/dist/shared/layers/utils.js +61 -36
- package/dist/shared/tools/Brush.svelte +6 -2
- package/dist/shared/utils/commands.d.ts +6 -2
- package/dist/shared/utils/commands.js +8 -0
- package/package.json +9 -9
- package/shared/ImageEditor.svelte +17 -1
- package/shared/InteractiveImageEditor.svelte +4 -1
- package/shared/layers/Layers.svelte +30 -16
- package/shared/layers/utils.ts +85 -54
- package/shared/tools/Brush.svelte +6 -3
- package/shared/utils/commands.ts +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @gradio/imageeditor
|
|
2
2
|
|
|
3
|
+
## 0.12.1
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- [#10306](https://github.com/gradio-app/gradio/pull/10306) [`9fc988e`](https://github.com/gradio-app/gradio/commit/9fc988ebaf4bea24b71308922bb1889f1d3f4f6e) - Fix bug where ImageEditor always sends empty layers list to the backend. Thanks @freddyaboulton!
|
|
8
|
+
|
|
9
|
+
## 0.12.0
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- [#10192](https://github.com/gradio-app/gradio/pull/10192) [`4fc7fb7`](https://github.com/gradio-app/gradio/commit/4fc7fb777c42af537e4af612423fa44029657d41) - Ensure components can be remounted with their previous data. Thanks @pngwn!
|
|
14
|
+
|
|
15
|
+
### Dependency updates
|
|
16
|
+
|
|
17
|
+
- @gradio/atoms@0.13.0
|
|
18
|
+
- @gradio/utils@0.10.0
|
|
19
|
+
- @gradio/upload@0.14.4
|
|
20
|
+
- @gradio/client@1.9.0
|
|
21
|
+
- @gradio/icons@0.9.0
|
|
22
|
+
- @gradio/statustracker@0.10.0
|
|
23
|
+
- @gradio/wasm@0.16.0
|
|
24
|
+
- @gradio/image@0.20.0
|
|
25
|
+
|
|
3
26
|
## 0.11.11
|
|
4
27
|
|
|
5
28
|
### Dependency updates
|
package/Index.svelte
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
};
|
|
58
58
|
export let canvas_size: [number, number] | undefined;
|
|
59
59
|
export let show_fullscreen_button = true;
|
|
60
|
+
export let full_history: any = null;
|
|
60
61
|
|
|
61
62
|
export let gradio: Gradio<{
|
|
62
63
|
change: never;
|
|
@@ -185,6 +186,7 @@
|
|
|
185
186
|
/>
|
|
186
187
|
|
|
187
188
|
<InteractiveImageEditor
|
|
189
|
+
on:history={(e) => (full_history = e.detail)}
|
|
188
190
|
bind:dragging
|
|
189
191
|
{canvas_size}
|
|
190
192
|
on:change={() => handle_history_change()}
|
|
@@ -221,6 +223,7 @@
|
|
|
221
223
|
upload={(...args) => gradio.client.upload(...args)}
|
|
222
224
|
stream_handler={(...args) => gradio.client.stream(...args)}
|
|
223
225
|
{placeholder}
|
|
226
|
+
{full_history}
|
|
224
227
|
></InteractiveImageEditor>
|
|
225
228
|
</Block>
|
|
226
229
|
{/if}
|
package/dist/Index.svelte
CHANGED
|
@@ -42,6 +42,7 @@ export let attached_events = [];
|
|
|
42
42
|
export let server;
|
|
43
43
|
export let canvas_size;
|
|
44
44
|
export let show_fullscreen_button = true;
|
|
45
|
+
export let full_history = null;
|
|
45
46
|
export let gradio;
|
|
46
47
|
let editor_instance;
|
|
47
48
|
let image_id = null;
|
|
@@ -142,6 +143,7 @@ $:
|
|
|
142
143
|
/>
|
|
143
144
|
|
|
144
145
|
<InteractiveImageEditor
|
|
146
|
+
on:history={(e) => (full_history = e.detail)}
|
|
145
147
|
bind:dragging
|
|
146
148
|
{canvas_size}
|
|
147
149
|
on:change={() => handle_history_change()}
|
|
@@ -178,6 +180,7 @@ $:
|
|
|
178
180
|
upload={(...args) => gradio.client.upload(...args)}
|
|
179
181
|
stream_handler={(...args) => gradio.client.stream(...args)}
|
|
180
182
|
{placeholder}
|
|
183
|
+
{full_history}
|
|
181
184
|
></InteractiveImageEditor>
|
|
182
185
|
</Block>
|
|
183
186
|
{/if}
|
package/dist/Index.svelte.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ declare const __propDef: {
|
|
|
36
36
|
};
|
|
37
37
|
canvas_size: [number, number] | undefined;
|
|
38
38
|
show_fullscreen_button?: boolean | undefined;
|
|
39
|
+
full_history?: any;
|
|
39
40
|
gradio: Gradio<{
|
|
40
41
|
change: never;
|
|
41
42
|
error: string;
|
|
@@ -158,6 +159,9 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
|
158
159
|
get show_fullscreen_button(): boolean | undefined;
|
|
159
160
|
/**accessor*/
|
|
160
161
|
set show_fullscreen_button(_: boolean | undefined);
|
|
162
|
+
get full_history(): any;
|
|
163
|
+
/**accessor*/
|
|
164
|
+
set full_history(_: any);
|
|
161
165
|
get gradio(): Gradio<{
|
|
162
166
|
change: never;
|
|
163
167
|
error: string;
|
|
@@ -22,6 +22,7 @@ const dispatch = createEventDispatcher();
|
|
|
22
22
|
export let crop_constraint = false;
|
|
23
23
|
export let canvas_size;
|
|
24
24
|
export let parent_height;
|
|
25
|
+
export let full_history = null;
|
|
25
26
|
$:
|
|
26
27
|
orig_canvas_size = canvas_size;
|
|
27
28
|
const BASE_DIMENSIONS = canvas_size || [800, 600];
|
|
@@ -54,8 +55,16 @@ const position_spring = spring(
|
|
|
54
55
|
const pixi = writable(null);
|
|
55
56
|
const CommandManager = command_manager();
|
|
56
57
|
const { can_redo, can_undo, current_history } = CommandManager;
|
|
58
|
+
function get_start_history(history2) {
|
|
59
|
+
if (history2.previous) {
|
|
60
|
+
return get_start_history(history2.previous);
|
|
61
|
+
}
|
|
62
|
+
return history2;
|
|
63
|
+
}
|
|
57
64
|
$:
|
|
58
65
|
$current_history.previous, dispatch("change");
|
|
66
|
+
$:
|
|
67
|
+
dispatch("history", get_start_history($current_history));
|
|
59
68
|
$: {
|
|
60
69
|
history = !!$current_history.previous || $active_tool !== "bg";
|
|
61
70
|
}
|
|
@@ -225,6 +234,11 @@ onMount(() => {
|
|
|
225
234
|
}
|
|
226
235
|
}
|
|
227
236
|
resize(...$dimensions);
|
|
237
|
+
tick().then(() => {
|
|
238
|
+
if (full_history) {
|
|
239
|
+
CommandManager.hydrate(full_history);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
228
242
|
return () => {
|
|
229
243
|
$pixi?.destroy();
|
|
230
244
|
resizer.disconnect();
|
|
@@ -2,7 +2,7 @@ import { SvelteComponent } from "svelte";
|
|
|
2
2
|
import type { Writable } from "svelte/store";
|
|
3
3
|
import type { Spring } from "svelte/motion";
|
|
4
4
|
import { type PixiApp } from "./utils/pixi";
|
|
5
|
-
import { type CommandManager } from "./utils/commands";
|
|
5
|
+
import { type CommandManager, type CommandNode } from "./utils/commands";
|
|
6
6
|
export declare const EDITOR_KEY: unique symbol;
|
|
7
7
|
export type context_type = "bg" | "layers" | "crop" | "draw" | "erase";
|
|
8
8
|
import { type tool } from "./tools";
|
|
@@ -52,6 +52,7 @@ declare const __propDef: {
|
|
|
52
52
|
crop_constraint?: boolean | undefined;
|
|
53
53
|
canvas_size: [number, number] | undefined;
|
|
54
54
|
parent_height: number;
|
|
55
|
+
full_history?: (CommandNode | null) | undefined;
|
|
55
56
|
height?: number | undefined;
|
|
56
57
|
get_blobs?: (() => Promise<ImageBlobs>) | undefined;
|
|
57
58
|
handle_remove?: (() => void) | undefined;
|
|
@@ -61,6 +62,7 @@ declare const __propDef: {
|
|
|
61
62
|
clear?: CustomEvent<undefined> | undefined;
|
|
62
63
|
save: CustomEvent<void>;
|
|
63
64
|
change: CustomEvent<void>;
|
|
65
|
+
history: CustomEvent<Writable<CommandNode>>;
|
|
64
66
|
} & {
|
|
65
67
|
[evt: string]: CustomEvent<any>;
|
|
66
68
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<script>import { createEventDispatcher } from "svelte";
|
|
4
4
|
import {} from "@gradio/utils";
|
|
5
5
|
import { prepare_files } from "@gradio/client";
|
|
6
|
+
import {} from "./utils/commands";
|
|
6
7
|
import ImageEditor from "./ImageEditor.svelte";
|
|
7
8
|
import Layers from "./layers/Layers.svelte";
|
|
8
9
|
import {} from "./tools/Brush.svelte";
|
|
@@ -36,6 +37,7 @@ export let stream_handler;
|
|
|
36
37
|
export let dragging;
|
|
37
38
|
export let placeholder = void 0;
|
|
38
39
|
export let height = 450;
|
|
40
|
+
export let full_history = null;
|
|
39
41
|
const dispatch = createEventDispatcher();
|
|
40
42
|
let editor;
|
|
41
43
|
function is_not_null(o) {
|
|
@@ -157,6 +159,7 @@ $:
|
|
|
157
159
|
label={label || i18n("image.image")}
|
|
158
160
|
/>
|
|
159
161
|
<ImageEditor
|
|
162
|
+
on:history
|
|
160
163
|
{canvas_size}
|
|
161
164
|
crop_size={Array.isArray(crop_size) ? crop_size : undefined}
|
|
162
165
|
bind:this={editor}
|
|
@@ -170,6 +173,7 @@ $:
|
|
|
170
173
|
bind:bg
|
|
171
174
|
{sources}
|
|
172
175
|
crop_constraint={!!crop_constraint}
|
|
176
|
+
{full_history}
|
|
173
177
|
>
|
|
174
178
|
<Tools {i18n}>
|
|
175
179
|
<Layers layer_files={value?.layers || null} enable_layers={layers} />
|
|
@@ -11,6 +11,7 @@ export interface ImageBlobs {
|
|
|
11
11
|
}
|
|
12
12
|
import { type I18nFormatter } from "@gradio/utils";
|
|
13
13
|
import { type FileData, type Client } from "@gradio/client";
|
|
14
|
+
import { type CommandNode } from "./utils/commands";
|
|
14
15
|
import { type Brush as IBrush } from "./tools/Brush.svelte";
|
|
15
16
|
import { type Eraser } from "./tools/Brush.svelte";
|
|
16
17
|
declare const __propDef: {
|
|
@@ -36,10 +37,12 @@ declare const __propDef: {
|
|
|
36
37
|
dragging: boolean;
|
|
37
38
|
placeholder?: string | undefined;
|
|
38
39
|
height?: number | undefined;
|
|
40
|
+
full_history?: (CommandNode | null) | undefined;
|
|
39
41
|
get_data?: (() => Promise<ImageBlobs>) | undefined;
|
|
40
42
|
image_id?: (null | string) | undefined;
|
|
41
43
|
};
|
|
42
44
|
events: {
|
|
45
|
+
history: CustomEvent<import("svelte/store").Writable<CommandNode>>;
|
|
43
46
|
save: CustomEvent<void>;
|
|
44
47
|
clear?: CustomEvent<undefined> | undefined;
|
|
45
48
|
upload?: CustomEvent<undefined> | undefined;
|
|
@@ -6,9 +6,20 @@ import { Layers } from "@gradio/icons";
|
|
|
6
6
|
let show_layers = false;
|
|
7
7
|
export let layer_files = [];
|
|
8
8
|
export let enable_layers = true;
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
pixi,
|
|
11
|
+
current_layer,
|
|
12
|
+
dimensions,
|
|
13
|
+
register_context,
|
|
14
|
+
command_manager,
|
|
15
|
+
current_history
|
|
16
|
+
} = getContext(EDITOR_KEY);
|
|
17
|
+
const { can_undo } = command_manager;
|
|
10
18
|
const LayerManager = layer_manager();
|
|
11
|
-
|
|
19
|
+
const manager_current_layer = LayerManager.active_layer;
|
|
20
|
+
const layers = LayerManager.layers;
|
|
21
|
+
$:
|
|
22
|
+
current_layer.set($manager_current_layer);
|
|
12
23
|
register_context("layers", {
|
|
13
24
|
init_fn: () => {
|
|
14
25
|
new_layer();
|
|
@@ -18,7 +29,7 @@ register_context("layers", {
|
|
|
18
29
|
}
|
|
19
30
|
});
|
|
20
31
|
async function validate_layers() {
|
|
21
|
-
let invalid = layers.some(
|
|
32
|
+
let invalid = $layers.some(
|
|
22
33
|
(layer) => layer.composite.texture?.width != $dimensions[0] || layer.composite.texture?.height != $dimensions[1]
|
|
23
34
|
);
|
|
24
35
|
if (invalid) {
|
|
@@ -34,13 +45,16 @@ $:
|
|
|
34
45
|
async function new_layer() {
|
|
35
46
|
if (!$pixi)
|
|
36
47
|
return;
|
|
37
|
-
const
|
|
48
|
+
const new_layer2 = LayerManager.add_layer(
|
|
38
49
|
$pixi.layer_container,
|
|
39
50
|
$pixi.renderer,
|
|
40
51
|
...$dimensions
|
|
41
52
|
);
|
|
42
|
-
$
|
|
43
|
-
|
|
53
|
+
if ($can_undo || $layers.length > 0) {
|
|
54
|
+
command_manager.execute(new_layer2);
|
|
55
|
+
} else {
|
|
56
|
+
new_layer2.execute();
|
|
57
|
+
}
|
|
44
58
|
}
|
|
45
59
|
$:
|
|
46
60
|
render_layer_files(layer_files);
|
|
@@ -71,19 +85,19 @@ async function render_layer_files(_layer_files) {
|
|
|
71
85
|
})
|
|
72
86
|
);
|
|
73
87
|
LayerManager.reset();
|
|
74
|
-
let last_layer = null;
|
|
75
88
|
for (const blob of blobs.filter(is_not_null)) {
|
|
76
|
-
|
|
89
|
+
const new_layer2 = await LayerManager.add_layer_from_blob(
|
|
77
90
|
$pixi.layer_container,
|
|
78
91
|
$pixi.renderer,
|
|
79
92
|
blob,
|
|
80
93
|
$pixi.view
|
|
81
94
|
);
|
|
95
|
+
if ($can_undo && $layers.length === 0) {
|
|
96
|
+
command_manager.execute(new_layer2);
|
|
97
|
+
} else {
|
|
98
|
+
new_layer2.execute();
|
|
99
|
+
}
|
|
82
100
|
}
|
|
83
|
-
if (!last_layer)
|
|
84
|
-
return;
|
|
85
|
-
$current_layer = last_layer[0];
|
|
86
|
-
layers = last_layer[1];
|
|
87
101
|
}
|
|
88
102
|
onMount(async () => {
|
|
89
103
|
await tick();
|
|
@@ -102,13 +116,13 @@ onMount(async () => {
|
|
|
102
116
|
<button
|
|
103
117
|
aria-label="Show Layers"
|
|
104
118
|
on:click={() => (show_layers = !show_layers)}
|
|
105
|
-
><span class="icon"><Layers /></span> Layer {layers.findIndex(
|
|
119
|
+
><span class="icon"><Layers /></span> Layer {$layers.findIndex(
|
|
106
120
|
(l) => l === $current_layer
|
|
107
121
|
) + 1}
|
|
108
122
|
</button>
|
|
109
123
|
{#if show_layers}
|
|
110
124
|
<ul>
|
|
111
|
-
{#each layers as layer, i (i)}
|
|
125
|
+
{#each $layers as layer, i (i)}
|
|
112
126
|
<li>
|
|
113
127
|
<button
|
|
114
128
|
class:selected_layer={$current_layer === layer}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Container, type IRenderer, RenderTexture, Sprite, Filter } from "pixi.js";
|
|
2
|
+
import { type Command } from "../utils/commands";
|
|
3
|
+
import { type Writable } from "svelte/store";
|
|
2
4
|
/**
|
|
3
5
|
* GLSL Shader that takes two textures and erases the second texture from the first.
|
|
4
6
|
*/
|
|
@@ -35,7 +37,7 @@ interface LayerManager {
|
|
|
35
37
|
* @param width the width of the layer
|
|
36
38
|
* @param height the height of the layer
|
|
37
39
|
*/
|
|
38
|
-
add_layer(container: Container, renderer: IRenderer, width: number, height: number):
|
|
40
|
+
add_layer(container: Container, renderer: IRenderer, width: number, height: number, sprite?: Sprite): Command;
|
|
39
41
|
/**
|
|
40
42
|
* Swaps the layer with the layer above or below it.
|
|
41
43
|
* @param layer The index layer to swap.
|
|
@@ -58,7 +60,9 @@ interface LayerManager {
|
|
|
58
60
|
* @returns The layers.
|
|
59
61
|
*/
|
|
60
62
|
get_layers(): LayerScene[];
|
|
61
|
-
|
|
63
|
+
layers: Writable<LayerScene[]>;
|
|
64
|
+
active_layer: Writable<LayerScene | null>;
|
|
65
|
+
add_layer_from_blob(container: Container, renderer: IRenderer, blob: Blob, view: HTMLCanvasElement): Promise<Command>;
|
|
62
66
|
}
|
|
63
67
|
/**
|
|
64
68
|
* Creates a layer manager.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Container, RenderTexture, Texture, Sprite, Filter } from "pixi.js";
|
|
2
|
+
import {} from "../utils/commands";
|
|
3
|
+
import { get, writable } from "svelte/store";
|
|
2
4
|
/**
|
|
3
5
|
* GLSL Shader that takes two textures and erases the second texture from the first.
|
|
4
6
|
*/
|
|
@@ -39,36 +41,58 @@ export function layer_manager() {
|
|
|
39
41
|
let current_layer = 0;
|
|
40
42
|
let position = 0;
|
|
41
43
|
return {
|
|
42
|
-
add_layer: function (container, renderer, width, height) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
add_layer: function (container, renderer, width, height, sprite) {
|
|
45
|
+
let layer_container;
|
|
46
|
+
let layer_number;
|
|
47
|
+
let that = this;
|
|
48
|
+
return {
|
|
49
|
+
execute: function () {
|
|
50
|
+
layer_container = new Container();
|
|
51
|
+
position++;
|
|
52
|
+
layer_container.zIndex = position;
|
|
53
|
+
const composite_texture = RenderTexture.create({
|
|
54
|
+
width,
|
|
55
|
+
height
|
|
56
|
+
});
|
|
57
|
+
const composite = new Sprite(composite_texture);
|
|
58
|
+
layer_container.addChild(composite);
|
|
59
|
+
composite.zIndex = position;
|
|
60
|
+
const layer_scene = {
|
|
61
|
+
draw_texture: RenderTexture.create({
|
|
62
|
+
width,
|
|
63
|
+
height
|
|
64
|
+
}),
|
|
65
|
+
erase_texture: RenderTexture.create({
|
|
66
|
+
width,
|
|
67
|
+
height
|
|
68
|
+
}),
|
|
69
|
+
composite
|
|
70
|
+
};
|
|
71
|
+
const erase_filter = new Filter(undefined, erase_shader, {
|
|
72
|
+
uEraserTexture: layer_scene.erase_texture,
|
|
73
|
+
uDrawingTexture: layer_scene.draw_texture
|
|
74
|
+
});
|
|
75
|
+
composite.filters = [erase_filter];
|
|
76
|
+
container.addChild(layer_container);
|
|
77
|
+
_layers.push(layer_scene);
|
|
78
|
+
that.layers.update((s) => [...s, layer_scene]);
|
|
79
|
+
that.active_layer.set(layer_scene);
|
|
80
|
+
layer_number = get(that.layers).length - 1;
|
|
81
|
+
if (sprite) {
|
|
82
|
+
renderer.render(sprite, {
|
|
83
|
+
renderTexture: layer_scene.draw_texture
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
undo: function () {
|
|
88
|
+
container.removeChild(layer_container);
|
|
89
|
+
_layers = get(that.layers);
|
|
90
|
+
_layers = _layers.filter((_, i) => i !== layer_number);
|
|
91
|
+
that.layers.set(_layers);
|
|
92
|
+
const new_layer = _layers[layer_number - 1] || _layers[0] || null;
|
|
93
|
+
that.active_layer.set(new_layer);
|
|
94
|
+
}
|
|
63
95
|
};
|
|
64
|
-
const erase_filter = new Filter(undefined, erase_shader, {
|
|
65
|
-
uEraserTexture: layer_scene.erase_texture,
|
|
66
|
-
uDrawingTexture: layer_scene.draw_texture
|
|
67
|
-
});
|
|
68
|
-
composite.filters = [erase_filter];
|
|
69
|
-
container.addChild(layer_container);
|
|
70
|
-
_layers.push(layer_scene);
|
|
71
|
-
return [layer_scene, _layers];
|
|
72
96
|
},
|
|
73
97
|
swap_layers: function (layer, direction) {
|
|
74
98
|
if (direction === "up") {
|
|
@@ -80,7 +104,8 @@ export function layer_manager() {
|
|
|
80
104
|
return _layers[layer];
|
|
81
105
|
},
|
|
82
106
|
change_active_layer: function (layer) {
|
|
83
|
-
current_layer = layer;
|
|
107
|
+
// current_layer = layer;
|
|
108
|
+
this.active_layer.set(get(this.layers)[layer]);
|
|
84
109
|
return _layers[layer];
|
|
85
110
|
},
|
|
86
111
|
reset() {
|
|
@@ -92,6 +117,8 @@ export function layer_manager() {
|
|
|
92
117
|
_layers = [];
|
|
93
118
|
current_layer = 0;
|
|
94
119
|
position = 0;
|
|
120
|
+
this.active_layer.set(null);
|
|
121
|
+
this.layers.set([]);
|
|
95
122
|
},
|
|
96
123
|
async add_layer_from_blob(container, renderer, blob, view) {
|
|
97
124
|
const img = await createImageBitmap(blob);
|
|
@@ -101,15 +128,13 @@ export function layer_manager() {
|
|
|
101
128
|
sprite.zIndex = 0;
|
|
102
129
|
sprite.width = w;
|
|
103
130
|
sprite.height = h;
|
|
104
|
-
|
|
105
|
-
renderer.render(sprite, {
|
|
106
|
-
renderTexture: layer.draw_texture
|
|
107
|
-
});
|
|
108
|
-
return [layer, layers];
|
|
131
|
+
return this.add_layer(container, renderer, view.width, view.height, sprite);
|
|
109
132
|
},
|
|
110
133
|
get_layers() {
|
|
111
134
|
return _layers;
|
|
112
|
-
}
|
|
135
|
+
},
|
|
136
|
+
layers: writable([]),
|
|
137
|
+
active_layer: writable(null)
|
|
113
138
|
};
|
|
114
139
|
}
|
|
115
140
|
function resize_to_fit(inner_width, inner_height, outer_width, outer_height) {
|
|
@@ -13,8 +13,12 @@ export let default_color = void 0;
|
|
|
13
13
|
export let colors = void 0;
|
|
14
14
|
export let color_mode = void 0;
|
|
15
15
|
export let mode;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
let processed_colors = [];
|
|
17
|
+
let old_colors = [];
|
|
18
|
+
if (colors && JSON.stringify(old_colors) !== JSON.stringify(colors)) {
|
|
19
|
+
processed_colors = colors.map(process_color).filter((_, i) => i < 4);
|
|
20
|
+
old_colors = processed_colors;
|
|
21
|
+
}
|
|
18
22
|
let selected_color = process_color(default_color || "#000000");
|
|
19
23
|
$:
|
|
20
24
|
if (mode === "draw") {
|
|
@@ -65,11 +65,16 @@ export interface CommandManager {
|
|
|
65
65
|
* Observable store that you can subscribe to for updates
|
|
66
66
|
*/
|
|
67
67
|
readonly current_history: Writable<CommandNode>;
|
|
68
|
+
/**
|
|
69
|
+
* Hydrates the command manager with a full history
|
|
70
|
+
* @param full_history the full history to hydrate with
|
|
71
|
+
*/
|
|
72
|
+
hydrate(full_history: CommandNode): void;
|
|
68
73
|
}
|
|
69
74
|
/**
|
|
70
75
|
* Command node interface that is used to create the undo/redo history
|
|
71
76
|
*/
|
|
72
|
-
interface CommandNode {
|
|
77
|
+
export interface CommandNode {
|
|
73
78
|
/**
|
|
74
79
|
* Command that the node holds
|
|
75
80
|
*/
|
|
@@ -93,4 +98,3 @@ interface CommandNode {
|
|
|
93
98
|
* @returns a command manager
|
|
94
99
|
*/
|
|
95
100
|
export declare function command_manager(): CommandManager;
|
|
96
|
-
export {};
|
|
@@ -52,6 +52,14 @@ export function command_manager() {
|
|
|
52
52
|
can_redo.set(!!history.next);
|
|
53
53
|
current_history.set(history);
|
|
54
54
|
},
|
|
55
|
+
hydrate: function (full_history) {
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
while (full_history.next) {
|
|
58
|
+
this.execute(full_history.next.command);
|
|
59
|
+
full_history = full_history.next;
|
|
60
|
+
}
|
|
61
|
+
}, 1000);
|
|
62
|
+
},
|
|
55
63
|
can_undo,
|
|
56
64
|
can_redo,
|
|
57
65
|
current_history,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/imageeditor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"@types/tinycolor2": "^1.4.6",
|
|
11
11
|
"pixi.js": "^7.3.2",
|
|
12
12
|
"tinycolor2": "^1.6.0",
|
|
13
|
-
"@gradio/
|
|
14
|
-
"@gradio/
|
|
15
|
-
"@gradio/icons": "^0.
|
|
16
|
-
"@gradio/image": "^0.
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/
|
|
19
|
-
"@gradio/wasm": "^0.
|
|
20
|
-
"@gradio/
|
|
13
|
+
"@gradio/client": "^1.9.0",
|
|
14
|
+
"@gradio/atoms": "^0.13.0",
|
|
15
|
+
"@gradio/icons": "^0.9.0",
|
|
16
|
+
"@gradio/image": "^0.20.0",
|
|
17
|
+
"@gradio/upload": "^0.14.4",
|
|
18
|
+
"@gradio/statustracker": "^0.10.0",
|
|
19
|
+
"@gradio/wasm": "^0.16.0",
|
|
20
|
+
"@gradio/utils": "^0.10.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@gradio/preview": "^0.13.0"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Writable, Readable } from "svelte/store";
|
|
3
3
|
import type { Spring } from "svelte/motion";
|
|
4
4
|
import { type PixiApp } from "./utils/pixi";
|
|
5
|
-
import { type CommandManager } from "./utils/commands";
|
|
5
|
+
import { type CommandManager, type CommandNode } from "./utils/commands";
|
|
6
6
|
|
|
7
7
|
export const EDITOR_KEY = Symbol("editor");
|
|
8
8
|
export type context_type = "bg" | "layers" | "crop" | "draw" | "erase";
|
|
@@ -71,10 +71,12 @@
|
|
|
71
71
|
clear?: never;
|
|
72
72
|
save: void;
|
|
73
73
|
change: void;
|
|
74
|
+
history: CommandManager["current_history"];
|
|
74
75
|
}>();
|
|
75
76
|
export let crop_constraint = false;
|
|
76
77
|
export let canvas_size: [number, number] | undefined;
|
|
77
78
|
export let parent_height: number;
|
|
79
|
+
export let full_history: CommandNode | null = null;
|
|
78
80
|
|
|
79
81
|
$: orig_canvas_size = canvas_size;
|
|
80
82
|
|
|
@@ -114,7 +116,15 @@
|
|
|
114
116
|
|
|
115
117
|
const { can_redo, can_undo, current_history } = CommandManager;
|
|
116
118
|
|
|
119
|
+
function get_start_history(history: any): any {
|
|
120
|
+
if (history.previous) {
|
|
121
|
+
return get_start_history(history.previous);
|
|
122
|
+
}
|
|
123
|
+
return history;
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
$: $current_history.previous, dispatch("change");
|
|
127
|
+
$: dispatch("history", get_start_history($current_history));
|
|
118
128
|
|
|
119
129
|
$: {
|
|
120
130
|
history = !!$current_history.previous || $active_tool !== "bg";
|
|
@@ -316,6 +326,12 @@
|
|
|
316
326
|
|
|
317
327
|
resize(...$dimensions);
|
|
318
328
|
|
|
329
|
+
tick().then(() => {
|
|
330
|
+
if (full_history) {
|
|
331
|
+
CommandManager.hydrate(full_history);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
319
335
|
return () => {
|
|
320
336
|
$pixi?.destroy();
|
|
321
337
|
resizer.disconnect();
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { createEventDispatcher } from "svelte";
|
|
17
17
|
import { type I18nFormatter } from "@gradio/utils";
|
|
18
18
|
import { prepare_files, type FileData, type Client } from "@gradio/client";
|
|
19
|
-
|
|
19
|
+
import { type CommandNode } from "./utils/commands";
|
|
20
20
|
import ImageEditor from "./ImageEditor.svelte";
|
|
21
21
|
import Layers from "./layers/Layers.svelte";
|
|
22
22
|
import { type Brush as IBrush } from "./tools/Brush.svelte";
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
export let dragging: boolean;
|
|
58
58
|
export let placeholder: string | undefined = undefined;
|
|
59
59
|
export let height = 450;
|
|
60
|
+
export let full_history: CommandNode | null = null;
|
|
60
61
|
|
|
61
62
|
const dispatch = createEventDispatcher<{
|
|
62
63
|
clear?: never;
|
|
@@ -215,6 +216,7 @@
|
|
|
215
216
|
label={label || i18n("image.image")}
|
|
216
217
|
/>
|
|
217
218
|
<ImageEditor
|
|
219
|
+
on:history
|
|
218
220
|
{canvas_size}
|
|
219
221
|
crop_size={Array.isArray(crop_size) ? crop_size : undefined}
|
|
220
222
|
bind:this={editor}
|
|
@@ -228,6 +230,7 @@
|
|
|
228
230
|
bind:bg
|
|
229
231
|
{sources}
|
|
230
232
|
crop_constraint={!!crop_constraint}
|
|
233
|
+
{full_history}
|
|
231
234
|
>
|
|
232
235
|
<Tools {i18n}>
|
|
233
236
|
<Layers layer_files={value?.layers || null} enable_layers={layers} />
|
|
@@ -12,11 +12,22 @@
|
|
|
12
12
|
export let layer_files: (FileData | null)[] | null = [];
|
|
13
13
|
export let enable_layers = true;
|
|
14
14
|
|
|
15
|
-
const {
|
|
16
|
-
|
|
15
|
+
const {
|
|
16
|
+
pixi,
|
|
17
|
+
current_layer,
|
|
18
|
+
dimensions,
|
|
19
|
+
register_context,
|
|
20
|
+
command_manager,
|
|
21
|
+
current_history
|
|
22
|
+
} = getContext<EditorContext>(EDITOR_KEY);
|
|
23
|
+
|
|
24
|
+
const { can_undo } = command_manager;
|
|
17
25
|
|
|
18
26
|
const LayerManager = layer_manager();
|
|
19
|
-
|
|
27
|
+
const manager_current_layer = LayerManager.active_layer;
|
|
28
|
+
const layers = LayerManager.layers;
|
|
29
|
+
|
|
30
|
+
$: current_layer.set($manager_current_layer);
|
|
20
31
|
|
|
21
32
|
register_context("layers", {
|
|
22
33
|
init_fn: () => {
|
|
@@ -28,7 +39,7 @@
|
|
|
28
39
|
});
|
|
29
40
|
|
|
30
41
|
async function validate_layers(): Promise<void> {
|
|
31
|
-
let invalid = layers.some(
|
|
42
|
+
let invalid = $layers.some(
|
|
32
43
|
(layer) =>
|
|
33
44
|
layer.composite.texture?.width != $dimensions[0] ||
|
|
34
45
|
layer.composite.texture?.height != $dimensions[1]
|
|
@@ -44,14 +55,17 @@
|
|
|
44
55
|
async function new_layer(): Promise<void> {
|
|
45
56
|
if (!$pixi) return;
|
|
46
57
|
|
|
47
|
-
const
|
|
58
|
+
const new_layer = LayerManager.add_layer(
|
|
48
59
|
$pixi.layer_container,
|
|
49
60
|
$pixi.renderer,
|
|
50
61
|
...$dimensions
|
|
51
62
|
);
|
|
52
63
|
|
|
53
|
-
$
|
|
54
|
-
|
|
64
|
+
if ($can_undo || $layers.length > 0) {
|
|
65
|
+
command_manager.execute(new_layer);
|
|
66
|
+
} else {
|
|
67
|
+
new_layer.execute();
|
|
68
|
+
}
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
$: render_layer_files(layer_files);
|
|
@@ -88,20 +102,20 @@
|
|
|
88
102
|
|
|
89
103
|
LayerManager.reset();
|
|
90
104
|
|
|
91
|
-
let last_layer: [LayerScene, LayerScene[]] | null = null;
|
|
92
105
|
for (const blob of blobs.filter(is_not_null)) {
|
|
93
|
-
|
|
106
|
+
const new_layer = await LayerManager.add_layer_from_blob(
|
|
94
107
|
$pixi.layer_container,
|
|
95
108
|
$pixi.renderer,
|
|
96
109
|
blob,
|
|
97
110
|
$pixi.view
|
|
98
111
|
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!last_layer) return;
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
if ($can_undo && $layers.length === 0) {
|
|
114
|
+
command_manager.execute(new_layer);
|
|
115
|
+
} else {
|
|
116
|
+
new_layer.execute();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
onMount(async () => {
|
|
@@ -121,13 +135,13 @@
|
|
|
121
135
|
<button
|
|
122
136
|
aria-label="Show Layers"
|
|
123
137
|
on:click={() => (show_layers = !show_layers)}
|
|
124
|
-
><span class="icon"><Layers /></span> Layer {layers.findIndex(
|
|
138
|
+
><span class="icon"><Layers /></span> Layer {$layers.findIndex(
|
|
125
139
|
(l) => l === $current_layer
|
|
126
140
|
) + 1}
|
|
127
141
|
</button>
|
|
128
142
|
{#if show_layers}
|
|
129
143
|
<ul>
|
|
130
|
-
{#each layers as layer, i (i)}
|
|
144
|
+
{#each $layers as layer, i (i)}
|
|
131
145
|
<li>
|
|
132
146
|
<button
|
|
133
147
|
class:selected_layer={$current_layer === layer}
|
package/shared/layers/utils.ts
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
Filter
|
|
9
9
|
} from "pixi.js";
|
|
10
10
|
|
|
11
|
+
import { type Command } from "../utils/commands";
|
|
12
|
+
import { get, writable, type Writable } from "svelte/store";
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* GLSL Shader that takes two textures and erases the second texture from the first.
|
|
13
16
|
*/
|
|
@@ -65,8 +68,9 @@ interface LayerManager {
|
|
|
65
68
|
container: Container,
|
|
66
69
|
renderer: IRenderer,
|
|
67
70
|
width: number,
|
|
68
|
-
height: number
|
|
69
|
-
|
|
71
|
+
height: number,
|
|
72
|
+
sprite?: Sprite
|
|
73
|
+
): Command;
|
|
70
74
|
/**
|
|
71
75
|
* Swaps the layer with the layer above or below it.
|
|
72
76
|
* @param layer The index layer to swap.
|
|
@@ -89,13 +93,15 @@ interface LayerManager {
|
|
|
89
93
|
* @returns The layers.
|
|
90
94
|
*/
|
|
91
95
|
get_layers(): LayerScene[];
|
|
96
|
+
layers: Writable<LayerScene[]>;
|
|
97
|
+
active_layer: Writable<LayerScene | null>;
|
|
92
98
|
|
|
93
99
|
add_layer_from_blob(
|
|
94
100
|
container: Container,
|
|
95
101
|
renderer: IRenderer,
|
|
96
102
|
blob: Blob,
|
|
97
103
|
view: HTMLCanvasElement
|
|
98
|
-
): Promise<
|
|
104
|
+
): Promise<Command>;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
/**
|
|
@@ -126,47 +132,72 @@ export function layer_manager(): LayerManager {
|
|
|
126
132
|
container: Container,
|
|
127
133
|
renderer: IRenderer,
|
|
128
134
|
width: number,
|
|
129
|
-
height: number
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
135
|
+
height: number,
|
|
136
|
+
sprite?: Sprite
|
|
137
|
+
): Command {
|
|
138
|
+
let layer_container: Container & DisplayObject;
|
|
139
|
+
let layer_number: number;
|
|
140
|
+
let that = this;
|
|
141
|
+
return {
|
|
142
|
+
execute: function () {
|
|
143
|
+
layer_container = new Container() as Container & DisplayObject;
|
|
144
|
+
position++;
|
|
145
|
+
layer_container.zIndex = position;
|
|
146
|
+
|
|
147
|
+
const composite_texture = RenderTexture.create({
|
|
148
|
+
width,
|
|
149
|
+
height
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const composite = new Sprite(composite_texture) as Sprite &
|
|
153
|
+
DisplayObject;
|
|
154
|
+
|
|
155
|
+
layer_container.addChild(composite);
|
|
156
|
+
|
|
157
|
+
composite.zIndex = position;
|
|
158
|
+
|
|
159
|
+
const layer_scene: LayerScene = {
|
|
160
|
+
draw_texture: RenderTexture.create({
|
|
161
|
+
width,
|
|
162
|
+
height
|
|
163
|
+
}),
|
|
164
|
+
erase_texture: RenderTexture.create({
|
|
165
|
+
width,
|
|
166
|
+
height
|
|
167
|
+
}),
|
|
168
|
+
composite
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const erase_filter = new Filter(undefined, erase_shader, {
|
|
172
|
+
uEraserTexture: layer_scene.erase_texture,
|
|
173
|
+
uDrawingTexture: layer_scene.draw_texture
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
composite.filters = [erase_filter];
|
|
177
|
+
|
|
178
|
+
container.addChild(layer_container);
|
|
179
|
+
_layers.push(layer_scene);
|
|
180
|
+
that.layers.update((s) => [...s, layer_scene]);
|
|
181
|
+
that.active_layer.set(layer_scene);
|
|
182
|
+
|
|
183
|
+
layer_number = get(that.layers).length - 1;
|
|
184
|
+
|
|
185
|
+
if (sprite) {
|
|
186
|
+
renderer.render(sprite, {
|
|
187
|
+
renderTexture: layer_scene.draw_texture
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
undo: function () {
|
|
192
|
+
container.removeChild(layer_container);
|
|
193
|
+
|
|
194
|
+
_layers = get(that.layers);
|
|
195
|
+
_layers = _layers.filter((_, i) => i !== layer_number);
|
|
196
|
+
that.layers.set(_layers);
|
|
197
|
+
const new_layer = _layers[layer_number - 1] || _layers[0] || null;
|
|
198
|
+
that.active_layer.set(new_layer);
|
|
199
|
+
}
|
|
156
200
|
};
|
|
157
|
-
|
|
158
|
-
const erase_filter = new Filter(undefined, erase_shader, {
|
|
159
|
-
uEraserTexture: layer_scene.erase_texture,
|
|
160
|
-
uDrawingTexture: layer_scene.draw_texture
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
composite.filters = [erase_filter];
|
|
164
|
-
|
|
165
|
-
container.addChild(layer_container);
|
|
166
|
-
|
|
167
|
-
_layers.push(layer_scene);
|
|
168
|
-
|
|
169
|
-
return [layer_scene, _layers];
|
|
170
201
|
},
|
|
171
202
|
|
|
172
203
|
swap_layers: function (
|
|
@@ -182,7 +213,8 @@ export function layer_manager(): LayerManager {
|
|
|
182
213
|
},
|
|
183
214
|
|
|
184
215
|
change_active_layer: function (layer: number): LayerScene {
|
|
185
|
-
current_layer = layer;
|
|
216
|
+
// current_layer = layer;
|
|
217
|
+
this.active_layer.set(get(this.layers)[layer]);
|
|
186
218
|
return _layers[layer];
|
|
187
219
|
},
|
|
188
220
|
reset() {
|
|
@@ -194,13 +226,15 @@ export function layer_manager(): LayerManager {
|
|
|
194
226
|
_layers = [];
|
|
195
227
|
current_layer = 0;
|
|
196
228
|
position = 0;
|
|
229
|
+
this.active_layer.set(null);
|
|
230
|
+
this.layers.set([]);
|
|
197
231
|
},
|
|
198
232
|
async add_layer_from_blob(
|
|
199
233
|
container: Container,
|
|
200
234
|
renderer: IRenderer,
|
|
201
235
|
blob: Blob,
|
|
202
236
|
view: HTMLCanvasElement
|
|
203
|
-
) {
|
|
237
|
+
): Promise<Command> {
|
|
204
238
|
const img = await createImageBitmap(blob);
|
|
205
239
|
const bitmap_texture = Texture.from(img);
|
|
206
240
|
|
|
@@ -217,22 +251,19 @@ export function layer_manager(): LayerManager {
|
|
|
217
251
|
sprite.width = w;
|
|
218
252
|
sprite.height = h;
|
|
219
253
|
|
|
220
|
-
|
|
254
|
+
return this.add_layer(
|
|
221
255
|
container,
|
|
222
256
|
renderer,
|
|
223
257
|
view.width,
|
|
224
|
-
view.height
|
|
258
|
+
view.height,
|
|
259
|
+
sprite
|
|
225
260
|
);
|
|
226
|
-
|
|
227
|
-
renderer.render(sprite, {
|
|
228
|
-
renderTexture: layer.draw_texture
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
return [layer, layers];
|
|
232
261
|
},
|
|
233
262
|
get_layers() {
|
|
234
263
|
return _layers;
|
|
235
|
-
}
|
|
264
|
+
},
|
|
265
|
+
layers: writable([]),
|
|
266
|
+
active_layer: writable(null)
|
|
236
267
|
};
|
|
237
268
|
}
|
|
238
269
|
|
|
@@ -43,9 +43,12 @@
|
|
|
43
43
|
export let color_mode: Brush["color_mode"] | undefined = undefined;
|
|
44
44
|
export let mode: "erase" | "draw";
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
let processed_colors: string[] = [];
|
|
47
|
+
let old_colors: string[] = [];
|
|
48
|
+
if (colors && JSON.stringify(old_colors) !== JSON.stringify(colors)) {
|
|
49
|
+
processed_colors = colors.map(process_color).filter((_, i) => i < 4);
|
|
50
|
+
old_colors = processed_colors;
|
|
51
|
+
}
|
|
49
52
|
|
|
50
53
|
let selected_color = process_color(default_color || "#000000");
|
|
51
54
|
|
package/shared/utils/commands.ts
CHANGED
|
@@ -67,12 +67,17 @@ export interface CommandManager {
|
|
|
67
67
|
* Observable store that you can subscribe to for updates
|
|
68
68
|
*/
|
|
69
69
|
readonly current_history: Writable<CommandNode>;
|
|
70
|
+
/**
|
|
71
|
+
* Hydrates the command manager with a full history
|
|
72
|
+
* @param full_history the full history to hydrate with
|
|
73
|
+
*/
|
|
74
|
+
hydrate(full_history: CommandNode): void;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
73
78
|
* Command node interface that is used to create the undo/redo history
|
|
74
79
|
*/
|
|
75
|
-
interface CommandNode {
|
|
80
|
+
export interface CommandNode {
|
|
76
81
|
/**
|
|
77
82
|
* Command that the node holds
|
|
78
83
|
*/
|
|
@@ -147,7 +152,14 @@ export function command_manager(): CommandManager {
|
|
|
147
152
|
can_redo.set(!!history.next);
|
|
148
153
|
current_history.set(history);
|
|
149
154
|
},
|
|
150
|
-
|
|
155
|
+
hydrate: function (full_history: CommandNode) {
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
while (full_history.next) {
|
|
158
|
+
this.execute(full_history.next.command!);
|
|
159
|
+
full_history = full_history.next;
|
|
160
|
+
}
|
|
161
|
+
}, 1000);
|
|
162
|
+
},
|
|
151
163
|
can_undo,
|
|
152
164
|
can_redo,
|
|
153
165
|
current_history,
|