Skip to content

Commit cfcedb3

Browse files
committed
Implementation of URL parameter linking to history
1 parent ff0856a commit cfcedb3

File tree

9 files changed

+78
-14
lines changed

9 files changed

+78
-14
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ There are a number of URL parameters that can aid in testing:
162162
|`noPersistentUI`|none |Do not initialize persistent ui store.|
163163
|`researcher` |`true` |When set to true the user authenticates as a researcher|
164164
|`studentDocument`|string |If set to the ID of a document, this will be displayed as the left-side content.|
165+
|`studentDocumentHistoryId`|string |Open the history slider and move to the specified revision in the `studentDocument`.|
165166

166167
The `unit` parameter can be in 3 forms:
167168

docs/firestore-schema.md

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ Fields:
7575

7676
### Contents of `documents/{docId}`
7777

78+
Holds metadata, history, and comments related to a user Document.
79+
(The actual current Document content is not stored here, it is in Firebase.)
80+
Note that there can be multiple Firestore records for a single Document,
81+
with different networks. These are needed to hold comments that can only
82+
be read by members of the network of their authors.
83+
7884
Fields:
7985

8086
- key: (string, the id of the document in firebase)

src/components/document/canvas.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ interface IState {
4747
documentScrollY: number;
4848
historyDocumentCopy?: DocumentModelType;
4949
showPlaybackControls: boolean;
50+
requestedHistoryId: string | undefined;
5051
}
5152

5253
@inject("stores")
@@ -80,11 +81,27 @@ export class CanvasComponent extends BaseComponent<IProps, IState> {
8081
documentScrollX: 0,
8182
documentScrollY: 0,
8283
showPlaybackControls: false,
84+
requestedHistoryId: undefined,
8385
};
8486

8587
this.canvasMethods = { cacheObjectBoundingBox: this.cacheObjectBoundingBox };
8688
}
8789

90+
componentDidMount(): void {
91+
// If there is a request to show this document at a point in its history, show the history slider.
92+
if (this.props.showPlayback && this.props.document?.key) {
93+
const request = this.stores.sortedDocuments.getDocumentHistoryViewRequest(this.props.document?.key);
94+
if (request) {
95+
this.setState((prevState, props) => {
96+
return {
97+
...this.updateHistoryDocument(prevState, true),
98+
requestedHistoryId: request
99+
};
100+
});
101+
}
102+
}
103+
}
104+
88105
private fallbackRender = ({ error, resetErrorBoundary }: FallbackProps) => {
89106
return (
90107
<DocumentError
@@ -205,6 +222,7 @@ export class CanvasComponent extends BaseComponent<IProps, IState> {
205222
document={documentToShow}
206223
showPlaybackControls={showPlaybackControls}
207224
onTogglePlaybackControls={this.handleTogglePlaybackControlComponent}
225+
requestedHistoryId={this.state.requestedHistoryId}
208226
/>
209227
)}
210228
</>

src/components/playback/playback.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import React from "react";
1+
import React, { useEffect } from "react";
22
import { Instance } from "mobx-state-tree";
33
import { observer } from "mobx-react";
44
import classNames from "classnames";
55
import { usePersistentUIStore } from "../../hooks/use-stores";
6-
import { HistoryStatus, TreeManager } from "../../models/history//tree-manager";
6+
import { HistoryStatus, TreeManager } from "../../models/history/tree-manager";
77
import { DocumentModelType } from "../../models/document/document";
88
import { PlaybackControlComponent } from "./playback-control";
99
import PlaybackIcon from "../../clue/assets/icons/playback/playback-icon.svg";
@@ -14,13 +14,20 @@ interface IProps {
1414
document: DocumentModelType | undefined;
1515
showPlaybackControls: boolean | undefined;
1616
onTogglePlaybackControls: (() => void) | undefined;
17+
requestedHistoryId: string | undefined;
1718
}
1819

1920
export const PlaybackComponent: React.FC<IProps> = observer((props: IProps) => {
2021
const { document, showPlaybackControls, onTogglePlaybackControls } = props;
2122
const { activeNavTab } = usePersistentUIStore();
2223
const treeManager = document?.treeManagerAPI as Instance<typeof TreeManager>;
2324

25+
useEffect(() => {
26+
if (props.requestedHistoryId) {
27+
treeManager.moveToHistoryEntryAfterLoad(props.requestedHistoryId);
28+
}
29+
}, [props.requestedHistoryId, treeManager]);
30+
2431
const renderPlaybackToolbarButton = () => {
2532
const playbackToolbarButtonComponentStyle =
2633
classNames("playback-toolbar-button-component", {"disabled" : false},

src/initialize-app.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ export const initializeApp = (authoring: boolean): IStores => {
7070

7171
const appConfig = AppConfigModel.create(appConfigSnapshot);
7272
const stores = createStores(
73-
{ appMode, appVersion, appConfig, user, showDemoCreator, demoName, documentToDisplay: urlParams.studentDocument });
73+
{ appMode, appVersion, appConfig, user, showDemoCreator, demoName,
74+
documentToDisplay: urlParams.studentDocument, documentHistoryId: urlParams.studentDocumentHistoryId });
7475

7576
// Expose the stores if the debug flag is set or we are running in Cypress
7677
const aWindow = window as any;

src/models/history/tree-manager.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
types, Instance, flow, IJsonPatch, detach, destroy, getSnapshot, toGenerator, addDisposer
33
} from "mobx-state-tree";
4+
import { when } from "mobx";
45
import firebase from "firebase/app";
56
import { nanoid } from "nanoid";
67
import { TreeAPI } from "./tree-api";
@@ -93,6 +94,10 @@ export const TreeManager = types
9394
return self.document.history.find(entry => entry.id === historyEntryId);
9495
},
9596

97+
findHistoryEntryIndex(historyEntryId: string) {
98+
return self.document.history.findIndex(entry => entry.id === historyEntryId);
99+
},
100+
96101
findActiveHistoryEntry(historyEntryId: string) {
97102
return self.activeHistoryEntries.find(entry => entry.id === historyEntryId);
98103
},
@@ -586,6 +591,17 @@ export const TreeManager = types
586591
self.numHistoryEventsApplied = newHistoryPosition;
587592
})
588593
}))
594+
.actions((self) => ({
595+
async moveToHistoryEntryAfterLoad(historyId: string) {
596+
await when(() => self.historyStatus === HistoryStatus.HISTORY_LOADED);
597+
const entry = self.findHistoryEntryIndex(historyId);
598+
if (entry >= 0) {
599+
self.goToHistoryEntry(entry);
600+
} else {
601+
console.warn("Did not find history entry with id: ", historyId);
602+
}
603+
}
604+
}))
589605
.views(self => ({
590606
getHistoryEntry: (historyIndex: number) => {
591607
return self.document.history.at(historyIndex);

src/models/stores/sorted-documents.ts

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { autorun, makeAutoObservable, runInAction } from "mobx";
1+
import { makeAutoObservable, runInAction, when } from "mobx";
22
import { types, Instance, SnapshotIn, applySnapshot, typecheck, unprotect } from "mobx-state-tree";
33
import { union } from "lodash";
44
import firebase from "firebase";
@@ -80,6 +80,8 @@ export class SortedDocuments {
8080
metadataDocsFiltered = MetadataDocMapModel.create();
8181
metadataDocsWithoutUnit = MetadataDocMapModel.create();
8282
docsReceived = false;
83+
// Maps from document ID to the history entry ID that the user requested to view.
84+
documentHistoryViewRequests: Record<string,string> = {};
8385

8486
constructor(stores: ISortedDocumentsStores) {
8587
makeAutoObservable(this);
@@ -117,6 +119,20 @@ export class SortedDocuments {
117119
return this.stores.user;
118120
}
119121

122+
setDocumentHistoryViewRequest(docKey: string, historyId: string) {
123+
this.documentHistoryViewRequests[docKey] = historyId;
124+
console.log("setDocumentHistoryViewRequest", docKey, historyId);
125+
}
126+
getDocumentHistoryViewRequest(docKey: string) {
127+
// We only want to move to this history entry once,
128+
// so we delete the request after it has been fulfilled.
129+
const historyId = this.documentHistoryViewRequests[docKey];
130+
if (historyId) {
131+
delete this.documentHistoryViewRequests[docKey];
132+
}
133+
return historyId;
134+
}
135+
120136
sortBy(sortType: PrimarySortType): DocumentGroup[] {
121137
switch (sortType) {
122138
case "Group":
@@ -356,15 +372,8 @@ export class SortedDocuments {
356372

357373
async fetchFullDocument(docKey: string) {
358374
if (!this.docsReceived) {
359-
// Wait until the initial documents have been received before trying to open a document.
360-
await new Promise<void>(resolve => {
361-
const disposer = autorun(() => {
362-
if (this.docsReceived) {
363-
disposer();
364-
resolve();
365-
}
366-
});
367-
});
375+
// Wait until the initial batch of documents has been received from Firestore.
376+
await when(() => this.docsReceived);
368377
}
369378
const metadataDoc = this.firestoreMetadataDocs.find(doc => doc.key === docKey);
370379
if (!metadataDoc) {

src/models/stores/stores.ts

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface IStores extends IBaseStores {
4343
userContextProvider: UserContextProvider;
4444
tabsToDisplay: NavTabModelType[];
4545
documentToDisplay?: string;
46+
documentHistoryId?: string;
4647
isShowingTeacherContent: boolean;
4748
studentWorkTabSelectedGroupId: string | undefined;
4849
setAppMode: (appMode: AppMode) => void;
@@ -169,6 +170,10 @@ class Stores implements IStores{
169170
if (docToDisplay) {
170171
// Make sure there is a Sort Work tab to display the document in.
171172
this.appConfig.setRequireSortWorkTab(true);
173+
// Set request for viewing at the given history ID, if provided
174+
if (params.documentHistoryId) {
175+
this.sortedDocuments.setDocumentHistoryViewRequest(docToDisplay, params.documentHistoryId);
176+
}
172177
// Wait until the document is loaded, then open it.
173178
const docPromise = this.sortedDocuments.fetchFullDocument(docToDisplay);
174179
docPromise.then((doc) => {

src/utilities/url-params.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ export interface QueryParams {
4646
reportType?: string;
4747
// set to string "true" to authenticate as a researcher
4848
researcher?: string;
49-
// display a certain student document
49+
// display a certain student document, optionally at a point in its history
5050
studentDocument?: string;
51+
studentDocumentHistoryId?: string
5152

5253
//
5354
// demo features

0 commit comments

Comments
 (0)