Added select button toggle for trash library grid
Template tests / tests (push) Successful in 2m0s

This commit is contained in:
Iisyourdad
2026-06-11 11:31:58 -05:00
parent 7e979cb2de
commit 33c421b746
4 changed files with 94 additions and 16 deletions
+5 -1
View File
@@ -202,7 +202,11 @@ function setupIpc() {
reindex(id); reindex(id);
return id; return id;
}); });
h('library:trash:purge', () => store.purgeTrash()); h('library:trash:purge', ({ names } = {}) => {
if (names && names.length) store.purgeTrashItems(names);
else store.purgeTrash();
return true;
});
h('folders:create', ({ name, parentId }) => store.createFolder(name, parentId || null)); h('folders:create', ({ name, parentId }) => store.createFolder(name, parentId || null));
h('folders:rename', ({ folderId, name }) => store.renameFolder(folderId, name)); h('folders:rename', ({ folderId, name }) => store.renameFolder(folderId, name));
h('folders:delete', ({ folderId }) => store.deleteFolder(folderId)); h('folders:delete', ({ folderId }) => store.deleteFolder(folderId));
+80 -15
View File
@@ -23,6 +23,7 @@ class StepForgeApp {
info: null, info: null,
selectMode: false, selectMode: false,
selectedGuides: new Set(), selectedGuides: new Set(),
selectedTrash: new Set(),
}; };
this.editorMeta = null; this.editorMeta = null;
this.libraryRenderToken = 0; this.libraryRenderToken = 0;
@@ -351,12 +352,13 @@ class StepForgeApp {
clearNode(this.libraryHost); clearNode(this.libraryHost);
const q = this.state.query.trim(); const q = this.state.query.trim();
const folderLabel = this.filterLabel(); const folderLabel = this.filterLabel();
// Selecting guides only makes sense for the plain guide grid — drop out // Selecting only makes sense for the guide grid and the trash — drop out
// of select mode for search results and the trash. // of select mode for search results.
const canSelect = !q && this.state.folderFilter !== 'trash'; const canSelect = !q;
if (!canSelect && this.state.selectMode) { if (!canSelect && this.state.selectMode) {
this.state.selectMode = false; this.state.selectMode = false;
this.state.selectedGuides = new Set(); this.state.selectedGuides = new Set();
this.state.selectedTrash = new Set();
} }
const body = el('div.library', {}, const body = el('div.library', {},
el('aside.lib-side', {}, el('aside.lib-side', {},
@@ -406,10 +408,18 @@ class StepForgeApp {
this.renderTopbar(); this.renderTopbar();
} }
setFolderFilter(folderFilter) {
this.state.folderFilter = folderFilter;
this.state.selectMode = false;
this.state.selectedGuides = new Set();
this.state.selectedTrash = new Set();
this.renderLibrary();
}
libraryNavItem(id, label, count) { libraryNavItem(id, label, count) {
const props = { const props = {
className: `nav-item${this.state.folderFilter === id ? ' active' : ''}`, className: `nav-item${this.state.folderFilter === id ? ' active' : ''}`,
onClick: () => { this.state.folderFilter = id; this.renderLibrary(); }, onClick: () => this.setFolderFilter(id),
}; };
if (!['all', 'favorites', 'trash'].includes(id)) { if (!['all', 'favorites', 'trash'].includes(id)) {
props.onContextMenu = (e) => this.folderContextMenu(e, id); props.onContextMenu = (e) => this.folderContextMenu(e, id);
@@ -430,7 +440,7 @@ class StepForgeApp {
out.push(el('div.nav-item', { out.push(el('div.nav-item', {
className: `nav-item${this.state.folderFilter === folder.id ? ' active' : ''}`, className: `nav-item${this.state.folderFilter === folder.id ? ' active' : ''}`,
style: { paddingLeft: `${8 + depth * 12}px` }, style: { paddingLeft: `${8 + depth * 12}px` },
onClick: () => { this.state.folderFilter = folder.id; this.renderLibrary(); }, onClick: () => this.setFolderFilter(folder.id),
onContextMenu: (e) => this.folderContextMenu(e, folder.id), onContextMenu: (e) => this.folderContextMenu(e, folder.id),
}, },
el('span', {}, folder.name), el('span', {}, folder.name),
@@ -514,17 +524,26 @@ class StepForgeApp {
this.domLibraryResults.append(el('div.empty-state', {}, el('div.big', {}, 'Trash'), 'Nothing deleted yet.')); this.domLibraryResults.append(el('div.empty-state', {}, el('div.big', {}, 'Trash'), 'Nothing deleted yet.'));
return; return;
} }
const items = this.state.trash.map((name) => el('div.guide-card', { const selectMode = this.state.selectMode;
onContextMenu: (e) => { const items = this.state.trash.map((name) => {
e.preventDefault(); const selected = this.state.selectedTrash.has(name);
contextMenu(e.clientX, e.clientY, [ return el('div.guide-card', {
{ label: 'Restore', action: () => this.restoreTrashItem(name) }, className: `guide-card${selected ? ' selected' : ''}`,
{ label: 'Empty trash', danger: true, action: () => this.purgeTrashItem() }, onClick: () => {
]); if (selectMode) this.toggleTrashSelection(name);
},
onContextMenu: (e) => {
e.preventDefault();
if (selectMode) return;
contextMenu(e.clientX, e.clientY, [
{ label: 'Restore', action: () => this.restoreTrashItem(name) },
{ label: 'Empty trash', danger: true, action: () => this.purgeTrashItem() },
]);
},
}, },
}, el('h4', {}, name),
el('h4', {}, name), el('div.meta', {}, 'Deleted guide archive'));
el('div.meta', {}, 'Deleted guide archive'))); });
this.domLibraryResults.append(el('div.guide-grid', {}, ...items)); this.domLibraryResults.append(el('div.guide-grid', {}, ...items));
} }
@@ -604,6 +623,7 @@ class StepForgeApp {
toggleSelectMode() { toggleSelectMode() {
this.state.selectMode = !this.state.selectMode; this.state.selectMode = !this.state.selectMode;
this.state.selectedGuides = new Set(); this.state.selectedGuides = new Set();
this.state.selectedTrash = new Set();
this.renderLibrary(); this.renderLibrary();
} }
@@ -627,10 +647,55 @@ class StepForgeApp {
this.renderBulkBar(); this.renderBulkBar();
} }
toggleTrashSelection(name) {
if (this.state.selectedTrash.has(name)) this.state.selectedTrash.delete(name);
else this.state.selectedTrash.add(name);
this.renderTrashView();
this.renderBulkBar();
}
selectAllTrash() {
this.state.selectedTrash = new Set(this.state.trash);
this.renderTrashView();
this.renderBulkBar();
}
clearTrashSelection() {
this.state.selectedTrash = new Set();
this.renderTrashView();
this.renderBulkBar();
}
async bulkPurgeTrash() {
const names = [...this.state.selectedTrash];
if (!names.length) return;
const ok = await confirmDialog(`Permanently delete ${names.length} item${names.length === 1 ? '' : 's'}? This cannot be undone.`, { danger: true, okLabel: 'Delete forever' });
if (!ok) return;
await api.library.trashPurge({ names });
this.state.selectedTrash = new Set();
await this.refreshLibrary();
}
renderBulkBar() { renderBulkBar() {
if (!this.domBulkBar) return; if (!this.domBulkBar) return;
clearNode(this.domBulkBar); clearNode(this.domBulkBar);
if (!this.state.selectMode) return; if (!this.state.selectMode) return;
if (this.state.folderFilter === 'trash') {
const n = this.state.selectedTrash.size;
const allSelected = this.state.trash.length > 0 && n === this.state.trash.length;
this.domBulkBar.append(
el('div.bulk-bar', {},
el('span', {}, n ? `${n} selected` : 'Select items to delete forever'),
el('span.spacer', {}),
el('button', {
type: 'button',
onClick: () => (allSelected ? this.clearTrashSelection() : this.selectAllTrash()),
}, allSelected ? 'Clear selection' : 'Select all'),
el('button.danger', { type: 'button', disabled: !n, onClick: () => this.bulkPurgeTrash() }, 'Delete forever'),
),
);
return;
}
const guides = this.state.library.guides.filter((guide) => this.scopeGuide(guide)); const guides = this.state.library.guides.filter((guide) => this.scopeGuide(guide));
const n = this.state.selectedGuides.size; const n = this.state.selectedGuides.size;
const allSelected = guides.length > 0 && n === guides.length; const allSelected = guides.length > 0 && n === guides.length;
+6
View File
@@ -134,6 +134,12 @@ class GuideStore {
} }
} }
purgeTrashItems(names) {
for (const name of names) {
fs.rmSync(path.join(this.trashDir, path.basename(name)), { recursive: true, force: true });
}
}
duplicateGuide(guideId, { title } = {}) { duplicateGuide(guideId, { title } = {}) {
const src = this.getGuide(guideId); const src = this.getGuide(guideId);
const steps = this.listSteps(guideId); const steps = this.listSteps(guideId);
+3
View File
@@ -58,6 +58,9 @@ Initial release.
recording" option to resume capturing more steps. recording" option to resume capturing more steps.
- Editor step list: a "Select" toggle enables multi-select (checkboxes) - Editor step list: a "Select" toggle enables multi-select (checkboxes)
with a "Select all" / "Delete" bar for removing several steps at once. with a "Select all" / "Delete" bar for removing several steps at once.
- The library's "Select" toggle and bulk action bar now also work in the
Trash, with a "Delete forever" action that permanently removes the
selected items.
### Fixed ### Fixed