(core) Allow docs to be permanently deleted in icon view

Summary:
Previously, soft-deleted docs in icon view were still accessible from
the Trash and couldn't be permanently deleted.

Test Plan:
Improved the nbrowser test for deleting docs to verify that it can
be done in both view modes.

Reviewers: dsagal, paulfitz

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2862
This commit is contained in:
George Gevoian 2021-06-14 09:54:47 -07:00
parent 30bbb6fa94
commit 3af6dd46ea
3 changed files with 28 additions and 17 deletions

View File

@ -200,7 +200,7 @@ function buildWorkspaceDocBlock(home: HomeModel, workspace: Workspace, flashDocI
return dom.forEach(docs, doc => { return dom.forEach(docs, doc => {
if (view === 'icons') { if (view === 'icons') {
return dom.update( return dom.update(
buildPinnedDoc(home, doc), buildPinnedDoc(home, doc, workspace),
testId('doc'), testId('doc'),
); );
} }
@ -347,7 +347,7 @@ export function makeDocOptionsMenu(home: HomeModel, doc: Document, renaming: Obs
]; ];
} }
function makeRemovedDocOptionsMenu(home: HomeModel, doc: Document, workspace: Workspace) { export function makeRemovedDocOptionsMenu(home: HomeModel, doc: Document, workspace: Workspace) {
function hardDeleteDoc() { function hardDeleteDoc() {
confirmModal(`Permanently Delete "${doc.name}"?`, 'Delete Forever', confirmModal(`Permanently Delete "${doc.name}"?`, 'Delete Forever',
() => home.deleteDoc(doc.id, true).catch(reportError), () => home.deleteDoc(doc.id, true).catch(reportError),

View File

@ -103,15 +103,15 @@ function _buildExampleListDocs(home: HomeModel, workspace: Workspace, viewSettin
testId('examples-desc'), testId('examples-desc'),
), ),
dom.domComputed(viewSettings.currentView, (view) => dom.domComputed(viewSettings.currentView, (view) =>
dom.forEach(workspace.docs, doc => buildExampleItem(doc, home, view)) dom.forEach(workspace.docs, doc => buildExampleItem(doc, home, workspace, view))
), ),
]; ];
} }
function buildExampleItem(doc: Document, home: HomeModel, view: 'list'|'icons') { function buildExampleItem(doc: Document, home: HomeModel, workspace: Workspace, view: 'list'|'icons') {
const ex = examples.find((e) => e.matcher.test(doc.name)); const ex = examples.find((e) => e.matcher.test(doc.name));
if (view === 'icons') { if (view === 'icons') {
return buildPinnedDoc(home, doc, ex); return buildPinnedDoc(home, doc, workspace, ex);
} else { } else {
return css.docRowWrapper( return css.docRowWrapper(
cssDocRowLink( cssDocRowLink(

View File

@ -1,13 +1,13 @@
import {docUrl, urlState} from 'app/client/models/gristUrlState'; import {docUrl, urlState} from 'app/client/models/gristUrlState';
import {getTimeFromNow, HomeModel} from 'app/client/models/HomeModel'; import {getTimeFromNow, HomeModel} from 'app/client/models/HomeModel';
import {makeDocOptionsMenu} from 'app/client/ui/DocMenu'; import {makeDocOptionsMenu, makeRemovedDocOptionsMenu} from 'app/client/ui/DocMenu';
import {IExampleInfo} from 'app/client/ui/ExampleInfo'; import {IExampleInfo} from 'app/client/ui/ExampleInfo';
import {transientInput} from 'app/client/ui/transientInput'; import {transientInput} from 'app/client/ui/transientInput';
import {colors, vars} from 'app/client/ui2018/cssVars'; import {colors, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons'; import {icon} from 'app/client/ui2018/icons';
import {menu} from 'app/client/ui2018/menus'; import {menu} from 'app/client/ui2018/menus';
import * as roles from 'app/common/roles'; import * as roles from 'app/common/roles';
import {Document} from 'app/common/UserAPI'; import {Document, Workspace} from 'app/common/UserAPI';
import {computed, dom, makeTestId, observable, styled} from 'grainjs'; import {computed, dom, makeTestId, observable, styled} from 'grainjs';
const testId = makeTestId('test-dm-'); const testId = makeTestId('test-dm-');
@ -20,7 +20,7 @@ const testId = makeTestId('test-dm-');
*/ */
export function createPinnedDocs(home: HomeModel) { export function createPinnedDocs(home: HomeModel) {
return pinnedDocList( return pinnedDocList(
dom.forEach(home.currentWSPinnedDocs, doc => buildPinnedDoc(home, doc)), dom.forEach(home.currentWSPinnedDocs, doc => buildPinnedDoc(home, doc, doc.workspace)),
testId('pinned-doc-list'), testId('pinned-doc-list'),
); );
} }
@ -29,7 +29,8 @@ export function createPinnedDocs(home: HomeModel) {
* Build a single doc card with a preview and name. A misnomer because it's now used not only for * Build a single doc card with a preview and name. A misnomer because it's now used not only for
* pinned docs, but also for the thumnbails (aka "icons") view mode. * pinned docs, but also for the thumnbails (aka "icons") view mode.
*/ */
export function buildPinnedDoc(home: HomeModel, doc: Document, example?: IExampleInfo): HTMLElement { export function buildPinnedDoc(home: HomeModel, doc: Document, workspace: Workspace,
example?: IExampleInfo): HTMLElement {
const renaming = observable<Document|null>(null); const renaming = observable<Document|null>(null);
const isRenamingDoc = computed((use) => use(renaming) === doc); const isRenamingDoc = computed((use) => use(renaming) === doc);
const docTitle = example?.title || doc.name; const docTitle = example?.title || doc.name;
@ -37,7 +38,7 @@ export function buildPinnedDoc(home: HomeModel, doc: Document, example?: IExampl
dom.autoDispose(isRenamingDoc), dom.autoDispose(isRenamingDoc),
dom.domComputed(isRenamingDoc, (isRenaming) => dom.domComputed(isRenamingDoc, (isRenaming) =>
pinnedDoc( pinnedDoc(
isRenaming ? null : urlState().setLinkUrl(docUrl(doc)), isRenaming || doc.removedAt ? null : urlState().setLinkUrl(docUrl(doc)),
pinnedDoc.cls('-no-access', !roles.canView(doc.access)), pinnedDoc.cls('-no-access', !roles.canView(doc.access)),
pinnedDocPreview( pinnedDocPreview(
example?.bgColor ? dom.style('background-color', example.bgColor) : null, example?.bgColor ? dom.style('background-color', example.bgColor) : null,
@ -64,17 +65,27 @@ export function buildPinnedDoc(home: HomeModel, doc: Document, example?: IExampl
) )
), ),
cssPinnedDocDesc( cssPinnedDocDesc(
example?.desc || capitalizeFirst(getTimeFromNow(doc.updatedAt)), example?.desc || capitalizeFirst(getTimeFromNow(doc.removedAt || doc.updatedAt)),
testId('pinned-doc-desc') testId('pinned-doc-desc')
) )
) )
) )
), ),
example ? null : pinnedDocOptions(icon('Dots'), example ? null : (doc.removedAt ?
menu(() => makeDocOptionsMenu(home, doc, renaming), {placement: 'bottom-start'}), [
// For deleted documents, attach the menu to the entire doc icon, and include the
// "Dots" icon just to clarify that there are options.
menu(() => makeRemovedDocOptionsMenu(home, doc, workspace),
{placement: 'right-start'}),
pinnedDocOptions(icon('Dots'), testId('pinned-doc-options')),
] :
pinnedDocOptions(icon('Dots'),
menu(() => makeDocOptionsMenu(home, doc, renaming),
{placement: 'bottom-start'}),
// Clicks on the menu trigger shouldn't follow the link that it's contained in. // Clicks on the menu trigger shouldn't follow the link that it's contained in.
dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }), dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }),
testId('pinned-doc-options') testId('pinned-doc-options'),
)
), ),
testId('pinned-doc') testId('pinned-doc')
); );