react-x11 2.15.2 → 2.15.3
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/package.json +2 -1
- package/src/application.js +25 -1
- package/src/capabilities.js +324 -0
- package/src/cocoa/app.js +13 -0
- package/src/cocoa/context2d.js +116 -6
- package/src/dbusmenuexport.js +243 -0
- package/src/desktopcapabilityhooks.js +137 -0
- package/src/globalmenu.js +3 -205
- package/src/imagesource.js +15 -2
- package/src/index.d.ts +1 -0
- package/src/index.js +8 -2
- package/src/launcher.js +235 -32
- package/src/launcherhooks.js +47 -28
- package/src/nodes/image.js +2 -1
- package/src/statusnotifier.js +605 -0
- package/src/trayhooks.js +177 -29
- package/src/types/capabilities.d.ts +127 -0
- package/src/types/launcher.d.ts +50 -4
- package/src/types/tray.d.ts +52 -6
package/src/launcher.js
CHANGED
|
@@ -1,35 +1,55 @@
|
|
|
1
|
-
// The launcher's view of the app: the badge on its icon
|
|
1
|
+
// The launcher's view of the app: the badge on its icon, a progress bar
|
|
2
|
+
// across it, an attention flag, and the menu behind a right-click.
|
|
2
3
|
//
|
|
3
4
|
// A count on the Dock tile, a dot on the taskbar entry — the one thing an
|
|
4
5
|
// app says to the desktop that the user reads without opening it. Two
|
|
5
|
-
// desktops, two mechanisms, one call:
|
|
6
|
+
// desktops, two mechanisms, one call each:
|
|
6
7
|
//
|
|
7
|
-
// 1. **the app's own tile** — the cocoa backend's `NSDockTile
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// 1. **the app's own tile** — the cocoa backend's `NSDockTile`, reached
|
|
9
|
+
// through the app object (`setDockBadge`, `setDockMenu`, src/cocoa/
|
|
10
|
+
// app.js).
|
|
10
11
|
// 2. **`com.canonical.Unity.LauncherEntry`** over the session bus — the
|
|
11
|
-
// protocol Unity defined and the KDE, elementary
|
|
12
|
-
// launchers still listen for. One signal,
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// protocol Unity defined and the KDE, elementary, Cairo-Dock and
|
|
13
|
+
// Dash-to-Dock launchers still listen for. One signal,
|
|
14
|
+
// `Update(app_uri, a{sv})`, attributed to the app by
|
|
15
|
+
// `application://<id>.desktop` — which is why it needs the identity
|
|
15
16
|
// `registerApplication({ appId })` established: with no app id there is
|
|
16
|
-
// nothing for a launcher to pin
|
|
17
|
+
// nothing for a launcher to pin anything to, and the calls resolve
|
|
17
18
|
// false rather than guessing.
|
|
18
19
|
//
|
|
19
|
-
// The
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
20
|
+
// The badge takes the stricter of the two shapes: a **count**. A number shows
|
|
21
|
+
// on both; a string shows on macOS and is a visible count of nothing on Linux
|
|
22
|
+
// (the protocol has no text field), so pass a string only where the Mac is
|
|
23
|
+
// the audience. `0`, `null` and `''` all clear it, because a badge of zero is
|
|
24
|
+
// the badge nobody wanted.
|
|
24
25
|
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
26
|
+
// ## The quicklist is not "desktop actions"
|
|
27
|
+
//
|
|
28
|
+
// This module used to say the freedesktop counterpart of the Dock menu was
|
|
29
|
+
// `Actions=` in the `.desktop` file — an install step, not runtime code — and
|
|
30
|
+
// that the hook was therefore cocoa-only. That was half the story and the
|
|
31
|
+
// wrong half. `LauncherEntry` carries a **`quicklist`** property: an object
|
|
32
|
+
// path to a `com.canonical.dbusmenu` tree, which is the same menu protocol
|
|
33
|
+
// the global menu and the tray already speak, built from the same items. It
|
|
34
|
+
// is runtime, it follows state, and Dash-to-Dock/ubuntu-dock, Plank and the
|
|
35
|
+
// Unity heritage launchers all render it.
|
|
36
|
+
//
|
|
37
|
+
// So `useDockMenu()` has a Linux rung after all, and the three menus an app
|
|
38
|
+
// puts on the desktop — panel, tray, launcher — are now one authoring model
|
|
39
|
+
// on both backends. `.desktop` actions are still the right place for entries
|
|
40
|
+
// that must work *while the app is not running*; they are a different feature
|
|
41
|
+
// wearing a similar hat.
|
|
42
|
+
//
|
|
43
|
+
// Nothing here imports react: `useBadge`/`useDockMenu` (launcherhooks.js) are
|
|
44
|
+
// the hooks, and these are the functions under them, callable from host-side
|
|
45
|
+
// code with no tree. The entry on the bus is held for as long as anything is
|
|
46
|
+
// shown and released when the last of it is cleared — a held bus ref is a
|
|
47
|
+
// ref()'d socket, and an app that cleared its badge on the way out should be
|
|
48
|
+
// free to exit.
|
|
30
49
|
|
|
31
50
|
import { currentRegistration } from './application.js';
|
|
32
51
|
import { loadTransport, sessionBus } from './bus.js';
|
|
52
|
+
import { DbusMenuExport } from './dbusmenuexport.js';
|
|
33
53
|
import { liveApps } from './trace-registry.js';
|
|
34
54
|
|
|
35
55
|
export const LAUNCHER_ENTRY_IFACE = 'com.canonical.Unity.LauncherEntry';
|
|
@@ -78,6 +98,29 @@ function soleApp() {
|
|
|
78
98
|
/** The one exported entry per process — an app has one icon. */
|
|
79
99
|
let entry = null;
|
|
80
100
|
|
|
101
|
+
/**
|
|
102
|
+
* What the launcher currently believes, so an `Update` can carry the whole of
|
|
103
|
+
* it. The protocol's dict is a *patch* and every launcher merges it, but
|
|
104
|
+
* sending the full state makes the call idempotent and means a launcher that
|
|
105
|
+
* restarted and missed a patch is corrected by the next one of any kind.
|
|
106
|
+
*/
|
|
107
|
+
function blankState() {
|
|
108
|
+
return {
|
|
109
|
+
count: 0,
|
|
110
|
+
countVisible: false,
|
|
111
|
+
progress: 0,
|
|
112
|
+
progressVisible: false,
|
|
113
|
+
urgent: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Is anything still being shown? When not, the entry can go. */
|
|
118
|
+
function stateIsEmpty(state, hasMenu) {
|
|
119
|
+
return (
|
|
120
|
+
!hasMenu && !state.countVisible && !state.progressVisible && !state.urgent
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
81
124
|
async function launcherEntry(appUri) {
|
|
82
125
|
if (entry && entry.appUri === appUri) return entry;
|
|
83
126
|
if (entry) await releaseEntry();
|
|
@@ -104,7 +147,17 @@ async function launcherEntry(appUri) {
|
|
|
104
147
|
await ref.release();
|
|
105
148
|
return null;
|
|
106
149
|
}
|
|
107
|
-
entry = {
|
|
150
|
+
entry = {
|
|
151
|
+
appUri,
|
|
152
|
+
ref,
|
|
153
|
+
dbus,
|
|
154
|
+
iface,
|
|
155
|
+
registration,
|
|
156
|
+
state: blankState(),
|
|
157
|
+
menu: null,
|
|
158
|
+
menuRegistration: null,
|
|
159
|
+
menuPath: `${launcherEntryPath(appUri)}/Menu`,
|
|
160
|
+
};
|
|
108
161
|
return entry;
|
|
109
162
|
}
|
|
110
163
|
|
|
@@ -112,10 +165,53 @@ async function releaseEntry() {
|
|
|
112
165
|
const held = entry;
|
|
113
166
|
entry = null;
|
|
114
167
|
if (!held) return;
|
|
168
|
+
await held.menuRegistration?.remove?.().catch(() => {});
|
|
115
169
|
await held.registration?.remove?.().catch(() => {});
|
|
116
170
|
await held.ref.release();
|
|
117
171
|
}
|
|
118
172
|
|
|
173
|
+
/** Emit the whole of the current state under the app's URI. */
|
|
174
|
+
function emit(held) {
|
|
175
|
+
const V = held.dbus.Variant;
|
|
176
|
+
const props = {
|
|
177
|
+
count: new V('x', held.state.count),
|
|
178
|
+
'count-visible': new V('b', held.state.countVisible),
|
|
179
|
+
progress: new V('d', held.state.progress),
|
|
180
|
+
'progress-visible': new V('b', held.state.progressVisible),
|
|
181
|
+
urgent: new V('b', held.state.urgent),
|
|
182
|
+
};
|
|
183
|
+
// Only advertised when there is one: a path to an object that is not
|
|
184
|
+
// exported makes a launcher build a menu client against nothing, and
|
|
185
|
+
// Dash-to-Dock keeps the stale client until the path *changes*.
|
|
186
|
+
if (held.menuRegistration) props.quicklist = new V('o', held.menuPath);
|
|
187
|
+
held.iface.emit.Update(held.appUri, props);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The shared half of every setter: find the identity, take or make the entry,
|
|
192
|
+
* let `mutate` move the state, emit, and drop the entry if nothing is left.
|
|
193
|
+
*
|
|
194
|
+
* `mutate` returns false to mean "nothing changed and nothing was being
|
|
195
|
+
* shown" — the case where clearing something that was already clear must not
|
|
196
|
+
* dial the bus.
|
|
197
|
+
*/
|
|
198
|
+
async function withEntry(mutate, { clearing }) {
|
|
199
|
+
const appId = currentRegistration()?.appId;
|
|
200
|
+
if (!appId) {
|
|
201
|
+
if (clearing && entry) await releaseEntry();
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (clearing && !entry) return false; // nothing shown, nothing to clear
|
|
205
|
+
const held = await launcherEntry(launcherAppUri(appId));
|
|
206
|
+
if (!held) return false;
|
|
207
|
+
mutate(held.state);
|
|
208
|
+
emit(held);
|
|
209
|
+
if (stateIsEmpty(held.state, Boolean(held.menuRegistration))) {
|
|
210
|
+
await releaseEntry();
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
119
215
|
/**
|
|
120
216
|
* Show `value` on the app's icon, or clear it.
|
|
121
217
|
*
|
|
@@ -144,23 +240,130 @@ export async function setBadge(value, { app } = {}) {
|
|
|
144
240
|
}
|
|
145
241
|
|
|
146
242
|
// Rung 2: the launcher protocol, which needs an identity to badge.
|
|
243
|
+
const count =
|
|
244
|
+
typeof value === 'number' && Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
245
|
+
return withEntry(
|
|
246
|
+
(state) => {
|
|
247
|
+
state.count = label === null ? 0 : count;
|
|
248
|
+
state.countVisible = label !== null;
|
|
249
|
+
},
|
|
250
|
+
{ clearing: label === null },
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A progress bar across the app's icon: `0`…`1`, or `null` to clear it.
|
|
256
|
+
*
|
|
257
|
+
* ```js
|
|
258
|
+
* await setProgress(downloaded / total);
|
|
259
|
+
* await setProgress(null); // done
|
|
260
|
+
* ```
|
|
261
|
+
*
|
|
262
|
+
* The launcher protocol's `progress`, which ubuntu-dock, Plank and the
|
|
263
|
+
* Unity-heritage launchers draw across the tile. **Inert on the cocoa
|
|
264
|
+
* backend**: `NSDockTile` has no progress of its own — an app that wants one
|
|
265
|
+
* on a Mac draws it into a custom tile view, which is app-side art rather
|
|
266
|
+
* than a call. Resolves to whether a launcher was told.
|
|
267
|
+
*/
|
|
268
|
+
export async function setProgress(value, { app } = {}) {
|
|
269
|
+
const target = app ?? soleApp();
|
|
270
|
+
// No cocoa rung to try: the Dock has no progress. Left deliberately without
|
|
271
|
+
// a `setDockProgress` probe so that adding one to the bridge later is the
|
|
272
|
+
// only change needed here.
|
|
273
|
+
void target;
|
|
274
|
+
const clearing =
|
|
275
|
+
value == null || value === false || !Number.isFinite(Number(value));
|
|
276
|
+
const clamped = clearing ? 0 : Math.min(1, Math.max(0, Number(value)));
|
|
277
|
+
return withEntry(
|
|
278
|
+
(state) => {
|
|
279
|
+
state.progress = clamped;
|
|
280
|
+
state.progressVisible = !clearing;
|
|
281
|
+
},
|
|
282
|
+
{ clearing },
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Ask the launcher for the user's attention, or stop asking.
|
|
288
|
+
*
|
|
289
|
+
* The launcher protocol's `urgent`, which is the tile bouncing or glowing.
|
|
290
|
+
* Distinct from `<window states={['demands_attention']}>`, which is the
|
|
291
|
+
* *window's* urgency hint and what a taskbar blinks: this one marks the app's
|
|
292
|
+
* icon in the launcher whether or not any window is open. On the cocoa
|
|
293
|
+
* backend the window state is the mechanism and this is inert.
|
|
294
|
+
*/
|
|
295
|
+
export async function setUrgent(urgent, { app } = {}) {
|
|
296
|
+
void (app ?? soleApp());
|
|
297
|
+
return withEntry((state) => void (state.urgent = Boolean(urgent)), {
|
|
298
|
+
clearing: !urgent,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The menu behind a right-click on the app's launcher icon — the quicklist.
|
|
304
|
+
*
|
|
305
|
+
* Takes `MenuBar`'s item vocabulary and exports it as a `com.canonical.dbusmenu`
|
|
306
|
+
* tree, exactly as the tray and the global menu do. `null` takes it down.
|
|
307
|
+
* Resolves to whether a launcher was told.
|
|
308
|
+
*
|
|
309
|
+
* The menu is exported **before** the property naming it is emitted, so a
|
|
310
|
+
* launcher that builds its client the instant it sees `quicklist` finds an
|
|
311
|
+
* object there.
|
|
312
|
+
*/
|
|
313
|
+
export async function setQuicklist(items, { app } = {}) {
|
|
314
|
+
const target = app ?? soleApp();
|
|
315
|
+
|
|
316
|
+
// Rung 1: the app's own tile menu.
|
|
317
|
+
if (typeof target?.setDockMenu === 'function') {
|
|
318
|
+
target.setDockMenu(items ?? null);
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
|
|
147
322
|
const appId = currentRegistration()?.appId;
|
|
148
323
|
if (!appId) {
|
|
149
|
-
if (
|
|
324
|
+
if (!items && entry) await releaseEntry();
|
|
150
325
|
return false;
|
|
151
326
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
327
|
+
if (!items) {
|
|
328
|
+
// Taking the menu down: drop the export, then say so.
|
|
329
|
+
if (!entry?.menuRegistration) return false;
|
|
330
|
+
const held = entry;
|
|
331
|
+
const registration = held.menuRegistration;
|
|
332
|
+
held.menuRegistration = null;
|
|
333
|
+
held.menu = null;
|
|
334
|
+
await registration?.remove?.().catch(() => {});
|
|
335
|
+
// `quicklist` is omitted rather than set empty — there is no "no menu"
|
|
336
|
+
// value in the protocol, and an object path to nothing is worse than a
|
|
337
|
+
// property a launcher never saw.
|
|
338
|
+
emit(held);
|
|
339
|
+
if (stateIsEmpty(held.state, false)) await releaseEntry();
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const held = await launcherEntry(launcherAppUri(appId));
|
|
155
344
|
if (!held) return false;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
345
|
+
|
|
346
|
+
if (held.menu) {
|
|
347
|
+
// Already exported: this is a re-render, and the tree diffs itself.
|
|
348
|
+
held.menu.update(items);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const menu = new DbusMenuExport({
|
|
353
|
+
getMenus: () => items,
|
|
354
|
+
onSelect: (item) => item.onSelect?.(),
|
|
355
|
+
onAboutToShow: (item) => item.onAboutToShow?.(),
|
|
162
356
|
});
|
|
163
|
-
|
|
357
|
+
const iface = menu.defineMenu(held.dbus);
|
|
358
|
+
try {
|
|
359
|
+
held.menuRegistration = await held.ref.bus.export(held.menuPath, iface);
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
menu.iface = iface;
|
|
364
|
+
menu.exported = true;
|
|
365
|
+
held.menu = menu;
|
|
366
|
+
emit(held);
|
|
164
367
|
return true;
|
|
165
368
|
}
|
|
166
369
|
|
package/src/launcherhooks.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
// `useBadge()` and `useDockMenu()` — the launcher's view of
|
|
2
|
-
// things a component declares rather than manages.
|
|
1
|
+
// `useBadge()`, `useProgress()` and `useDockMenu()` — the launcher's view of
|
|
2
|
+
// the app, as things a component declares rather than manages.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// All three now have a rung on both backends except progress, which has one
|
|
5
|
+
// only on Linux (`NSDockTile` has no progress bar; see `setProgress`). Where
|
|
6
|
+
// a hook has no mechanism it is inert and silent: a mark on an icon is not a
|
|
7
|
+
// feature an app should branch on, and a development warning for every
|
|
8
|
+
// desktop that lacks one is a warning nobody can act on.
|
|
9
|
+
//
|
|
10
|
+
// The one that changed shape is `useDockMenu`. It used to be cocoa-only on
|
|
11
|
+
// the grounds that the freedesktop counterpart was an install step — see
|
|
12
|
+
// `launcher.js`, where that reasoning is corrected: the launcher protocol
|
|
13
|
+
// carries a `quicklist` dbusmenu, so the Dock menu is runtime code on both.
|
|
10
14
|
|
|
11
15
|
import { useEffect, useRef } from 'react';
|
|
12
16
|
|
|
13
17
|
import { useAppOrNull } from './appcontext.js';
|
|
14
|
-
import { setBadge } from './launcher.js';
|
|
18
|
+
import { setBadge, setProgress, setQuicklist } from './launcher.js';
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Show `value` on the app's icon while this component is mounted, and clear
|
|
@@ -37,12 +41,31 @@ export function useBadge(value) {
|
|
|
37
41
|
}, [app]);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
/**
|
|
45
|
+
* A progress bar across the app's icon, `0`…`1`, cleared on unmount.
|
|
46
|
+
*
|
|
47
|
+
* ```jsx
|
|
48
|
+
* useProgress(done / total); // null or undefined clears it
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* Linux launchers only — the Dock has no progress bar of its own.
|
|
52
|
+
*/
|
|
53
|
+
export function useProgress(value) {
|
|
54
|
+
const app = useAppOrNull();
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
setProgress(value, { app }).catch(() => {});
|
|
57
|
+
}, [value, app]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
return () => {
|
|
60
|
+
setProgress(null, { app }).catch(() => {});
|
|
61
|
+
};
|
|
62
|
+
}, [app]);
|
|
63
|
+
}
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
|
-
* The menu behind a right-click on the
|
|
44
|
-
* vocabulary `MenuBar` and `ContextMenu` take — an
|
|
45
|
-
* when the user picks it.
|
|
66
|
+
* The menu behind a right-click on the app's icon in the Dock or launcher,
|
|
67
|
+
* from the same `items` vocabulary `MenuBar` and `ContextMenu` take — an
|
|
68
|
+
* item's `onSelect` fires when the user picks it.
|
|
46
69
|
*
|
|
47
70
|
* ```jsx
|
|
48
71
|
* useDockMenu([
|
|
@@ -53,8 +76,10 @@ let warnedInert = false;
|
|
|
53
76
|
* ```
|
|
54
77
|
*
|
|
55
78
|
* Installed for as long as the component is mounted, replaced when `items`
|
|
56
|
-
* changes, and taken down on unmount.
|
|
57
|
-
*
|
|
79
|
+
* changes, and taken down on unmount. `NSDockTile`'s menu on the cocoa
|
|
80
|
+
* backend; the launcher protocol's `quicklist` on Linux, which needs the
|
|
81
|
+
* identity `registerApplication({ appId })` establishes and a `.desktop` file
|
|
82
|
+
* of that name for a launcher to attach it to.
|
|
58
83
|
*/
|
|
59
84
|
export function useDockMenu(items) {
|
|
60
85
|
const app = useAppOrNull();
|
|
@@ -64,18 +89,12 @@ export function useDockMenu(items) {
|
|
|
64
89
|
live.current = items;
|
|
65
90
|
|
|
66
91
|
useEffect(() => {
|
|
67
|
-
|
|
68
|
-
if (process.env.NODE_ENV !== 'production' && !warnedInert && app) {
|
|
69
|
-
warnedInert = true;
|
|
70
|
-
console.warn(
|
|
71
|
-
'react-x11: useDockMenu() is inert on this backend — only the ' +
|
|
72
|
-
'cocoa backend has a Dock. On a Linux desktop the counterpart ' +
|
|
73
|
-
'is desktop actions in the .desktop file (docs/desktop.md).',
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
app.setDockMenu(items ?? null);
|
|
79
|
-
return () => app.setDockMenu(null);
|
|
92
|
+
setQuicklist(items ?? null, { app }).catch(() => {});
|
|
80
93
|
}, [items, app]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
return () => {
|
|
97
|
+
setQuicklist(null, { app }).catch(() => {});
|
|
98
|
+
};
|
|
99
|
+
}, [app]);
|
|
81
100
|
}
|
package/src/nodes/image.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
PictureSource,
|
|
7
7
|
acquireImageSource,
|
|
8
8
|
decodeImageSource,
|
|
9
|
+
freeImage,
|
|
9
10
|
imageSourceChanged,
|
|
10
11
|
isDirectImageSource,
|
|
11
12
|
isPathImageSource,
|
|
@@ -215,7 +216,7 @@ export class ImageNode extends Node {
|
|
|
215
216
|
}
|
|
216
217
|
if (this._ownedImage) {
|
|
217
218
|
// frees the per-app upload; the caller's own Images are never here
|
|
218
|
-
this._ownedImage
|
|
219
|
+
freeImage(this.app, this._ownedImage);
|
|
219
220
|
this._ownedImage = null;
|
|
220
221
|
}
|
|
221
222
|
if (this._serverSource) {
|