aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-07-10 21:18:46 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-07-10 21:18:46 -0400
commitd9e95cb63563c7f780a39c0db242c26be9807ff1 (patch)
treedb5ea52f525c6ca1fff3761d0a7400645a8f1a37 /src
parenta56eb6c5dde8a7015932faec094cbe83bf0a825b (diff)
parent2862912f09bcfa00a5db204d779afa778159997b (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web
Diffstat (limited to 'src')
-rw-r--r--src/client/util/History.ts136
-rw-r--r--src/client/util/SerializationHelper.ts2
-rw-r--r--src/client/views/MainView.tsx22
-rw-r--r--src/new_fields/util.ts1
4 files changed, 119 insertions, 42 deletions
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 545ea8629..1a807b581 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -2,6 +2,8 @@ import { Doc, Opt, Field } from "../../new_fields/Doc";
import { DocServer } from "../DocServer";
import { RouteStore } from "../../server/RouteStore";
import { MainView } from "../views/MainView";
+import * as qs from 'query-string';
+import { Utils, OmitKeys } from "../../Utils";
export namespace HistoryUtil {
export interface DocInitializerList {
@@ -11,9 +13,11 @@ export namespace HistoryUtil {
export interface DocUrl {
type: "doc";
docId: string;
- initializers: {
+ initializers?: {
[docId: string]: DocInitializerList;
};
+ readonly?: boolean;
+ nro?: boolean;
}
export type ParsedUrl = DocUrl;
@@ -21,7 +25,7 @@ export namespace HistoryUtil {
// 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);
+ const url = e.state as ParsedUrl || parseUrl(window.location);
if (url) {
switch (url.type) {
case "doc":
@@ -62,42 +66,111 @@ export namespace HistoryUtil {
// }
// }
- export function parseUrl(pathname: string): ParsedUrl | undefined {
- let pathnameSplit = pathname.split("/");
- if (pathnameSplit.length !== 2) {
- return undefined;
+ const parsers: { [type: string]: (pathname: string[], opts: qs.ParsedQuery) => ParsedUrl | undefined } = {};
+ const stringifiers: { [type: string]: (state: ParsedUrl) => string } = {};
+
+ type ParserValue = true | "none" | "json" | ((value: string) => any);
+
+ type Parser = {
+ [key: string]: ParserValue
+ };
+
+ function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: qs.ParsedQuery, current: ParsedUrl) => ParsedUrl | null | undefined) {
+ function parse(parser: ParserValue, value: string | string[] | null | undefined) {
+ if (value === undefined || value === null) {
+ return value;
+ }
+ if (Array.isArray(value)) {
+ } else if (parser === true || parser === "json") {
+ value = JSON.parse(value);
+ } else if (parser === "none") {
+ } else {
+ value = parser(value);
+ }
+ return value;
}
- const type = pathnameSplit[0];
- const data = pathnameSplit[1];
+ parsers[type] = (pathname, opts) => {
+ const current: any = { type };
+ for (const required in requiredFields) {
+ if (!(required in opts)) {
+ return undefined;
+ }
+ const parser = requiredFields[required];
+ let value = opts[required];
+ value = parse(parser, value);
+ if (value !== null && value !== undefined) {
+ current[required] = value;
+ }
+ }
+ for (const opt in optionalFields) {
+ if (!(opt in opts)) {
+ continue;
+ }
+ const parser = optionalFields[opt];
+ let value = opts[opt];
+ value = parse(parser, value);
+ if (value !== undefined) {
+ current[opt] = value;
+ }
+ }
+ if (customParser) {
+ const val = customParser(pathname, opts, current);
+ if (val === null) {
+ return undefined;
+ } else if (val === undefined) {
+ return current;
+ } else {
+ return val;
+ }
+ }
+ return current;
+ };
+ }
- if (type === "doc") {
- const s = data.split("?");
- if (s.length < 1 || s.length > 2) {
- return undefined;
+ function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
+ stringifiers[type] = state => {
+ let path = DocServer.prepend(`/${type}`);
+ if (customStringifier) {
+ path = customStringifier(state, path);
}
- const docId = s[0];
- const initializers = s.length === 2 ? JSON.parse(decodeURIComponent(s[1])) : {};
- return {
- type: "doc",
- docId,
- initializers
- };
+ const queryObj = OmitKeys(state, keys).extract;
+ const query: any = {};
+ Object.keys(queryObj).forEach(key => query[key] = queryObj[key] === null ? null : queryObj[key]);
+ const queryString = qs.stringify(query);
+ return path + (queryString ? `?${queryString}` : "");
+ };
+ }
+
+ addParser("doc", {}, { readonly: true, initializers: true, nro: true }, (pathname, opts, current) => {
+ if (pathname.length !== 2) return undefined;
+
+ current.initializers = current.initializers || {};
+ const docId = pathname[1];
+ current.docId = docId;
+ });
+ addStringifier("doc", ["initializers", "readonly", "nro"], (state, current) => {
+ return `${current}/${state.docId}`;
+ });
+
+
+ export function parseUrl(location: Location | URL): ParsedUrl | undefined {
+ const pathname = location.pathname.substring(1);
+ const search = location.search;
+ const opts = qs.parse(search, { sort: false });
+ let pathnameSplit = pathname.split("/");
+
+ const type = pathnameSplit[0];
+
+ if (type in parsers) {
+ return parsers[type](pathnameSplit, opts);
}
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;
+ if (params.type in stringifiers) {
+ return stringifiers[params.type](params);
}
return "";
}
@@ -112,7 +185,10 @@ export namespace HistoryUtil {
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])));
+ const init = url.initializers;
+ if (init) {
+ await Promise.all(Object.keys(init).map(id => initDoc(id, init[id])));
+ }
if (field instanceof Doc) {
MainView.Instance.openWorkspace(field, true);
}
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index a7246d7c4..17ae407c4 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -9,7 +9,7 @@ export namespace SerializationHelper {
export function Serialize(obj: Field): any {
if (obj === undefined || obj === null) {
- return undefined;
+ return null;
}
if (typeof obj !== 'object') {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dd07fdc3a..bf4ee84d6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -93,15 +93,6 @@ export class MainView extends React.Component {
MainView.Instance = this;
// causes errors to be generated when modifying an observable outside of an action
configure({ enforceActions: "observed" });
- if (window.location.search.includes("readonly")) {
- DocServer.makeReadOnly();
- }
- if (window.location.search.includes("safe")) {
- if (!window.location.search.includes("nro")) {
- DocServer.makeReadOnly();
- }
- CollectionBaseView.SetSafeMode(true);
- }
if (window.location.pathname !== RouteStore.home) {
let pathname = window.location.pathname.substr(1).split("/");
if (pathname.length > 1) {
@@ -194,12 +185,21 @@ export class MainView extends React.Component {
openWorkspace = async (doc: Doc, fromHistory = false) => {
CurrentUserUtils.MainDocId = doc[Id];
this.mainContainer = doc;
- if (BoolCast(doc.readOnly)) {
+ const state = HistoryUtil.parseUrl(window.location) || {} as any;
+ fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], readonly: state.readonly, nro: state.nro });
+ if (state.readonly === true || state.readonly === null) {
+ DocServer.makeReadOnly();
+ } else if (state.safe) {
+ if (!state.nro) {
+ DocServer.makeReadOnly();
+ }
+ CollectionBaseView.SetSafeMode(true);
+ } else if (state.nro || state.nro === null || state.readonly === false) {
+ } else if (BoolCast(doc.readOnly)) {
DocServer.makeReadOnly();
} else {
DocServer.makeEditable();
}
- 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/new_fields/util.ts b/src/new_fields/util.ts
index b5e50d501..47e467041 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -50,6 +50,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
target.__fields[prop] = value;
}
if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
+ if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
UndoManager.AddEvent({
redo: () => receiver[prop] = value,