diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/History.ts | 122 | ||||
-rw-r--r-- | src/client/views/Main.tsx | 19 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 36 | ||||
-rw-r--r-- | src/new_fields/Doc.ts | 7 |
5 files changed, 159 insertions, 27 deletions
diff --git a/src/client/util/History.ts b/src/client/util/History.ts new file mode 100644 index 000000000..92d2b2b44 --- /dev/null +++ b/src/client/util/History.ts @@ -0,0 +1,122 @@ +import { Doc, Opt, Field } from "../../new_fields/Doc"; +import { DocServer } from "../DocServer"; +import { Main } from "../views/Main"; +import { RouteStore } from "../../server/RouteStore"; + +export namespace HistoryUtil { + export interface DocInitializerList { + [key: string]: string | number; + } + + export interface DocUrl { + type: "doc"; + docId: string; + initializers: { + [docId: string]: DocInitializerList; + }; + } + + export type ParsedUrl = DocUrl; + + // const handlers: ((state: ParsedUrl | null) => void)[] = []; + function onHistory(e: PopStateEvent) { + if (window.location.pathname !== RouteStore.home) { + const url = e.state as ParsedUrl || parseUrl(window.location.pathname); + if (url) { + switch (url.type) { + case "doc": + onDocUrl(url); + break; + } + } + } + // for (const handler of handlers) { + // handler(e.state); + // } + } + + export function pushState(state: ParsedUrl) { + history.pushState(state, "", createUrl(state)); + } + + export function replaceState(state: ParsedUrl) { + history.replaceState(state, "", createUrl(state)); + } + + function copyState(state: ParsedUrl): ParsedUrl { + return JSON.parse(JSON.stringify(state)); + } + + export function getState(): ParsedUrl { + return copyState(history.state); + } + + // export function addHandler(handler: (state: ParsedUrl | null) => void) { + // handlers.push(handler); + // } + + // export function removeHandler(handler: (state: ParsedUrl | null) => void) { + // const index = handlers.indexOf(handler); + // if (index !== -1) { + // handlers.splice(index, 1); + // } + // } + + export function parseUrl(pathname: string): ParsedUrl | undefined { + let pathnameSplit = pathname.split("/"); + if (pathnameSplit.length !== 2) { + return undefined; + } + const type = pathnameSplit[0]; + const data = pathnameSplit[1]; + + if (type === "doc") { + const s = data.split("?"); + if (s.length < 1 || s.length > 2) { + return undefined; + } + const docId = s[0]; + const initializers = s.length === 2 ? JSON.parse(decodeURIComponent(s[1])) : {}; + return { + type: "doc", + docId, + initializers + }; + } + + return undefined; + } + + export function createUrl(params: ParsedUrl): string { + let baseUrl = DocServer.prepend(`/${params.type}`); + switch (params.type) { + case "doc": + const initializers = encodeURIComponent(JSON.stringify(params.initializers)); + const id = params.docId; + let url = baseUrl + `/${id}`; + if (Object.keys(params.initializers).length) { + url += `?${initializers}`; + } + return url; + } + return ""; + } + + export async function initDoc(id: string, initializer: DocInitializerList) { + const doc = await DocServer.GetRefField(id); + if (!(doc instanceof Doc)) { + return; + } + Doc.assign(doc, initializer); + } + + async function onDocUrl(url: DocUrl) { + const field = await DocServer.GetRefField(url.docId); + await Promise.all(Object.keys(url.initializers).map(id => initDoc(id, url.initializers[id]))); + if (field instanceof Doc) { + Main.Instance.openWorkspace(field, true); + } + } + + window.onpopstate = onHistory; +} diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 66205f8ca..55252ab1d 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -38,6 +38,8 @@ import { Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { DocServer } from '../DocServer'; import { listSpec } from '../../new_fields/Schema'; import { Id } from '../../new_fields/RefField'; +import { HistoryUtil } from '../util/History'; + @observer export class Main extends React.Component { @@ -95,21 +97,6 @@ export class Main extends React.Component { // } } - componentDidMount() { window.onpopstate = this.onHistory; } - - componentWillUnmount() { window.onpopstate = null; } - - onHistory = () => { - if (window.location.pathname !== RouteStore.home) { - let pathname = window.location.pathname.split("/"); - DocServer.GetRefField(pathname[pathname.length - 1]).then(action((field: Opt<Field>) => { - if (field instanceof Doc) { - this.openWorkspace(field, true); - } - })); - } - } - initEventListeners = () => { // window.addEventListener("pointermove", (e) => this.reportLocation(e)) window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler @@ -165,7 +152,7 @@ export class Main extends React.Component { openWorkspace = async (doc: Doc, fromHistory = false) => { CurrentUserUtils.MainDocId = doc[Id]; this.mainContainer = doc; - fromHistory || window.history.pushState(null, StrCast(doc.title), "/doc/" + doc[Id]); + fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} }); const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc); // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized) setTimeout(async () => { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6651a834d..58f1e33a1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -190,7 +190,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } reactionDisposer?: Lambda; componentDidMount: () => void = () => { - console.log("Docking mount"); if (this._containerRef.current) { this.reactionDisposer = reaction( () => StrCast(this.props.Document.dockingConfig), @@ -207,7 +206,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } } componentWillUnmount: () => void = () => { - console.log("Docking unmount"); try { this._goldenLayout.unbind('itemDropped', this.itemDropped); this._goldenLayout.unbind('tabCreated', this.tabCreated); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c36c708db..e60bb2fb2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -24,6 +24,7 @@ import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types"; import { pageSchema } from "../../nodes/ImageBox"; import { Id } from "../../../../new_fields/RefField"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { HistoryUtil } from "../../../util/History"; export const panZoomSchema = createSchema({ panX: "number", @@ -219,6 +220,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number) { + this.panDisposer && clearTimeout(this.panDisposer); + this.props.Document.panTransformType = "None"; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); @@ -245,16 +248,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { doc.zIndex = docs.length + 1; } + panDisposer?: NodeJS.Timeout; focusDocument = (doc: Doc) => { + const panX = this.Document.panX; + const panY = this.Document.panY; + const id = this.Document[Id]; + const state = HistoryUtil.getState(); + // TODO This technically isn't correct if type !== "doc", as + // currently nothing is done, but we should probably push a new state + if (state.type === "doc" && panX !== undefined && panY !== undefined) { + const init = state.initializers[id]; + if (!init) { + state.initializers[id] = { + panX, panY + }; + HistoryUtil.pushState(state); + } else if (init.panX !== panX || init.panY !== panY) { + init.panX = panX; + init.panY = panY; + HistoryUtil.pushState(state); + } + } SelectionManager.DeselectAll(); + const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; + const newState = HistoryUtil.getState(); + newState.initializers[id] = { panX: newPanX, panY: newPanY }; + HistoryUtil.pushState(newState); + this.setPan(newPanX, newPanY); this.props.Document.panTransformType = "Ease"; - this.setPan( - NumCast(doc.x) + NumCast(doc.width) / 2, - NumCast(doc.y) + NumCast(doc.height) / 2); this.props.focus(this.props.Document); - if (this.props.Document.panTransformType === "Ease") { - setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false - } + this.panDisposer = setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 89901490d..d6043ef7a 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -159,9 +159,10 @@ export namespace Doc { for (const key in fields) { if (fields.hasOwnProperty(key)) { const value = fields[key]; - if (value !== undefined) { - doc[key] = value; - } + // Do we want to filter out undefineds? + // if (value !== undefined) { + doc[key] = value; + // } } } return doc; |