mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) move client code to core
Summary: This moves all client code to core, and makes minimal fix-ups to get grist and grist-core to compile correctly. The client works in core, but I'm leaving clean-up around the build and bundles to follow-up. Test Plan: existing tests pass; server-dev bundle looks sane Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2627
This commit is contained in:
235
app/client/ui/PinnedDocs.ts
Normal file
235
app/client/ui/PinnedDocs.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import {docUrl, urlState} from 'app/client/models/gristUrlState';
|
||||
import {getTimeFromNow, HomeModel} from 'app/client/models/HomeModel';
|
||||
import {makeDocOptionsMenu} from 'app/client/ui/DocMenu';
|
||||
import {IExampleInfo} from 'app/client/ui/ExampleInfo';
|
||||
import {transientInput} from 'app/client/ui/transientInput';
|
||||
import {colors, vars} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {menu} from 'app/client/ui2018/menus';
|
||||
import * as roles from 'app/common/roles';
|
||||
import {Document} from 'app/common/UserAPI';
|
||||
import {computed, dom, makeTestId, observable, styled} from 'grainjs';
|
||||
|
||||
const testId = makeTestId('test-dm-');
|
||||
|
||||
/**
|
||||
* PinnedDocs builds the dom at the top of the doclist showing all the pinned docs in the
|
||||
* selectedOrg. Builds nothing if there are no pinned docs.
|
||||
*
|
||||
* Used only by DocMenu.
|
||||
*/
|
||||
export function createPinnedDocs(home: HomeModel) {
|
||||
return pinnedDocList(
|
||||
dom.forEach(home.currentWSPinnedDocs, doc => buildPinnedDoc(home, doc)),
|
||||
testId('pinned-doc-list'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function buildPinnedDoc(home: HomeModel, doc: Document, example?: IExampleInfo): HTMLElement {
|
||||
const renaming = observable<Document|null>(null);
|
||||
const isRenaming = computed((use) => use(renaming) === doc);
|
||||
const docTitle = example?.title || doc.name;
|
||||
return pinnedDocWrapper(
|
||||
dom.autoDispose(isRenaming),
|
||||
pinnedDoc(
|
||||
urlState().setLinkUrl(docUrl(doc)),
|
||||
pinnedDoc.cls('-no-access', !roles.canView(doc.access)),
|
||||
pinnedDocPreview(
|
||||
example?.bgColor ? dom.style('background-color', example.bgColor) : null,
|
||||
(example?.imgUrl ?
|
||||
cssImage({src: example.imgUrl}) :
|
||||
[docInitials(docTitle), pinnedDocThumbnail()]
|
||||
),
|
||||
(doc.public && !example ? cssPublicIcon('PublicFilled', testId('public')) : null),
|
||||
),
|
||||
pinnedDocFooter(
|
||||
dom.domComputed(isRenaming, (yesNo) => yesNo ?
|
||||
pinnedDocEditorInput({
|
||||
initialValue: doc.name || '',
|
||||
save: async (val) => (val !== doc.name) ? home.renameDoc(doc.id, val) : undefined,
|
||||
close: () => renaming.set(null),
|
||||
}, testId('doc-name-editor'))
|
||||
:
|
||||
pinnedDocTitle(
|
||||
dom.text(docTitle),
|
||||
testId('pinned-doc-name'),
|
||||
// Mostly for the sake of tests, allow .test-dm-pinned-doc-name to find documents in
|
||||
// either 'list' or 'icons' views.
|
||||
testId('doc-name')
|
||||
),
|
||||
),
|
||||
cssPinnedDocDesc(
|
||||
example?.desc || capitalizeFirst(getTimeFromNow(doc.updatedAt)),
|
||||
testId('pinned-doc-desc')
|
||||
)
|
||||
)
|
||||
),
|
||||
example ? null : pinnedDocOptions(icon('Dots'),
|
||||
menu(() => makeDocOptionsMenu(home, doc, renaming), {placement: 'bottom-start'}),
|
||||
testId('pinned-doc-options')
|
||||
),
|
||||
testId('pinned-doc')
|
||||
);
|
||||
}
|
||||
|
||||
function docInitials(docTitle: string) {
|
||||
return cssDocInitials(docTitle.slice(0, 2), testId('pinned-initials'));
|
||||
}
|
||||
|
||||
// Capitalizes the first letter in the given string.
|
||||
function capitalizeFirst(str: string): string {
|
||||
return str.replace(/^[a-z]/gi, c => c.toUpperCase());
|
||||
}
|
||||
|
||||
const pinnedDocList = styled('div', `
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: 16px;
|
||||
margin: 0 0 28px 0;
|
||||
`);
|
||||
|
||||
export const pinnedDocWrapper = styled('div', `
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
width: 210px;
|
||||
margin: 16px 24px 16px 0;
|
||||
border: 1px solid ${colors.mediumGrey};
|
||||
border-radius: 1px;
|
||||
vertical-align: top;
|
||||
&:hover {
|
||||
border: 1px solid ${colors.slate};
|
||||
}
|
||||
`);
|
||||
|
||||
const pinnedDoc = styled('a', `
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
&-no-access, &-no-access:hover {
|
||||
color: ${colors.slate};
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`);
|
||||
|
||||
const pinnedDocPreview = styled('div', `
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 100%;
|
||||
height: 131px;
|
||||
background-color: ${colors.dark};
|
||||
min-height: 0;
|
||||
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.${pinnedDoc.className}-no-access > & {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`);
|
||||
|
||||
const pinnedDocThumbnail = styled('div', `
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
background-image: var(--icon-ThumbPreview);
|
||||
background-size: 48px 48px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`);
|
||||
|
||||
const cssDocInitials = styled('div', `
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
font-size: 32px;
|
||||
border: 1px solid ${colors.lightGreen};
|
||||
color: ${colors.mediumGreyOpaque};
|
||||
border-radius: 3px;
|
||||
padding: 4px 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
`);
|
||||
|
||||
const pinnedDocOptions = styled('div', `
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
padding: 4px;
|
||||
line-height: 0px;
|
||||
border-radius: 3px;
|
||||
cursor: default;
|
||||
visibility: hidden;
|
||||
background-color: ${colors.mediumGrey};
|
||||
--icon-color: ${colors.light};
|
||||
|
||||
.${pinnedDocWrapper.className}:hover &, &.weasel-popup-open {
|
||||
visibility: visible;
|
||||
}
|
||||
`);
|
||||
|
||||
const pinnedDocFooter = styled('div', `
|
||||
width: 100%;
|
||||
font-size: ${vars.mediumFontSize};
|
||||
`);
|
||||
|
||||
const pinnedDocTitle = styled('div', `
|
||||
margin: 16px 16px 0px 16px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`);
|
||||
|
||||
export const pinnedDocEditorInput = styled(transientInput, `
|
||||
margin: 16px 16px 0px 16px;
|
||||
font-weight: bold;
|
||||
min-width: 0px;
|
||||
color: initial;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: ${colors.mediumGrey};
|
||||
`);
|
||||
|
||||
const cssPinnedDocDesc = styled('div', `
|
||||
margin: 8px 16px 16px 16px;
|
||||
color: ${colors.slate};
|
||||
`);
|
||||
|
||||
const cssImage = styled('img', `
|
||||
position: relative;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
`);
|
||||
|
||||
const cssPublicIcon = styled(icon, `
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
--icon-color: ${colors.lightGreen};
|
||||
`);
|
||||
Reference in New Issue
Block a user