Give the collections sidebar its own Slint global
AppWindow carried the sidebar's tree, its row menu, renaming, drag and drop between rows, the trash row, and the membership sheet as ~40 properties and callbacks on the root component, in the pattern CH-1 describes and the develop screen's globals (Adjustments, Framing, Steps, ...) already replaced. Collections.* in collections.slint now holds that state, declared next to the MembershipRow struct it and the membership sheet both use; Rust reaches it through window.global::<Collections>() instead of window.set_/get_/on_/invoke_ on the root. collection-selected, collection-select, and collection-offline-menu stay on AppWindow: library_ui.rs invokes collection-select directly and registers collection-offline-menu's handler, and lib.rs reads collection-selected for back-navigation, so moving them would have meant editing library_ui.rs, which another change on this branch is splitting into a module directory. collections-visible stays too — it is lib.rs's panel-layout state, seeded from the saved layout before the sidebar exists, and collections_ui never touches it. Everything prefixed library- (the grid's drag, selection and keyword state that the sidebar's Rust also wires for cross-feature gestures like filing a selection into a collection) stays on the window as well, since it belongs to the library screen, not the sidebar.
This commit is contained in:
+62
-145
@@ -10,7 +10,7 @@ import { IdentityScreen, IdentityPerson, IdentityFace } from "identity.slint";
|
||||
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChip } from "library.slint";
|
||||
import { GestureRow } from "gestures.slint";
|
||||
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
||||
import { CollectionsPanel, CollectionRow, OfflinePrompt, CollectionMenu,
|
||||
import { CollectionsPanel, CollectionRow, Collections, OfflinePrompt, CollectionMenu,
|
||||
MembershipSheet, MembershipRow } from "collections.slint";
|
||||
import { HistogramPanel, HistogramView, Levels } from "histogram.slint";
|
||||
import { RecoveryPrompt } from "recovery.slint";
|
||||
@@ -31,6 +31,7 @@ export { ViewMode, GradientHandle, HandleRole, SpotHandle, SpotRole }
|
||||
// exports appears in the generated API. `session.slint` says why the develop
|
||||
// panels take their wiring this way and why there is one global per family.
|
||||
export { Develop, Capture, Levels, Peaking, Adjustments, Framing, Transfer, Masking, Repair, Steps }
|
||||
export { Collections }
|
||||
|
||||
/// Which top-level screen is on display. Exactly one at a time — replacing
|
||||
/// the `show-launch` / `show-library` / `show-identity` booleans, with
|
||||
@@ -539,75 +540,25 @@ export component AppWindow inherits Window {
|
||||
|
||||
// --- collections (FR-CAT-7) ---
|
||||
//
|
||||
// The sidebar's tree, flattened in Rust: Slint cannot instantiate a
|
||||
// component recursively, so depth arrives as an integer per row.
|
||||
in-out property <[CollectionRow]> collection-rows;
|
||||
// State that belongs to the sidebar itself — the tree, the row menu,
|
||||
// drag and drop between rows, the trash row — has moved to the
|
||||
// `Collections` global in `collections.slint`. What is left here is
|
||||
// shared with the library grid, whose Rust wiring still reaches it on
|
||||
// the window.
|
||||
/// Which collection scopes the grid. 0 is the whole library.
|
||||
in property <int> collection-selected: 0;
|
||||
in property <string> collection-scope-label: "";
|
||||
in property <string> collection-error: "";
|
||||
/// Whether the sidebar is shown at all. Closed by default in the compact
|
||||
/// layout, where 232px of the window is most of the photograph (FR-UI-1),
|
||||
/// and openable from the grid header either way — see `panel-visible` for
|
||||
/// how a default and a user's answer to it are kept apart.
|
||||
/// how a default and a user's answer to it are kept apart. Owned by
|
||||
/// `run()`'s panel-layout wiring rather than the sidebar's own global,
|
||||
/// because it seeds from the saved layout before the sidebar exists.
|
||||
in property <bool> collections-visible: true;
|
||||
|
||||
/// Which collection is being renamed in the sidebar, by id. 0 is none.
|
||||
in property <int> collection-renaming: 0;
|
||||
|
||||
callback collection-select(int);
|
||||
callback collection-toggle(int);
|
||||
callback collection-new();
|
||||
callback collection-menu(int);
|
||||
|
||||
// --- the row menu the gesture above opens (FR-CAT-7) -------------------
|
||||
//
|
||||
// Rust owns every one of these. What the menu offers depends on counts it
|
||||
// has to query — whether the collection is nested, what deleting would
|
||||
// take — and the confirmation step is a policy decision, not a local bit
|
||||
// the sheet can flip for itself.
|
||||
/// The collection the menu is about. Empty closes it.
|
||||
in property <string> collection-menu-title;
|
||||
/// "12 photographs · 2 collections inside".
|
||||
in property <string> collection-menu-detail;
|
||||
/// Whether it has a parent, and so somewhere to be promoted out to.
|
||||
in property <bool> collection-menu-nested: false;
|
||||
/// Whether it is a saved filter, which cannot hold a nested collection.
|
||||
in property <bool> collection-menu-smart: false;
|
||||
/// Whether a delete has been asked once and awaits confirmation.
|
||||
in property <bool> collection-menu-confirming: false;
|
||||
/// What the delete would take with it, and what survives it.
|
||||
in property <string> collection-menu-confirm-detail;
|
||||
in property <string> collection-menu-delete-label;
|
||||
/// TRACES: FR-NC-6a
|
||||
/// Whether the collection the menu is about is kept on this device.
|
||||
in property <bool> collection-menu-pinned: false;
|
||||
callback collection-menu-rename();
|
||||
callback collection-menu-new-child();
|
||||
/// Move it back to the top level. The inverse of the drag that nests one,
|
||||
/// which until now had no gesture at all.
|
||||
callback collection-menu-promote();
|
||||
/// TRACES: FR-NC-6a
|
||||
/// Hand the offline question over to the handler the tray on the row
|
||||
/// already uses, rather than a second route to the same prompt.
|
||||
callback collection-menu-offline();
|
||||
callback collection-menu-delete();
|
||||
callback collection-menu-dismiss();
|
||||
/// TRACES: FR-NC-6a
|
||||
/// Ask about a collection's local copies: the tray on a row, or a long
|
||||
/// press anywhere on it.
|
||||
callback collection-offline-menu(int);
|
||||
/// A row is held or released — the long press is timed in Rust.
|
||||
callback collection-row-press(int, bool);
|
||||
// --- picking a collection up (FR-UI-3, FR-UI-4) ------------------------
|
||||
/// Which collection the hold has picked up, by id. 0 is none.
|
||||
in property <int> collection-lifted: 0;
|
||||
/// A row's drag crossed the threshold, or ended.
|
||||
callback collection-drag-active(int, bool);
|
||||
/// Whether "All photographs" would take the drag currently in flight.
|
||||
in property <bool> collection-root-drop-allowed: false;
|
||||
/// A collection was dropped on "All photographs": out to the top level.
|
||||
callback collection-dropped-on-root();
|
||||
/// TRACES: FR-CAT-7 | FR-UI-4
|
||||
/// File the grid's selection in a collection without dragging it there:
|
||||
/// the target's id, and whether to take the images out of the collection
|
||||
@@ -646,38 +597,6 @@ export component AppWindow inherits Window {
|
||||
in-out property <int> library-held-row: -1;
|
||||
/// A press on a cell ended, so the long-press timer can be cancelled.
|
||||
callback library-cell-press-ended();
|
||||
/// TRACES: FR-UI-2 | FR-UI-4
|
||||
/// Two taps on a cell: in selection mode, the far end of a range.
|
||||
/// Renaming started on a row, by id — a double-click, or `F2`.
|
||||
callback collection-rename-start(int);
|
||||
/// A rename was committed: the collection's id and the new name.
|
||||
callback collection-rename-commit(int, string);
|
||||
/// Renaming was abandoned with Escape.
|
||||
callback collection-rename-cancel();
|
||||
/// Row index and collection id under the pointer. The id is what the
|
||||
/// drop highlight follows: a spring expansion rebuilds the row model
|
||||
/// mid-drag, and an index would then point at a different collection.
|
||||
/// A drag is dwelling over a collection, or has left it: id, and whether
|
||||
/// it is over. Drives the spring-loaded expansion, timed in Rust.
|
||||
callback collection-drag-over(int, bool);
|
||||
/// Images were dropped on a collection, by id. Slint hit-tests the release,
|
||||
/// so this is the collection actually under the pointer.
|
||||
callback collection-dropped(int);
|
||||
/// Payload for dragging a collection onto another to reparent it.
|
||||
pure callback collection-drag-payload(int) -> data-transfer;
|
||||
|
||||
// --- trash (FR-CAT-15) ---
|
||||
//
|
||||
// Not a collection: a drop here moves the file into a trash folder on the
|
||||
// server, where a collection drop only adds a reference.
|
||||
in property <int> trash-count: 0;
|
||||
in property <string> trash-label: "";
|
||||
/// A soft delete: move the dragged images to the trash folder.
|
||||
callback trash-dropped();
|
||||
/// A hard delete: permanently remove everything in the trash.
|
||||
callback trash-empty();
|
||||
/// Put the selected trashed images back where they came from.
|
||||
callback trash-restore();
|
||||
|
||||
// --- drag and drop ---
|
||||
//
|
||||
@@ -703,14 +622,12 @@ export component AppWindow inherits Window {
|
||||
// --- where the selection is filed (FR-CAT-7) ---------------------------
|
||||
//
|
||||
// `library-remove-from-collection` can only ever mean the collection the
|
||||
// grid is scoped to — it is the only one that button can name. This sheet
|
||||
// is the other half: which collections the selection is actually in, and
|
||||
// the way out of any of them without navigating there first.
|
||||
in property <bool> membership-open: false;
|
||||
in property <[MembershipRow]> membership-rows;
|
||||
// grid is scoped to — it is the only one that button can name. Opens the
|
||||
// membership sheet, whose state (`Collections.membership-open` and
|
||||
// `.membership-rows`) is on the sidebar's own global — only the library
|
||||
// side knows the selection has changed enough to be worth recomputing
|
||||
// the sheet against.
|
||||
callback library-open-membership();
|
||||
callback membership-remove(int);
|
||||
callback membership-dismiss();
|
||||
|
||||
// The keyboard cursor: where the arrows are in the library, as an image
|
||||
// ordinal. Rust owns it — clamping it needs the library's length, and
|
||||
@@ -1287,12 +1204,12 @@ in property <bool> panel-visible: true;
|
||||
// menu's own confirmation is a step Back must be able to take
|
||||
// one at a time, which is why the dismiss handlers in Rust
|
||||
// back out of a pending delete rather than closing outright.
|
||||
if (root.collection-menu-title != "") {
|
||||
root.collection-menu-dismiss();
|
||||
if (Collections.collection-menu-title != "") {
|
||||
Collections.collection-menu-dismiss();
|
||||
return accept;
|
||||
}
|
||||
if (root.membership-open) {
|
||||
root.membership-dismiss();
|
||||
if (Collections.membership-open) {
|
||||
Collections.membership-dismiss();
|
||||
return accept;
|
||||
}
|
||||
if (root.offline-prompt-title != "") {
|
||||
@@ -1641,10 +1558,10 @@ in property <bool> panel-visible: true;
|
||||
CollectionsPanel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
rows: root.collection-rows;
|
||||
rows: Collections.collection-rows;
|
||||
selected-id: root.collection-selected;
|
||||
total-images: root.library-total;
|
||||
error: root.collection-error;
|
||||
error: Collections.collection-error;
|
||||
|
||||
// TRACES: FR-NC-6a
|
||||
// The two actions that were in the library header and
|
||||
@@ -1657,32 +1574,32 @@ in property <bool> panel-visible: true;
|
||||
change-library() => { root.library-change(); }
|
||||
|
||||
select(id) => { root.collection-select(id); }
|
||||
toggle(id) => { root.collection-toggle(id); }
|
||||
new-collection() => { root.collection-new(); }
|
||||
row-menu(id) => { root.collection-menu(id); }
|
||||
toggle(id) => { Collections.collection-toggle(id); }
|
||||
new-collection() => { Collections.collection-new(); }
|
||||
row-menu(id) => { Collections.collection-menu(id); }
|
||||
offline-menu(id) => { root.collection-offline-menu(id); }
|
||||
row-press(id, down) => { root.collection-row-press(id, down); }
|
||||
lifted-id: root.collection-lifted;
|
||||
drag-active(id, on) => { root.collection-drag-active(id, on); }
|
||||
root-drop-allowed: root.collection-root-drop-allowed;
|
||||
dropped-on-root() => { root.collection-dropped-on-root(); }
|
||||
row-press(id, down) => { Collections.collection-row-press(id, down); }
|
||||
lifted-id: Collections.collection-lifted;
|
||||
drag-active(id, on) => { Collections.collection-drag-active(id, on); }
|
||||
root-drop-allowed: Collections.collection-root-drop-allowed;
|
||||
dropped-on-root() => { Collections.collection-dropped-on-root(); }
|
||||
|
||||
renaming-id: root.collection-renaming;
|
||||
rename-start(id) => { root.collection-rename-start(id); }
|
||||
renaming-id: Collections.collection-renaming;
|
||||
rename-start(id) => { Collections.collection-rename-start(id); }
|
||||
rename-commit(id, name) => {
|
||||
root.collection-rename-commit(id, name);
|
||||
Collections.collection-rename-commit(id, name);
|
||||
}
|
||||
rename-cancel() => { root.collection-rename-cancel(); }
|
||||
trash-count: root.trash-count;
|
||||
trash-label: root.trash-label;
|
||||
rename-cancel() => { Collections.collection-rename-cancel(); }
|
||||
trash-count: Collections.trash-count;
|
||||
trash-label: Collections.trash-label;
|
||||
selected-count: root.library-selected-count;
|
||||
|
||||
dropped-on(id) => { root.collection-dropped(id); }
|
||||
collection-drag-payload(id) => { return root.collection-drag-payload(id); }
|
||||
dropped-on-trash() => { root.trash-dropped(); }
|
||||
empty-trash() => { root.trash-empty(); }
|
||||
restore-selected() => { root.trash-restore(); }
|
||||
drag-over(id, over) => { root.collection-drag-over(id, over); }
|
||||
dropped-on(id) => { Collections.collection-dropped(id); }
|
||||
collection-drag-payload(id) => { return Collections.collection-drag-payload(id); }
|
||||
dropped-on-trash() => { Collections.trash-dropped(); }
|
||||
empty-trash() => { Collections.trash-empty(); }
|
||||
restore-selected() => { Collections.trash-restore(); }
|
||||
drag-over(id, over) => { Collections.collection-drag-over(id, over); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,7 +1654,7 @@ in property <bool> panel-visible: true;
|
||||
window-label: root.library-window-label;
|
||||
offset: root.library-offset;
|
||||
selected-count: root.library-selected-count;
|
||||
scope-label: root.collection-scope-label;
|
||||
scope-label: Collections.collection-scope-label;
|
||||
settings-armed: Transfer.armed;
|
||||
paste-settings-to-selection => {
|
||||
root.paste-settings-to-selection();
|
||||
@@ -1804,7 +1721,7 @@ in property <bool> panel-visible: true;
|
||||
held-row <=> root.library-held-row;
|
||||
// The sidebar's rows, not a second model: the sheet files
|
||||
// into the same tree the sidebar draws.
|
||||
collections: root.collection-rows;
|
||||
collections: Collections.collection-rows;
|
||||
file-in-collection(id, moves) => {
|
||||
root.library-file-in-collection(id, moves);
|
||||
}
|
||||
@@ -1856,7 +1773,7 @@ in property <bool> panel-visible: true;
|
||||
// trash, neither of which has a name to change — Rust drops
|
||||
// those rather than the binding trying to reason about it.
|
||||
rename-scope() => {
|
||||
root.collection-rename-start(root.collection-selected);
|
||||
Collections.collection-rename-start(root.collection-selected);
|
||||
}
|
||||
filter-min-rating-changed(n) => {
|
||||
root.library-filter-min-rating-changed(n);
|
||||
@@ -3261,30 +3178,30 @@ in property <bool> panel-visible: true;
|
||||
MembershipSheet {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
open: root.membership-open;
|
||||
open: Collections.membership-open;
|
||||
selected-count: root.library-selected-count;
|
||||
rows: root.membership-rows;
|
||||
remove(id) => { root.membership-remove(id); }
|
||||
dismiss() => { root.membership-dismiss(); }
|
||||
rows: Collections.membership-rows;
|
||||
remove(id) => { Collections.membership-remove(id); }
|
||||
dismiss() => { Collections.membership-dismiss(); }
|
||||
}
|
||||
|
||||
CollectionMenu {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
title: root.collection-menu-title;
|
||||
detail: root.collection-menu-detail;
|
||||
nested: root.collection-menu-nested;
|
||||
smart: root.collection-menu-smart;
|
||||
confirming: root.collection-menu-confirming;
|
||||
confirm-detail: root.collection-menu-confirm-detail;
|
||||
delete-label: root.collection-menu-delete-label;
|
||||
pinned: root.collection-menu-pinned;
|
||||
rename() => { root.collection-menu-rename(); }
|
||||
new-child() => { root.collection-menu-new-child(); }
|
||||
promote() => { root.collection-menu-promote(); }
|
||||
offline() => { root.collection-menu-offline(); }
|
||||
delete() => { root.collection-menu-delete(); }
|
||||
dismiss() => { root.collection-menu-dismiss(); }
|
||||
title: Collections.collection-menu-title;
|
||||
detail: Collections.collection-menu-detail;
|
||||
nested: Collections.collection-menu-nested;
|
||||
smart: Collections.collection-menu-smart;
|
||||
confirming: Collections.collection-menu-confirming;
|
||||
confirm-detail: Collections.collection-menu-confirm-detail;
|
||||
delete-label: Collections.collection-menu-delete-label;
|
||||
pinned: Collections.collection-menu-pinned;
|
||||
rename() => { Collections.collection-menu-rename(); }
|
||||
new-child() => { Collections.collection-menu-new-child(); }
|
||||
promote() => { Collections.collection-menu-promote(); }
|
||||
offline() => { Collections.collection-menu-offline(); }
|
||||
delete() => { Collections.collection-menu-delete(); }
|
||||
dismiss() => { Collections.collection-menu-dismiss(); }
|
||||
}
|
||||
|
||||
// Last, and therefore over everything including the settings page and
|
||||
|
||||
@@ -1179,6 +1179,124 @@ export struct MembershipRow {
|
||||
detail: string,
|
||||
}
|
||||
|
||||
// TRACES: FR-CAT-7 | FR-CAT-15
|
||||
// The collections sidebar's own state: the tree, the row menu, renaming,
|
||||
// drag and drop between rows, the trash row, and the membership sheet this
|
||||
// file also declares.
|
||||
//
|
||||
// A global rather than properties and callbacks on the window root — see
|
||||
// `session.slint` for why the develop panels stopped taking their wiring
|
||||
// through the window root; the same reasoning applies here. What the sidebar
|
||||
// shares with the library grid (a photograph dragged out of a cell, the
|
||||
// selection a "file in collection" gesture acts on) stays on the window,
|
||||
// because Rust on the library side still reaches it there.
|
||||
export global Collections {
|
||||
// --- collections (FR-CAT-7) ---
|
||||
//
|
||||
// The sidebar's tree, flattened in Rust: Slint cannot instantiate a
|
||||
// component recursively, so depth arrives as an integer per row.
|
||||
in-out property <[CollectionRow]> collection-rows;
|
||||
in property <string> collection-scope-label: "";
|
||||
in property <string> collection-error: "";
|
||||
|
||||
/// Which collection is being renamed in the sidebar, by id. 0 is none.
|
||||
in property <int> collection-renaming: 0;
|
||||
|
||||
callback collection-toggle(int);
|
||||
callback collection-new();
|
||||
callback collection-menu(int);
|
||||
|
||||
// --- the row menu the gesture above opens (FR-CAT-7) -------------------
|
||||
//
|
||||
// Rust owns every one of these. What the menu offers depends on counts it
|
||||
// has to query — whether the collection is nested, what deleting would
|
||||
// take — and the confirmation step is a policy decision, not a local bit
|
||||
// the sheet can flip for itself.
|
||||
/// The collection the menu is about. Empty closes it.
|
||||
in property <string> collection-menu-title;
|
||||
/// "12 photographs · 2 collections inside".
|
||||
in property <string> collection-menu-detail;
|
||||
/// Whether it has a parent, and so somewhere to be promoted out to.
|
||||
in property <bool> collection-menu-nested: false;
|
||||
/// Whether it is a saved filter, which cannot hold a nested collection.
|
||||
in property <bool> collection-menu-smart: false;
|
||||
/// Whether a delete has been asked once and awaits confirmation.
|
||||
in property <bool> collection-menu-confirming: false;
|
||||
/// What the delete would take with it, and what survives it.
|
||||
in property <string> collection-menu-confirm-detail;
|
||||
in property <string> collection-menu-delete-label;
|
||||
/// TRACES: FR-NC-6a
|
||||
/// Whether the collection the menu is about is kept on this device.
|
||||
in property <bool> collection-menu-pinned: false;
|
||||
callback collection-menu-rename();
|
||||
callback collection-menu-new-child();
|
||||
/// Move it back to the top level. The inverse of the drag that nests one,
|
||||
/// which until now had no gesture at all.
|
||||
callback collection-menu-promote();
|
||||
/// TRACES: FR-NC-6a
|
||||
/// Hand the offline question over to the handler the tray on the row
|
||||
/// already uses, rather than a second route to the same prompt.
|
||||
callback collection-menu-offline();
|
||||
callback collection-menu-delete();
|
||||
callback collection-menu-dismiss();
|
||||
/// A row is held or released — the long press is timed in Rust.
|
||||
callback collection-row-press(int, bool);
|
||||
// --- picking a collection up (FR-UI-3, FR-UI-4) ------------------------
|
||||
/// Which collection the hold has picked up, by id. 0 is none.
|
||||
in property <int> collection-lifted: 0;
|
||||
/// A row's drag crossed the threshold, or ended.
|
||||
callback collection-drag-active(int, bool);
|
||||
/// Whether "All photographs" would take the drag currently in flight.
|
||||
in property <bool> collection-root-drop-allowed: false;
|
||||
/// A collection was dropped on "All photographs": out to the top level.
|
||||
callback collection-dropped-on-root();
|
||||
/// TRACES: FR-UI-2 | FR-UI-4
|
||||
/// Renaming started on a row, by id — a double-click, or `F2`.
|
||||
callback collection-rename-start(int);
|
||||
/// A rename was committed: the collection's id and the new name.
|
||||
callback collection-rename-commit(int, string);
|
||||
/// Renaming was abandoned with Escape.
|
||||
callback collection-rename-cancel();
|
||||
/// Row index and collection id under the pointer. The id is what the
|
||||
/// drop highlight follows: a spring expansion rebuilds the row model
|
||||
/// mid-drag, and an index would then point at a different collection.
|
||||
/// A drag is dwelling over a collection, or has left it: id, and whether
|
||||
/// it is over. Drives the spring-loaded expansion, timed in Rust.
|
||||
callback collection-drag-over(int, bool);
|
||||
/// Images were dropped on a collection, by id. Slint hit-tests the release,
|
||||
/// so this is the collection actually under the pointer.
|
||||
callback collection-dropped(int);
|
||||
/// Payload for dragging a collection onto another to reparent it.
|
||||
pure callback collection-drag-payload(int) -> data-transfer;
|
||||
|
||||
// --- trash (FR-CAT-15) ---
|
||||
//
|
||||
// Not a collection: a drop here moves the file into a trash folder on the
|
||||
// server, where a collection drop only adds a reference.
|
||||
in property <int> trash-count: 0;
|
||||
in property <string> trash-label: "";
|
||||
/// A soft delete: move the dragged images to the trash folder.
|
||||
callback trash-dropped();
|
||||
/// A hard delete: permanently remove everything in the trash.
|
||||
callback trash-empty();
|
||||
/// Put the selected trashed images back where they came from.
|
||||
callback trash-restore();
|
||||
|
||||
// --- where the selection is filed (FR-CAT-7) ---------------------------
|
||||
//
|
||||
// The library's "Remove from collection" can only ever mean the
|
||||
// collection the grid is scoped to — it is the only one that button can
|
||||
// name. This sheet is the other half: which collections the selection is
|
||||
// actually in, and the way out of any of them without navigating there
|
||||
// first. Opened from the library grid via the window's own
|
||||
// `library-open-membership`, because only the library side knows the
|
||||
// selection has changed enough to be worth recomputing against.
|
||||
in property <bool> membership-open: false;
|
||||
in property <[MembershipRow]> membership-rows;
|
||||
callback membership-remove(int);
|
||||
callback membership-dismiss();
|
||||
}
|
||||
|
||||
// Where the selection is filed, and the way out of any of it.
|
||||
//
|
||||
// The library's "Remove from collection" button takes the selection out of the
|
||||
|
||||
Reference in New Issue
Block a user