gristlabs_grist-core/app/client/ui/DocHistory.ts
Dmitry S 51ff72c15e (core) Faster builds all around.
Summary:
Building:
- Builds no longer wait for tsc for either client, server, or test targets. All use esbuild which is very fast.
- Build still runs tsc, but only to report errors. This may be turned off with `SKIP_TSC=1` env var.
- Grist-core continues to build using tsc.
- Esbuild requires ES6 module semantics. Typescript's esModuleInterop is turned
  on, so that tsc accepts and enforces correct usage.
- Client-side code is watched and bundled by webpack as before (using esbuild-loader)

Code changes:
- Imports must now follow ES6 semantics: `import * as X from ...` produces a
  module object; to import functions or class instances, use `import X from ...`.
- Everything is now built with isolatedModules flag. Some exports were updated for it.

Packages:
- Upgraded browserify dependency, and related packages (used for the distribution-building step).
- Building the distribution now uses esbuild's minification. babel-minify is no longer used.

Test Plan: Should have no behavior changes, existing tests should pass, and docker image should build too.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: alexmojaki

Differential Revision: https://phab.getgrist.com/D3506
2022-07-04 10:42:40 -04:00

158 lines
5.4 KiB
TypeScript

import {createSessionObs} from 'app/client/lib/sessionObs';
import {DocPageModel} from 'app/client/models/DocPageModel';
import {reportError} from 'app/client/models/errors';
import {urlState} from 'app/client/models/gristUrlState';
import {getTimeFromNow} from 'app/client/models/HomeModel';
import {buildConfigContainer} from 'app/client/ui/RightPanel';
import {buttonSelect} from 'app/client/ui2018/buttonSelect';
import {colors, testId, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {menu, menuAnnotate, menuItemLink} from 'app/client/ui2018/menus';
import {buildUrlId, parseUrlId} from 'app/common/gristUrls';
import {StringUnion} from 'app/common/StringUnion';
import {DocSnapshot} from 'app/common/UserAPI';
import {Disposable, dom, IDomComponent, MultiHolder, Observable, styled} from 'grainjs';
import moment from 'moment';
const DocHistorySubTab = StringUnion("activity", "snapshots");
export class DocHistory extends Disposable implements IDomComponent {
private _subTab = createSessionObs(this, "docHistorySubTab", "snapshots", DocHistorySubTab.guard);
constructor(private _docPageModel: DocPageModel, private _actionLog: IDomComponent) {
super();
}
public buildDom() {
const tabs = [
{value: 'activity', label: 'Activity'},
{value: 'snapshots', label: 'Snapshots'},
];
return [
cssSubTabs(
buttonSelect(this._subTab, tabs, {}, testId('doc-history-tabs')),
),
dom.domComputed(this._subTab, (subTab) =>
buildConfigContainer(
subTab === 'activity' ? this._actionLog.buildDom() :
subTab === 'snapshots' ? dom.create(this._buildSnapshots.bind(this)) :
null
)
),
];
}
private _buildSnapshots(owner: MultiHolder) {
// Fetch snapshots, and render.
const doc = this._docPageModel.currentDoc.get();
if (!doc) { return null; }
// origUrlId is the snapshot-less URL, which we use to fetch snapshot history, and for
// snapshot comparisons.
const origUrlId = buildUrlId({...doc.idParts, snapshotId: undefined});
// If comparing one snapshot to another, get the other ID, so that we can highlight it too.
const compareUrlId = urlState().state.get().params?.compare;
const compareSnapshotId = compareUrlId && parseUrlId(compareUrlId).snapshotId;
// Helper to set a link to open a snapshot, optionally comparing it with a docId.
// We include urlState().state to preserve the currently selected page.
function setLink(snapshot: DocSnapshot, compareDocId?: string) {
return dom.attr('href', (use) => urlState().makeUrl({
...use(urlState().state), doc: snapshot.docId,
params: (compareDocId ? {compare: compareDocId} : {})
}));
}
const snapshots = Observable.create<DocSnapshot[]>(owner, []);
const userApi = this._docPageModel.appModel.api;
const docApi = userApi.getDocAPI(origUrlId);
docApi.getSnapshots().then(result =>
snapshots.isDisposed() || snapshots.set(result.snapshots)).catch(reportError);
return dom('div',
// Note that most recent snapshots are first.
dom.domComputed(snapshots, (snapshotList) => snapshotList.map((snapshot, index) => {
const modified = moment(snapshot.lastModified);
const prevSnapshot = snapshotList[index + 1] || null;
return cssSnapshot(
cssSnapshotTime(getTimeFromNow(snapshot.lastModified)),
cssSnapshotCard(
cssSnapshotCard.cls('-current', Boolean(
snapshot.snapshotId === doc.idParts.snapshotId ||
(compareSnapshotId && snapshot.snapshotId === compareSnapshotId)
)),
dom('div',
cssDatePart(modified.format('ddd ll')), ' ',
cssDatePart(modified.format('LT'))
),
cssMenuDots(icon('Dots'),
menu(() => [
menuItemLink(setLink(snapshot), 'Open Snapshot'),
menuItemLink(setLink(snapshot, origUrlId), 'Compare to Current',
menuAnnotate('Beta')),
prevSnapshot && menuItemLink(setLink(prevSnapshot, snapshot.docId), 'Compare to Previous',
menuAnnotate('Beta')),
],
{placement: 'bottom-end', parentSelectorToMark: '.' + cssSnapshotCard.className}
),
testId('doc-history-snapshot-menu'),
),
testId('doc-history-card'),
),
testId('doc-history-snapshot'),
);
})),
);
}
}
const cssSubTabs = styled('div', `
padding: 16px;
border-bottom: 1px solid ${colors.mediumGrey};
`);
const cssSnapshot = styled('div', `
margin: 8px 16px;
`);
const cssSnapshotTime = styled('div', `
text-align: right;
color: ${colors.slate};
font-size: ${vars.smallFontSize};
`);
const cssSnapshotCard = styled('div', `
border: 1px solid ${colors.mediumGrey};
padding: 8px;
background: white;
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
--icon-color: ${colors.slate};
&-current {
background-color: ${colors.dark};
color: ${colors.light};
--icon-color: ${colors.light};
}
`);
const cssDatePart = styled('span', `
display: inline-block;
`);
const cssMenuDots = styled('div', `
flex: none;
margin: 0 4px 0 auto;
height: 24px;
width: 24px;
padding: 4px;
line-height: 0px;
border-radius: 3px;
cursor: default;
&:hover, &.weasel-popup-open {
background-color: ${colors.mediumGrey};
}
`);