aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/launch.json4
-rw-r--r--src/client/DocServer.ts20
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts184
-rw-r--r--src/client/documents/Documents.ts20
-rw-r--r--src/client/util/Scripting.ts50
-rw-r--r--src/client/util/SerializationHelper.ts33
-rw-r--r--src/client/util/type_decls.d2
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/EditableView.tsx4
-rw-r--r--src/client/views/GlobalKeyHandler.ts4
-rw-r--r--src/client/views/InkingCanvas.tsx4
-rw-r--r--src/client/views/InkingControl.tsx6
-rw-r--r--src/client/views/MainView.tsx7
-rw-r--r--src/client/views/OverlayView.scss2
-rw-r--r--src/client/views/ScriptingRepl.tsx61
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss15
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx36
-rw-r--r--src/client/views/collections/CollectionStackingView.scss42
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx23
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx26
-rw-r--r--src/client/views/collections/CollectionTreeView.scss27
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx120
-rw-r--r--src/client/views/collections/CollectionView.tsx29
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss255
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx249
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx115
-rw-r--r--src/client/views/nodes/ButtonBox.scss10
-rw-r--r--src/client/views/nodes/ButtonBox.tsx75
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx3
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.tsx45
-rw-r--r--src/client/views/nodes/FieldView.tsx3
-rw-r--r--src/client/views/nodes/ImageBox.tsx17
-rw-r--r--src/client/views/presentationview/PresentationView.tsx31
-rw-r--r--src/client/views/search/SearchItem.tsx6
-rw-r--r--src/new_fields/Doc.ts96
-rw-r--r--src/new_fields/FieldSymbols.ts2
-rw-r--r--src/new_fields/InkField.ts6
-rw-r--r--src/new_fields/List.ts4
-rw-r--r--src/new_fields/Proxy.ts4
-rw-r--r--src/new_fields/RefField.ts5
-rw-r--r--src/new_fields/ScriptField.ts22
-rw-r--r--src/scraping/buxton/scraper.py40
-rw-r--r--src/server/authentication/models/current_user_utils.ts4
-rw-r--r--src/server/index.ts29
46 files changed, 1079 insertions, 671 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index e4196600e..822a06024 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -25,6 +25,10 @@
{
"type": "chrome",
"request": "launch",
+ "runtimeArgs": [
+ "--enable-logging",
+ "--v=1"
+ ],
"name": "Launch Chrome against Dash server",
"sourceMaps": true,
"breakOnLoad": true,
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 8c64d2b2f..cb460799f 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,7 +5,6 @@ import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../new_fields/RefField';
import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
-import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -26,7 +25,6 @@ export namespace DocServer {
// this client's distinct GUID created at initialization
let GUID: string;
// indicates whether or not a document is currently being udpated, and, if so, its id
- let updatingId: string | undefined;
export function init(protocol: string, hostname: string, port: number, identifier: string) {
_cache = {};
@@ -126,12 +124,11 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
- const field = SerializationHelper.Deserialize(fieldJson);
+ const field = await SerializationHelper.Deserialize(fieldJson);
// either way, overwrite or delete any promises cached at this id (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
// should be an actual value within or entirely absent from the cache.
if (field !== undefined) {
- await field.proto;
_cache[id] = field;
} else {
delete _cache[id];
@@ -202,18 +199,18 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
const deserializeFields = getSerializedFields.then(async fields => {
const fieldMap: { [id: string]: RefField } = {};
- const protosToLoad: any = [];
+ // const protosToLoad: any = [];
for (const field of fields) {
if (field !== undefined) {
// deserialize
- let deserialized: any = SerializationHelper.Deserialize(field);
+ let deserialized = await SerializationHelper.Deserialize(field);
fieldMap[field.id] = deserialized;
// adds to a list of promises that will be awaited asynchronously
- protosToLoad.push(deserialized.proto);
+ // protosToLoad.push(deserialized.proto);
}
}
// this actually handles the loading of prototypes
- await Promise.all(protosToLoad);
+ // await Promise.all(protosToLoad);
return fieldMap;
});
@@ -304,9 +301,6 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- if (id === updatingId) {
- return;
- }
Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
@@ -329,11 +323,7 @@ export namespace DocServer {
// extract this Doc's update handler
const handler = f[HandleUpdate];
if (handler) {
- // set the 'I'm currently updating this Doc' flag
- updatingId = id;
handler.call(f, diff.diff);
- // reset to indicate no ongoing updates
- updatingId = undefined;
}
};
// check the cache for the field
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d4085cf76..d69378d0e 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,5 +1,5 @@
import * as request from "request-promise";
-import { Doc, Field } from "../../new_fields/Doc";
+import { Doc, Field, Opt } from "../../new_fields/Doc";
import { Cast } from "../../new_fields/Types";
import { ImageField } from "../../new_fields/URLField";
import { List } from "../../new_fields/List";
@@ -8,10 +8,21 @@ import { RouteStore } from "../../server/RouteStore";
import { Utils } from "../../Utils";
import { CompileScript } from "../util/Scripting";
import { ComputedField } from "../../new_fields/ScriptField";
+import { InkData } from "../../new_fields/InkField";
-export enum Services {
+type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor, analyzer: AnalysisApplier };
+type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
+type AnalysisApplier = (target: Doc, relevantKeys: string[], ...args: any) => any;
+type BodyConverter<D> = (data: D) => string;
+type Converter = (results: any) => Field;
+
+export type Tag = { name: string, confidence: number };
+export type Rectangle = { top: number, left: number, width: number, height: number };
+
+export enum Service {
ComputerVision = "vision",
- Face = "face"
+ Face = "face",
+ Handwriting = "handwriting"
}
export enum Confidence {
@@ -23,11 +34,6 @@ export enum Confidence {
Excellent = 0.95
}
-export type Tag = { name: string, confidence: number };
-export type Rectangle = { top: number, left: number, width: number, height: number };
-export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
-export type Converter = (results: any) => Field;
-
/**
* A file that handles all interactions with Microsoft Azure's Cognitive
* Services APIs. These machine learning endpoints allow basic data analytics for
@@ -35,19 +41,36 @@ export type Converter = (results: any) => Field;
*/
export namespace CognitiveServices {
+ const executeQuery = async <D, R>(service: Service, manager: APIManager<D>, data: D): Promise<Opt<R>> => {
+ return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
+ let apiKey = await response.text();
+ if (!apiKey) {
+ console.log(`No API key found for ${service}: ensure index.ts has access to a .env file in your root directory`);
+ return undefined;
+ }
+
+ let results: Opt<R>;
+ try {
+ results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json));
+ } catch {
+ results = undefined;
+ }
+ return results;
+ });
+ };
+
export namespace Image {
- export const analyze = async (imageUrl: string, service: Services) => {
- return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
- let apiKey = await response.text();
- if (!apiKey) {
- return undefined;
- }
+ export const Manager: APIManager<string> = {
+
+ converter: (imageUrl: string) => JSON.stringify({ url: imageUrl }),
+
+ requester: async (apiKey: string, body: string, service: Service) => {
let uriBase;
let parameters;
switch (service) {
- case Services.Face:
+ case Service.Face:
uriBase = 'face/v1.0/detect';
parameters = {
'returnFaceId': 'true',
@@ -56,7 +79,7 @@ export namespace CognitiveServices {
'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise'
};
break;
- case Services.ComputerVision:
+ case Service.ComputerVision:
uriBase = 'vision/v2.0/analyze';
parameters = {
'visualFeatures': 'Categories,Description,Color,Objects,Tags,Adult',
@@ -69,42 +92,40 @@ export namespace CognitiveServices {
const options = {
uri: 'https://eastus.api.cognitive.microsoft.com/' + uriBase,
qs: parameters,
- body: `{"url": "${imageUrl}"}`,
+ body: body,
headers: {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': apiKey
}
};
- let results: any;
- try {
- results = await request.post(options).then(response => JSON.parse(response));
- } catch (e) {
- results = undefined;
- }
- return results;
- });
- };
+ return request.post(options);
+ },
- const analyzeDocument = async (target: Doc, service: Services, converter: Converter, storageKey: string) => {
- let imageData = Cast(target.data, ImageField);
- if (!imageData || await Cast(target[storageKey], Doc)) {
- return;
- }
- let toStore: any;
- let results = await analyze(imageData.url.href, service);
- if (!results) {
- toStore = "Cognitive Services could not process the given image URL.";
- } else {
- if (!results.length) {
- toStore = converter(results);
+ analyzer: async (target: Doc, keys: string[], service: Service, converter: Converter) => {
+ let imageData = Cast(target.data, ImageField);
+ let storageKey = keys[0];
+ if (!imageData || await Cast(target[storageKey], Doc)) {
+ return;
+ }
+ let toStore: any;
+ let results = await executeQuery<string, any>(service, Manager, imageData.url.href);
+ if (!results) {
+ toStore = "Cognitive Services could not process the given image URL.";
} else {
- toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+ if (!results.length) {
+ toStore = converter(results);
+ } else {
+ toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+ }
}
+ target[storageKey] = toStore;
}
- target[storageKey] = toStore;
+
};
+ export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
+
export const generateMetadata = async (target: Doc, threshold: Confidence = Confidence.Excellent) => {
let converter = (results: any) => {
let tagDoc = new Doc;
@@ -118,7 +139,7 @@ export namespace CognitiveServices {
tagDoc.confidence = threshold;
return tagDoc;
};
- analyzeDocument(target, Services.ComputerVision, converter, "generatedTags");
+ Manager.analyzer(target, ["generatedTags"], Service.ComputerVision, converter);
};
export const extractFaces = async (target: Doc) => {
@@ -127,9 +148,88 @@ export namespace CognitiveServices {
results.map((face: Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!));
return faceDocs;
};
- analyzeDocument(target, Services.Face, converter, "faces");
+ Manager.analyzer(target, ["faces"], Service.Face, converter);
+ };
+
+ }
+
+ export namespace Inking {
+
+ export const Manager: APIManager<InkData> = {
+
+ converter: (inkData: InkData): string => {
+ let entries = inkData.entries(), next = entries.next();
+ let strokes: AzureStrokeData[] = [], id = 0;
+ while (!next.done) {
+ strokes.push({
+ id: id++,
+ points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+ language: "en-US"
+ });
+ next = entries.next();
+ }
+ return JSON.stringify({
+ version: 1,
+ language: "en-US",
+ unit: "mm",
+ strokes: strokes
+ });
+ },
+
+ requester: async (apiKey: string, body: string) => {
+ let xhttp = new XMLHttpRequest();
+ let serverAddress = "https://api.cognitive.microsoft.com";
+ let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+
+ let promisified = (resolve: any, reject: any) => {
+ xhttp.onreadystatechange = function () {
+ if (this.readyState === 4) {
+ let result = xhttp.responseText;
+ switch (this.status) {
+ case 200:
+ return resolve(result);
+ case 400:
+ default:
+ return reject(result);
+ }
+ }
+ };
+
+ xhttp.open("PUT", endpoint, true);
+ xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
+ xhttp.setRequestHeader('Content-Type', 'application/json');
+ xhttp.send(body);
+ };
+
+ return new Promise<any>(promisified);
+ },
+
+ analyzer: async (target: Doc, keys: string[], inkData: InkData) => {
+ let results = await executeQuery<InkData, any>(Service.Handwriting, Manager, inkData);
+ if (results) {
+ results.recognitionUnits && (results = results.recognitionUnits);
+ target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
+ let recognizedText = results.map((item: any) => item.recognizedText);
+ let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
+ target[keys[1]] = individualWords.join(" ");
+ }
+ }
+
};
+ export interface AzureStrokeData {
+ id: number;
+ points: string;
+ language?: string;
+ }
+
+ export interface HandwritingUnit {
+ version: number;
+ language: string;
+ unit: string;
+ strokes: AzureStrokeData[];
+ }
+
}
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a419aed54..76ac34d85 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -38,6 +38,7 @@ import { LinkManager } from "../util/LinkManager";
import { DocumentManager } from "../util/DocumentManager";
import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox";
import { Scripting } from "../util/Scripting";
+import { ButtonBox } from "../views/nodes/ButtonBox";
var requestImageSize = require('../util/request-image-size');
var path = require('path');
@@ -56,7 +57,9 @@ export enum DocumentType {
IMPORT = "import",
LINK = "link",
LINKDOC = "linkdoc",
- TEMPLATE = "template"
+ BUTTON = "button",
+ TEMPLATE = "template",
+ EXTENSION = "extension"
}
export interface DocumentOptions {
@@ -161,6 +164,9 @@ export namespace Docs {
data: new List<Doc>(),
layout: { view: EmptyBox },
options: {}
+ }],
+ [DocumentType.BUTTON, {
+ layout: { view: ButtonBox },
}]
]);
@@ -276,7 +282,7 @@ export namespace Docs {
* only when creating a DockDocument from the current user's already existing
* main document.
*/
- export function InstanceFromProto(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) {
+ export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string) {
const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
if (!("author" in protoProps)) {
@@ -305,9 +311,11 @@ export namespace Docs {
* @param options initial values to apply to this new delegate
* @param value the data to store in this new delegate
*/
- function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value: D) {
+ function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D) {
const deleg = Doc.MakeDelegate(proto);
- deleg.data = value;
+ if (value !== undefined) {
+ deleg.data = value;
+ }
return Doc.assign(deleg, options);
}
@@ -414,6 +422,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Masonry });
}
+ export function ButtonDocument(options?: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) });
+ }
+
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 46dc320b0..1d0916ac0 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -52,10 +52,10 @@ export namespace Scripting {
} else {
throw new Error("Must either register an object with a name, or give a name and an object");
}
- if (scriptingGlobals.hasOwnProperty(n)) {
+ if (_scriptingGlobals.hasOwnProperty(n)) {
throw new Error(`Global with name ${n} is already registered, choose another name`);
}
- scriptingGlobals[n] = obj;
+ _scriptingGlobals[n] = obj;
}
export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
@@ -188,6 +188,10 @@ class ScriptingCompilerHost {
export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser };
+export type Transformer = {
+ transformer: ts.TransformerFactory<ts.SourceFile>,
+ getVars?: () => { capturedVariables: { [name: string]: Field } }
+};
export interface ScriptOptions {
requiredType?: string;
addReturn?: boolean;
@@ -196,7 +200,7 @@ export interface ScriptOptions {
typecheck?: boolean;
editable?: boolean;
traverser?: TraverserParam;
- transformer?: ts.TransformerFactory<ts.SourceFile>;
+ transformer?: Transformer;
globals?: { [name: string]: any };
}
@@ -213,6 +217,27 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
Scripting.setScriptingGlobals(options.globals);
}
let host = new ScriptingCompilerHost;
+ if (options.traverser) {
+ const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+ const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
+ const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
+ forEachNode(sourceFile, onEnter, onLeave);
+ }
+ if (options.transformer) {
+ const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+ const result = ts.transform(sourceFile, [options.transformer.transformer]);
+ if (options.transformer.getVars) {
+ const newCaptures = options.transformer.getVars();
+ // tslint:disable-next-line: prefer-object-spread
+ options.capturedVariables = Object.assign(capturedVariables, newCaptures.capturedVariables) as any;
+ }
+ const transformed = result.transformed;
+ const printer = ts.createPrinter({
+ newLine: ts.NewLineKind.LineFeed
+ });
+ script = printer.printFile(transformed[0]);
+ result.dispose();
+ }
let paramNames: string[] = [];
if ("this" in params || "this" in capturedVariables) {
paramNames.push("this");
@@ -227,26 +252,11 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
});
for (const key in capturedVariables) {
if (key === "this") continue;
+ const val = capturedVariables[key];
paramNames.push(key);
- paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
+ paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
}
let paramString = paramList.join(", ");
- if (options.traverser) {
- const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
- const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
- const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
- forEachNode(sourceFile, onEnter, onLeave);
- }
- if (options.transformer) {
- const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
- const result = ts.transform(sourceFile, [options.transformer]);
- const transformed = result.transformed;
- const printer = ts.createPrinter({
- newLine: ts.NewLineKind.LineFeed
- });
- script = printer.printFile(transformed[0]);
- result.dispose();
- }
let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
})`;
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index dca539f3b..034be8f67 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,9 +1,14 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
-import { Field } from "../../new_fields/Doc";
+import { Field, Doc } from "../../new_fields/Doc";
import { ClientUtils } from "./ClientUtils";
+let serializing = 0;
+export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
+ serializing++;
+ cb(err, newValue);
+ serializing--;
+}
export namespace SerializationHelper {
- let serializing: number = 0;
export function IsSerializing() {
return serializing > 0;
}
@@ -17,18 +22,18 @@ export namespace SerializationHelper {
return obj;
}
- serializing += 1;
+ serializing++;
if (!(obj.constructor.name in reverseMap)) {
throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
}
const json = serialize(obj);
json.__type = reverseMap[obj.constructor.name];
- serializing -= 1;
+ serializing--;
return json;
}
- export function Deserialize(obj: any): any {
+ export async function Deserialize(obj: any): Promise<any> {
if (obj === undefined || obj === null) {
return undefined;
}
@@ -37,7 +42,6 @@ export namespace SerializationHelper {
return obj;
}
- serializing += 1;
if (!obj.__type) {
if (ClientUtils.RELEASE) {
console.warn("No property 'type' found in JSON.");
@@ -52,16 +56,15 @@ export namespace SerializationHelper {
}
const type = serializationTypes[obj.__type];
- const value = deserialize(type.ctor, obj);
+ const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
if (type.afterDeserialize) {
- type.afterDeserialize(value);
+ await type.afterDeserialize(value);
}
- serializing -= 1;
return value;
}
}
-let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void } } = {};
+let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
let reverseMap: { [ctor: string]: string } = {};
export interface DeserializableOpts {
@@ -69,7 +72,7 @@ export interface DeserializableOpts {
withFields(fields: string[]): Function;
}
-export function Deserializable(name: string, afterDeserialize?: (obj: any) => void): DeserializableOpts;
+export function Deserializable(name: string, afterDeserialize?: (obj: any) => void | Promise<any>): DeserializableOpts;
export function Deserializable(constructor: { new(...args: any[]): any }): void;
export function Deserializable(constructor: { new(...args: any[]): any } | string, afterDeserialize?: (obj: any) => void): DeserializableOpts | void {
function addToMap(name: string, ctor: { new(...args: any[]): any }) {
@@ -88,15 +91,15 @@ export function Deserializable(constructor: { new(...args: any[]): any } | strin
if (typeof constructor === "string") {
return Object.assign((ctor: { new(...args: any[]): any }) => {
addToMap(constructor, ctor);
- }, { withFields: Deserializable.withFields });
+ }, { withFields: (fields: string[]) => Deserializable.withFields(fields, name, afterDeserialize) });
}
addToMap(constructor.name, constructor);
}
export namespace Deserializable {
- export function withFields(fields: string[]) {
+ export function withFields(fields: string[], name?: string, afterDeserialize?: (obj: any) => void | Promise<any>) {
return function (constructor: { new(...fields: any[]): any }) {
- Deserializable(constructor);
+ Deserializable(name || constructor.name, afterDeserialize)(constructor);
let schema = getDefaultModelSchema(constructor);
if (schema) {
schema.factory = context => {
@@ -135,6 +138,6 @@ export namespace Deserializable {
export function autoObject(): PropSchema {
return custom(
(s) => SerializationHelper.Serialize(s),
- (s) => SerializationHelper.Deserialize(s)
+ (json: any, context: any, oldValue: any, cb: (err: any, result: any) => void) => SerializationHelper.Deserialize(json).then(res => cb(null, res))
);
} \ No newline at end of file
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 1f95af00c..79a4e50d5 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -179,7 +179,7 @@ declare class Doc extends RefField {
// [ToScriptString](): string;
}
-declare class ListImpl<T extends Field> extends ObjectField {
+declare class List<T extends Field> extends ObjectField {
constructor(fields?: T[]);
[index: number]: T | (T extends RefField ? Promise<T> : never);
[Copy](): ObjectField;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 989b35581..6fecbd3a7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -304,7 +304,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
iconDoc.height = Number(MINIMIZED_ICON_SIZE);
iconDoc.x = NumCast(doc.x);
iconDoc.y = NumCast(doc.y) - 24;
- iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document.proto!));
+ iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document));
selected.length === 1 && (doc.minimizedDoc = iconDoc);
selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
return iconDoc;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 1a61e5311..31e4557be 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -131,8 +131,8 @@ export class EditableView extends React.Component<EditableProps> {
return (
<div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
- onClick={this.onClick}>
- <span style={{ fontStyle: this.props.fontStyle }}>{this.props.contents}</span>
+ onClick={this.onClick} >
+ <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span>
</div>
);
}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e8a588e58..7477c5b4f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,4 +1,4 @@
-import { UndoManager, undoBatch } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
import { SelectionManager } from "../util/SelectionManager";
import { CollectionDockingView } from "./collections/CollectionDockingView";
import { MainView } from "./MainView";
@@ -144,9 +144,11 @@ export default class KeyManager {
break;
case "y":
UndoManager.Redo();
+ stopPropagation = false;
break;
case "z":
UndoManager.Undo();
+ stopPropagation = false;
break;
case "a":
case "c":
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 3e0d7b476..c4cd863d1 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -6,7 +6,7 @@ import "./InkingCanvas.scss";
import { InkingControl } from "./InkingControl";
import { InkingStroke } from "./InkingStroke";
import React = require("react");
-import { undoBatch, UndoManager } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
@@ -178,7 +178,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
render() {
let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
return (
- <div className="inkingCanvas" >
+ <div className="inkingCanvas">
<div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
{this.props.children()}
{this.drawnPaths}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index c7f7bdb66..da388e532 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
import { observable, action, computed, runInAction } from "mobx";
-import { ColorResult } from 'react-color';
+import { ColorState } from 'react-color';
import React = require("react");
import { observer } from "mobx-react";
import "./InkingControl.scss";
@@ -20,7 +20,7 @@ export class InkingControl extends React.Component {
static Instance: InkingControl = new InkingControl({});
@observable private _selectedTool: InkTool = InkTool.None;
@observable private _selectedColor: string = "rgb(244, 67, 54)";
- @observable private _selectedWidth: string = "25";
+ @observable private _selectedWidth: string = "5";
@observable public _open: boolean = false;
constructor(props: Readonly<{}>) {
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
}
@undoBatch
- switchColor = action((color: ColorResult): void => {
+ switchColor = action((color: ColorState): void => {
this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 69602deed..0e04b5e7e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faCaretUp, faLongArrowAltRight, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faCaretUp, faLongArrowAltRight, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat, faBolt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
@@ -133,6 +133,7 @@ export class MainView extends React.Component {
library.add(faArrowDown);
library.add(faArrowUp);
library.add(faCloudUploadAlt);
+ library.add(faBolt);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -380,10 +381,12 @@ export class MainView extends React.Component {
let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
let addTreeNode = action(() => CurrentUserUtils.UserDocument);
let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+ let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" }));
let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+ [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
// [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
[React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode],
];
@@ -396,6 +399,7 @@ export class MainView extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
<li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
+ <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
<li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
<li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
{btns.map(btn =>
@@ -446,7 +450,6 @@ export class MainView extends React.Component {
this.isSearchVisible = !this.isSearchVisible;
}
-
render() {
return (
<div id="main-div">
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 4d1e8cf0b..dc122497f 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -32,7 +32,7 @@
}
.overlayWindow-resizeDragger {
- background-color: red;
+ background-color: rgb(0, 0, 0);
position: absolute;
right: 0px;
bottom: 0px;
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 6eabc7b70..9e538cf1b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
import './ScriptingRepl.scss';
-import { Scripting, CompileScript, ts } from '../util/Scripting';
+import { Scripting, CompileScript, ts, Transformer } from '../util/Scripting';
import { DocumentManager } from '../util/DocumentManager';
import { DocumentView } from './nodes/DocumentView';
import { OverlayView } from './OverlayView';
@@ -16,17 +16,16 @@ library.add(faCaretRight);
@observer
export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
render() {
- this.props.view.props.ScreenToLocalTransform();
- this.props.view.props.Document.width;
- this.props.view.props.Document.height;
- const screenCoords = this.props.view.screenRect();
+ const view = this.props.view;
+ const transform = view.props.ScreenToLocalTransform().scale(view.props.ContentScaling()).inverse();
+ const { x, y, width, height } = transform.transformBounds(0, 0, view.props.PanelWidth(), view.props.PanelHeight());
return (
<div className="documentIcon-outerDiv" style={{
position: "absolute",
- transform: `translate(${screenCoords.left + screenCoords.width / 2}px, ${screenCoords.top}px)`,
+ transform: `translate(${x + width / 2}px, ${y}px)`,
}}>
- <p >${this.props.index}</p>
+ <p>${this.props.index}</p>
</div>
);
}
@@ -106,31 +105,35 @@ export class ScriptingRepl extends React.Component {
private args: any = {};
- getTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
- const knownVars: { [name: string]: number } = {};
- const usedDocuments: number[] = [];
- Scripting.getGlobals().forEach(global => knownVars[global] = 1);
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
+ getTransformer = (): Transformer => {
+ return {
+ transformer: context => {
+ const knownVars: { [name: string]: number } = {};
+ const usedDocuments: number[] = [];
+ Scripting.getGlobals().forEach(global => knownVars[global] = 1);
+ return root => {
+ function visit(node: ts.Node) {
+ node = ts.visitEachChild(node, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
- const match = node.text.match(/\$([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- usedDocuments.push(m);
- } else {
- return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+ const match = node.text.match(/\$([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ usedDocuments.push(m);
+ } else {
+ return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+ }
+ }
}
- }
- }
- return node;
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
}
- return ts.visitNode(root, visit);
};
}
@@ -142,7 +145,7 @@ export class ScriptingRepl extends React.Component {
const docGlobals: { [name: string]: any } = {};
DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`$${i}`] = dv.props.Document);
const globals = Scripting.makeMutableGlobalsCopy(docGlobals);
- const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer, globals });
+ const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
return;
}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index e56c705f0..91c6e8b3c 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -10,8 +10,19 @@
width: 100%;
height: calc(100% - 50px);
// overflow: hidden;
- overflow-x: scroll;
- border: none;
+ // overflow-x: scroll;
+ // border: none;
+ overflow: hidden;
+ transition: top 0.5s;
+
+ // .collectionSchemaView-cellContents {
+ // height: $MAX_ROW_HEIGHT;
+
+ // img {
+ // width: auto;
+ // max-height: $MAX_ROW_HEIGHT;
+ // }
+ // }
.collectionSchemaView-previewRegion {
position: relative;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index aa36b8584..60644b741 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -15,7 +15,7 @@ import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_field
import { Docs, DocumentOptions } from "../../documents/Documents";
import { Gateway } from "../../northstar/manager/Gateway";
import { SetupDrag, DragManager } from "../../util/DragManager";
-import { CompileScript } from "../../util/Scripting";
+import { CompileScript, ts, Transformer } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
@@ -70,6 +70,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@observable private _node: HTMLDivElement | null = null;
@observable private _focusedTable: Doc = this.props.Document;
+ @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
@computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
@computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
@computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
@@ -219,7 +220,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
@computed
- get schemaToolbar() {
+ public get schemaToolbar() {
return (
<div className="collectionSchemaView-toolbar">
<div className="collectionSchemaView-toolbar-item">
@@ -234,15 +235,12 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
// if (SelectionManager.SelectedDocuments().length > 0) console.log(StrCast(SelectionManager.SelectedDocuments()[0].Document.title));
// if (DocumentManager.Instance.getDocumentView(this.props.Document)) console.log(StrCast(this.props.Document.title), SelectionManager.IsSelected(DocumentManager.Instance.getDocumentView(this.props.Document)!))
return (
- <>
- {this.schemaToolbar}
- <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
- onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
- {this.schemaTable}
- {this.dividerDragger}
- {!this.previewWidth() ? (null) : this.previewPanel}
- </div>
- </>
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
+ {this.schemaTable}
+ {this.dividerDragger}
+ {!this.previewWidth() ? (null) : this.previewPanel}
+ </div>
);
}
}
@@ -441,7 +439,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
if (!column) return {};
let row = rowInfo.index;
- let col = this.columns.indexOf(column.id);
+ //@ts-ignore
+ let col = this.columns.indexOf(column!.id);
// let col = column ? this.columns.indexOf(column!) : -1;
let isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document);
// let column = this.columns.indexOf(column.id!);
@@ -549,6 +548,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
fdoc && this.props.setPreviewDoc(fdoc);
}
+ createRow = () => {
+ console.log("creating row");
+ let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
+ let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []);
+
+ let newDoc = Docs.Create.TextDocument({ width: 100, height: 30 });
+ let proto = Doc.GetProto(newDoc);
+ proto.title = "";
+ children.push(newDoc);
+ }
+
@action
createColumn = () => {
let index = 0;
@@ -676,6 +686,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
let hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false);
let expandedRowsList = this._openCollections.map(col => children.findIndex(doc => doc[Id] === col).toString());
let expanded = {};
+ //@ts-ignore
expandedRowsList.forEach(row => expanded[row] = true);
console.log(...[...this._textWrappedRows]); // TODO: get component to rerender on text wrap change without needign to console.log :((((
@@ -738,6 +749,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
<div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
onDrop={(e: React.DragEvent) => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
{this.reactTable}
+ <button onClick={() => this.createRow()}>new row</button>
</div>
);
}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index f43340967..9dbe4ccb8 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -96,7 +96,6 @@
outline: none;
border: 0px;
color: $light-color;
- text-transform: uppercase;
letter-spacing: 2px;
font-size: 75%;
transition: transform 0.2s;
@@ -118,9 +117,7 @@
padding: 12px 10px 11px 10px;
border: 0px;
color: grey;
- // font-size: 75%;
text-align: center;
- text-transform: uppercase;
letter-spacing: 2px;
outline-color: black;
}
@@ -142,15 +139,6 @@
width: 90%;
color: lightgrey;
overflow: ellipses;
- }
-
- .collectionStackingView-addGroupButton {
- background: rgb(238, 238, 238);
- font-size: 75%;
- text-align: center;
- text-transform: uppercase;
- letter-spacing: 2px;
- height: fit-content;
.editableView-container-editing-oneLine,
.editableView-container-editing {
@@ -165,14 +153,32 @@
}
.editableView-input {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: grey;
- // font-size: 75%;
- text-align: center;
- text-transform: uppercase;
+ outline-color: black;
letter-spacing: 2px;
+ color: grey;
+ border: 0px;
+ padding: 12px 10px 11px 10px;
+ }
+ }
+
+ .collectionStackingView-addDocumentButton {
+ font-size: 75%;
+ letter-spacing: 2px;
+
+ .editableView-input {
outline-color: black;
+ letter-spacing: 2px;
+ color: grey;
+ border: 0px;
+ padding: 12px 10px 11px 10px;
}
}
+
+ .collectionStackingView-addGroupButton {
+ background: rgb(238, 238, 238);
+ font-size: 75%;
+ text-align: center;
+ letter-spacing: 2px;
+ height: fit-content;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2e90e8be4..7677f53c1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,8 +4,8 @@ import { action, computed, IReactionDisposer, reaction, untracked, observable, r
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, Cast, StrCast, FieldValue } from "../../../new_fields/Types";
-import { emptyFunction, Utils } from "../../../Utils";
+import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, Utils, returnTrue } from "../../../Utils";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
@@ -21,8 +21,6 @@ import { List } from "../../../new_fields/List";
import { EditableView } from "../EditableView";
import { CollectionViewProps } from "./CollectionBaseView";
-let valuesCreated = 1;
-
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
_masonryGridRef: HTMLDivElement | null = null;
@@ -33,7 +31,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
_columnStart: number = 0;
@observable private cursor: CursorProperty = "grab";
get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
- get chromeCollapsed() { return this.props.chromeCollapsed; }
+ @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
@computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
@computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
@computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@@ -49,7 +47,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []]));
if (sectionFilter) {
this.filteredChildren.map(d => {
- let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `No ${sectionFilter} value`) as object;
+ let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `NO ${sectionFilter.toUpperCase()} VALUE`) as object;
// the next five lines ensures that floating point rounding errors don't create more than one section -syip
let parsed = parseInt(sectionValue.toString());
let castedSectionValue: any = sectionValue;
@@ -58,12 +56,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
// look for if header exists already
- let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `No ${sectionFilter} value`));
+ let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`));
if (existingHeader) {
fields.get(existingHeader)!.push(d);
}
else {
- let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `No ${sectionFilter} value`);
+ let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`);
fields.set(newSchemaHeader, [d]);
sectionHeaders!.push(newSchemaHeader);
}
@@ -84,9 +82,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
() => StrCast(this.props.Document.sectionFilter),
() => {
this.props.Document.sectionHeaders = new List();
- valuesCreated = 1;
}
- )
+ );
}
componentWillUnmount() {
this._heightDisposer && this._heightDisposer();
@@ -264,8 +261,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
let editableViewProps = {
GetValue: () => "",
SetValue: this.addGroup,
- contents: "+ Add a Group"
- }
+ contents: "+ ADD A GROUP"
+ };
// let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
return (
<div className="collectionStackingView" style={{ top: this.chromeCollapsed ? 0 : 100 }}
@@ -275,7 +272,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
{this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
- map(section => this.section(section[0], section[1] as Doc[])) :
+ map(section => this.section(section[0], section[1])) :
this.section(undefined, this.filteredChildren)}
{this.props.Document.sectionFilter ?
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 582adc418..ea2a302ff 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -18,6 +18,7 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ScriptField } from "../../../new_fields/ScriptField";
import { CompileScript } from "../../util/Scripting";
+import { RichTextField } from "../../../new_fields/RichTextField";
interface CSVFieldColumnProps {
@@ -168,11 +169,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
}
@action
- addDocument = () => {
+ addDocument = (value: string, shiftDown?: boolean) => {
let key = StrCast(this.props.parent.props.Document.sectionFilter);
- let newDoc = Docs.Create.TextDocument({ height: 18, title: "new text document" });
+ let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value });
newDoc[key] = this.getValue(this.props.heading);
- this.props.parent.props.addDocument(newDoc);
+ return this.props.parent.props.addDocument(newDoc);
}
@action
@@ -231,29 +232,34 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
let style = this.props.parent;
let singleColumn = style.singleColumn;
let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
- let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `No ${key} value`;
- let editableViewProps = {
+ let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
+ let headerEditableViewProps = {
GetValue: () => evContents,
SetValue: this.headingChanged,
contents: evContents,
oneLine: true
}
+ let newEditableViewProps = {
+ GetValue: () => "",
+ SetValue: this.addDocument,
+ contents: "+ NEW"
+ }
let headingView = this.props.headingObject ?
<div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
{/* the default bucket (no key value) has a tooltip that describes what it is.
Further, it does not have a color and cannot be deleted. */}
<div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
- title={evContents === `No ${key} value` ?
+ title={evContents === `NO ${key.toUpperCase()} VALUE` ?
`Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
style={{
width: "100%",
- background: this.props.headingObject && evContents !== `No ${key} value` ?
+ background: this.props.headingObject && evContents !== `NO ${key.toUpperCase()} VALUE` ?
this.props.headingObject.color : "lightgrey",
color: "grey"
}}>
- <EditableView {...editableViewProps} />
- {evContents === `No ${key} value` ?
+ <EditableView {...headerEditableViewProps} />
+ {evContents === `NO ${key.toUpperCase()} VALUE` ?
(null) :
<button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" />
@@ -282,7 +288,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div>
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
style={{ width: style.columnWidth / (uniqueHeadings.length + 1) }}>
- <button style={{ width: "100%" }} onClick={this.addDocument}>+ New</button>
+ <EditableView {...newEditableViewProps} />
</div>
</div>
);
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 5205f4313..db3652ff6 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -31,6 +31,7 @@
margin-top: 4px;
transform: scale(1.3, 1.3);
}
+
.editableView-container {
font-weight: bold;
}
@@ -43,18 +44,20 @@
display: inline;
}
- .editableView-input, .editableView-container-editing {
+ .editableView-input,
+ .editableView-container-editing {
display: block;
text-overflow: ellipsis;
font-size: 24px;
white-space: nowrap;
}
}
+
.collectionTreeView-keyHeader {
font-style: italic;
font-size: 8pt;
margin-left: 3px;
- display:none;
+ display: none;
background: lightgray;
}
@@ -72,28 +75,31 @@
// width:100%;//width: max-content;
}
+
.treeViewItem-openRight {
display: none;
}
.treeViewItem-border {
- display:inherit;
+ display: inherit;
border-left: dashed 1px #00000042;
}
.treeViewItem-header:hover {
.collectionTreeView-keyHeader {
- display:inherit;
+ display: inherit;
}
+
.treeViewItem-openRight {
display: inline-block;
- height:13px;
- margin-top:2px;
+ height: 13px;
+ margin-top: 2px;
margin-left: 5px;
+
// display: inline;
svg {
- display:block;
- padding:0px;
+ display: block;
+ padding: 0px;
margin: 0px;
}
}
@@ -101,14 +107,17 @@
.treeViewItem-header {
border: transparent 1px solid;
- display:flex;
+ display: flex;
}
+
.treeViewItem-header-above {
border-top: black 1px solid;
}
+
.treeViewItem-header-below {
border-bottom: black 1px solid;
}
+
.treeViewItem-header-inside {
border: black 1px solid;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f98629c5b..eeb33b40d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,7 +3,7 @@ import { faAngleRight, faCamera, faExpand, faTrash, faBell, faCaretDown, faCaret
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, Opt } from '../../../new_fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym, Opt, Field } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
@@ -26,6 +26,8 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
import { LinkManager } from '../../util/LinkManager';
+import { ComputedField } from '../../../new_fields/ScriptField';
+import { KeyValueBox } from '../nodes/KeyValueBox';
export interface TreeViewProps {
@@ -68,15 +70,15 @@ class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
- @observable __chosenKey: string = "";
- @computed get _chosenKey() { return this.__chosenKey ? this.__chosenKey : this.fieldKey; }
+ @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "data"); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@observable _collapsed: boolean = true;
@computed get fieldKey() {
- let keys = Array.from(Object.keys(this.dataDoc)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
- if (this.dataDoc.proto instanceof Doc) {
- let arr = Array.from(Object.keys(this.dataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+ let target = this.props.document;
+ let keys = Array.from(Object.keys(target)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+ if (target.proto instanceof Doc) {
+ let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
keys.push(...arr);
while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
@@ -88,7 +90,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
});
let layout = StrCast(this.props.document.layout);
- if (layout.indexOf("fieldKey={\"") !== -1) {
+ if (layout.indexOf("fieldKey={\"") !== -1 && layout.indexOf("fieldExt=") === -1) {
return layout.split("fieldKey={\"")[1].split("\"")[0];
}
return keyList.length ? keyList[0] : "data";
@@ -124,12 +126,12 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
onPointerLeave = (e: React.PointerEvent): void => {
- this.props.document.libraryBrush = undefined;
+ this.props.document.libraryBrush = false;
this._header!.current!.className = "treeViewItem-header";
document.removeEventListener("pointermove", this.onDragMove, true);
}
onDragMove = (e: PointerEvent): void => {
- this.props.document.libraryBrush = undefined;
+ this.props.document.libraryBrush = false;
let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
@@ -190,24 +192,6 @@ class TreeView extends React.Component<TreeViewProps> {
OnTab={() => this.props.indentDocument && this.props.indentDocument()}
/>)
- @computed get keyList() {
- let keys = Array.from(Object.keys(this.dataDoc));
- if (this.dataDoc.proto instanceof Doc) {
- keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
- }
- let keyList: string[] = keys.reduce((l, key) => {
- let listspec = DocListCast(this.dataDoc[key]);
- if (listspec && listspec.length) return [...l, key];
- return l;
- }, [] as string[]);
- keys.map(key => Cast(this.dataDoc[key], Doc) instanceof Doc && (Cast(this.dataDoc[key], Doc) as Doc).type !== undefined && keyList.push(key));
- if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
- if (keyList.indexOf(this.fieldKey) !== -1) {
- keyList.splice(keyList.indexOf(this.fieldKey), 1);
- }
- keyList.splice(0, 0, this.fieldKey);
- return keyList.filter((item, index) => keyList.indexOf(item) >= index);
- }
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -216,13 +200,9 @@ class TreeView extends React.Component<TreeViewProps> {
let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
let headerElements = (
- <span className="collectionTreeView-keyHeader" key={this._chosenKey + "chosen"}
- onPointerDown={action(() => {
- let ind = this.keyList.indexOf(this._chosenKey);
- ind = (ind + 1) % this.keyList.length;
- this.__chosenKey = this.keyList[ind];
- })} >
- {this._chosenKey}
+ <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
+ onPointerDown={action(() => this.props.document.treeViewExpandedView = this.treeViewExpandedView === "data" ? "fields" : this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : "data")}>
+ {this.treeViewExpandedView}
</span>);
let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : (
@@ -247,7 +227,6 @@ class TreeView extends React.Component<TreeViewProps> {
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: (BoolCast(this.props.document.embed) ? "Collapse" : "Expand") + " inline", event: () => this.props.document.embed = !BoolCast(this.props.document.embed), icon: "expand" });
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
@@ -309,8 +288,8 @@ class TreeView extends React.Component<TreeViewProps> {
renderLinks = () => {
let ele: JSX.Element[] = [];
- let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
+ let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this.fieldKey, doc, addBefore, before);
let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
groups.forEach((groupLinkDocs, groupType) => {
// let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
@@ -354,22 +333,67 @@ class TreeView extends React.Component<TreeViewProps> {
})());
}
- noOverlays = (doc: Doc) => { return { title: "", caption: "" } };
+ noOverlays = (doc: Doc) => ({ title: "", caption: "" });
+
+ expandedField = (doc?: Doc) => {
+ if (!doc) return <div />;
+ let realDoc = doc;
+
+ let ids: { [key: string]: string } = {};
+ Object.keys(doc).forEach(key => {
+ if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
+ ids[key] = key;
+ }
+ });
+
+ let rows: JSX.Element[] = [];
+ for (let key of Object.keys(ids).sort()) {
+ let contents = realDoc[key] ? realDoc[key] : undefined;
+ let contentElement: JSX.Element[] | JSX.Element = [];
+
+ if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
+ let docList = contents;
+ let remDoc = (doc: Doc) => this.remove(doc, key);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before);
+ contentElement = key === "links" ? this.renderLinks() :
+ TreeView.GetChildElements(docList instanceof Doc ? [docList as Doc] : DocListCast(docList), this.props.treeViewId, realDoc, undefined, key, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+ } else {
+ contentElement = <EditableView
+ key="editableView"
+ contents={contents ? contents.toString() : "null"}
+ height={13}
+ fontSize={12}
+ GetValue={() => Field.toKeyValueString(realDoc, key)}
+ SetValue={(value: string) => KeyValueBox.SetField(realDoc, key, value)} />;
+ }
+ rows.push(<div style={{ display: "flex" }} key={key}>
+ <span style={{ fontWeight: "bold" }}>{key + ":"}</span>
+ &nbsp;
+ {contentElement}
+ </div>);
+ }
+ return rows;
+ }
render() {
let contentElement: (JSX.Element | null) = null;
- let docList = Cast(this.dataDoc[this._chosenKey], listSpec(Doc));
- let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this._chosenKey, doc, addBefore, before);
- let doc = Cast(this.dataDoc[this._chosenKey], Doc);
+ let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc));
+ let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before);
if (!this._collapsed) {
- if (!this.props.document.embed) {
- contentElement = <ul key={this._chosenKey + "more"}>
- {this._chosenKey === "links" ? this.renderLinks() :
- TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this._chosenKey, addDoc, remDoc, this.move,
+ if (this.treeViewExpandedView === "data") {
+ let doc = Cast(this.props.document[this.fieldKey], Doc);
+ contentElement = <ul key={this.fieldKey + "more"}>
+ {this.fieldKey === "links" ? this.renderLinks() :
+ TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
</ul >;
+ } else if (this.treeViewExpandedView === "fields") {
+ contentElement = <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
+ {this.expandedField(this.dataDoc)}
+ </div></ul>;
} else {
let layoutDoc = this.props.document;
contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
@@ -446,7 +470,7 @@ class TreeView extends React.Component<TreeViewProps> {
dataDoc={dataDoc}
containingCollection={containingCollection}
treeViewId={treeViewId}
- key={child[Id] + "child " + i}
+ key={child[Id]}
indentDocument={indent}
renderDepth={renderDepth}
deleteDoc={remove}
@@ -469,6 +493,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
private treedropDisposer?: DragManager.DragDropDisposer;
private _mainEle?: HTMLDivElement;
+ @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
+
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer && this.treedropDisposer();
if (this._mainEle = ele) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index df8a1ebc1..d90eba401 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV, faFingerprint, faLaptopCode } from '@fortawesome/free-solid-svg-icons';
import { observer } from "mobx-react";
import * as React from 'react';
import { Doc, DocListCast, WidthSym, HeightSym } from '../../../new_fields/Doc';
@@ -18,7 +18,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
import { StrCast, PromiseValue } from '../../../new_fields/Types';
import { DocumentType } from '../../documents/Documents';
import { CollectionStackingViewChrome, CollectionViewBaseChrome } from './CollectionViewChromes';
-import { observable } from 'mobx';
+import { observable, action, runInAction } from 'mobx';
export const COLLECTION_BORDER_WIDTH = 2;
library.add(faTh);
@@ -27,6 +27,7 @@ library.add(faSquare);
library.add(faProjectDiagram);
library.add(faSignature);
library.add(faThList);
+library.add(faFingerprint);
library.add(faColumns);
library.add(faEllipsisV);
library.add(faImage);
@@ -37,11 +38,20 @@ export class CollectionView extends React.Component<FieldViewProps> {
public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); }
+ componentDidMount = () => {
+ // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+ let chromeStatus = this.props.Document.chromeStatus;
+ if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
+ runInAction(() => this._collapsed = true);
+ }
+ }
+
private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
let props = { ...this.props, ...renderProps };
switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
case CollectionViewType.Schema: return (<CollectionSchemaView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
- case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
+ // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
+ case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={true} key="collview" {...props} CollectionView={this} />);
case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
@@ -52,17 +62,20 @@ export class CollectionView extends React.Component<FieldViewProps> {
return (null);
}
+ @action
private collapse = (value: boolean) => {
this._collapsed = value;
+ this.props.Document.chromeStatus = value ? "collapsed" : "visible";
}
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
- if (this.isAnnotationOverlay || this.props.Document === CurrentUserUtils.UserDocument.sidebar) {
+ // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
+ if (this.isAnnotationOverlay || this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking) {
return [(null), this.SubViewHelper(type, renderProps)];
}
else {
return [
- // (<CollectionViewBaseChrome CollectionView={this} type={type} collapse={this.collapse} />),
+ (<CollectionViewBaseChrome CollectionView={this} type={type} collapse={this.collapse} />),
this.SubViewHelper(type, renderProps)
];
}
@@ -81,6 +94,12 @@ export class CollectionView extends React.Component<FieldViewProps> {
subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "ellipsis-v" });
subItems.push({ description: "Masonry", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Masonry), icon: "columns" });
+ switch (this.props.Document.viewType) {
+ case CollectionViewType.Freeform: {
+ subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
+ break;
+ }
+ }
ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
ContextMenu.Instance.addItem({ description: "Apply Template", event: undoBatch(() => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight")), icon: "project-diagram" });
}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index d37228bde..6525f3b07 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -1,167 +1,168 @@
@import "../globalCssVariables";
@import '~js-datepicker/dist/datepicker.min.css';
-.collectionViewChrome {
- display: grid;
- grid-template-columns: 1fr auto;
- padding-bottom: 10px;
- border-bottom: .5px solid lightgrey;
- margin: 10px;
+.collectionViewChrome-cont {
position: relative;
z-index: 9001;
transition: top .5s;
+ background: lightslategray;
+ padding: 10px;
- .collectionViewBaseChrome {
- display: flex;
+ .collectionViewChrome {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ padding-bottom: 10px;
+ border-bottom: .5px solid lightgrey;
- .collectionViewBaseChrome-viewPicker {
- font-size: 75%;
- text-transform: uppercase;
- letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
- outline-color: black;
- border: none;
- padding: 12px 10px 11px 10px;
- margin-left: 50px;
- }
-
- .collectionViewBaseChrome-viewPicker:active {
- outline-color: black;
- }
-
- .collectionViewBaseChrome-collapse {
- transition: all .5s;
- position: absolute;
- width: 40px;
- }
+ .collectionViewBaseChrome {
+ display: flex;
- .collectionViewBaseChrome-viewSpecs {
- margin-left: 10px;
- display: grid;
-
- .collectionViewBaseChrome-viewSpecsInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: grey;
- text-align: center;
+ .collectionViewBaseChrome-viewPicker {
+ font-size: 75%;
text-transform: uppercase;
letter-spacing: 2px;
- outline-color: black;
- font-size: 75%;
background: rgb(238, 238, 238);
- height: 100%;
- width: 150px;
+ color: grey;
+ outline-color: black;
+ border: none;
+ padding: 12px 10px 11px 10px;
+ margin-left: 50px;
+ }
+
+ .collectionViewBaseChrome-viewPicker:active {
+ outline-color: black;
}
- .collectionViewBaseChrome-viewSpecsMenu {
- overflow: hidden;
- transition: height .5s, display .5s;
+ .collectionViewBaseChrome-collapse {
+ transition: all .5s;
position: absolute;
- top: 60px;
- z-index: 100;
- display: flex;
- flex-direction: column;
- background: rgb(238, 238, 238);
- box-shadow: grey 2px 2px 4px;
+ width: 40px;
+ }
- .qs-datepicker {
- left: unset;
- right: 0;
+ .collectionViewBaseChrome-viewSpecs {
+ margin-left: 10px;
+ display: grid;
+
+ .collectionViewBaseChrome-viewSpecsInput {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ text-align: center;
+ letter-spacing: 2px;
+ outline-color: black;
+ font-size: 75%;
+ background: rgb(238, 238, 238);
+ height: 100%;
+ width: 150px;
}
- .collectionViewBaseChrome-viewSpecsMenu-row {
- display: grid;
- grid-template-columns: 150px 200px 150px;
- margin-top: 10px;
- margin-right: 10px;
-
- .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
- .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
- .collectionViewBaseChrome-viewSpecsMenu-rowRight {
- font-size: 75%;
- letter-spacing: 2px;
- color: grey;
- margin-left: 10px;
- padding: 5px;
- border: none;
- outline-color: black;
+ .collectionViewBaseChrome-viewSpecsMenu {
+ overflow: hidden;
+ transition: height .5s, display .5s;
+ position: absolute;
+ top: 60px;
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+ background: rgb(238, 238, 238);
+ box-shadow: grey 2px 2px 4px;
+
+ .qs-datepicker {
+ left: unset;
+ right: 0;
}
- }
- .collectionViewBaseChrome-viewSpecsMenu-lastRow {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-gap: 10px;
- margin: 10px;
+ .collectionViewBaseChrome-viewSpecsMenu-row {
+ display: grid;
+ grid-template-columns: 150px 200px 150px;
+ margin-top: 10px;
+ margin-right: 10px;
+
+ .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+ .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+ .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+ font-size: 75%;
+ letter-spacing: 2px;
+ color: grey;
+ margin-left: 10px;
+ padding: 5px;
+ border: none;
+ outline-color: black;
+ }
+ }
+
+ .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 10px;
+ margin: 10px;
+ }
}
}
}
- }
- .collectionStackingViewChrome-cont {
- display: flex;
- justify-content: space-between;
- }
+ .collectionStackingViewChrome-cont {
+ display: flex;
+ justify-content: space-between;
+ }
- .collectionStackingViewChrome-sort {
- display: flex;
- align-items: center;
- justify-content: space-between;
+ .collectionStackingViewChrome-sort {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
- .collectionStackingViewChrome-sortIcon {
- transition: transform .5s;
- margin-left: 10px;
+ .collectionStackingViewChrome-sortIcon {
+ transition: transform .5s;
+ margin-left: 10px;
+ }
}
- }
-
- button:hover {
- transform: scale(1);
- }
+ button:hover {
+ transform: scale(1);
+ }
- .collectionStackingViewChrome-sectionFilter-cont {
- justify-self: right;
- display: flex;
- font-size: 75%;
- text-transform: uppercase;
- letter-spacing: 2px;
- .collectionStackingViewChrome-sectionFilter-label {
- vertical-align: center;
- padding: 10px;
- }
+ .collectionStackingViewChrome-sectionFilter-cont {
+ justify-self: right;
+ display: flex;
+ font-size: 75%;
+ letter-spacing: 2px;
- .collectionStackingViewChrome-sectionFilter {
- color: white;
- width: 100px;
- text-align: center;
- background: rgb(238, 238, 238);
+ .collectionStackingViewChrome-sectionFilter-label {
+ vertical-align: center;
+ padding: 10px;
+ }
- .editable-view-input,
- input,
- .editableView-container-editing-oneLine,
- .editableView-container-editing {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: grey;
+ .collectionStackingViewChrome-sectionFilter {
+ color: white;
+ width: 100px;
text-align: center;
- text-transform: uppercase;
- letter-spacing: 2px;
- outline-color: black;
- height: 100%;
- }
+ background: rgb(238, 238, 238);
- .react-autosuggest__container {
- margin: 0;
- color: grey;
- padding: 0px;
+ .editable-view-input,
+ input,
+ .editableView-container-editing-oneLine,
+ .editableView-container-editing {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ text-align: center;
+ letter-spacing: 2px;
+ outline-color: black;
+ height: 100%;
+ }
+
+ .react-autosuggest__container {
+ margin: 0;
+ color: grey;
+ padding: 0px;
+ }
}
}
- }
- .collectionStackingViewChrome-sectionFilter:hover {
- cursor: text;
+ .collectionStackingViewChrome-sectionFilter:hover {
+ cursor: text;
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index dbd61fb6e..1ad9e47ce 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -15,6 +15,7 @@ import { Utils } from "../../../Utils";
import KeyRestrictionRow from "./KeyRestrictionRow";
import { CompileScript } from "../../util/Scripting";
import { ScriptField } from "../../../new_fields/ScriptField";
+import { CollectionSchemaView } from "./CollectionSchemaView";
const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
@@ -47,6 +48,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
runInAction(() => {
this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]);
this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
+
+ // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+ let chromeStatus = this.props.CollectionView.props.Document.chromeStatus;
+ if (chromeStatus) {
+ if (chromeStatus === "disabled") {
+ throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
+ }
+ else if (chromeStatus === "collapsed") {
+ this._collapsed = true;
+ if (this.props.collapse) {
+ this.props.collapse(true);
+ }
+ }
+ }
});
}
@@ -130,17 +145,24 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@action
toggleCollapse = () => {
this._collapsed = !this._collapsed;
- this.props.collapse(this._collapsed);
+ if (this.props.collapse) {
+ this.props.collapse(this._collapsed);
+ }
}
subChrome = () => {
-
switch (this.props.type) {
case CollectionViewType.Stacking: return (
<CollectionStackingViewChrome
key="collchrome"
CollectionView={this.props.CollectionView}
type={this.props.type} />);
+ case CollectionViewType.Schema: return (
+ <CollectionSchemaViewChrome
+ key="collchrome"
+ CollectionView={this.props.CollectionView}
+ type={this.props.type}
+ />);
default:
return null;
}
@@ -148,71 +170,73 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
render() {
return (
- <div className="collectionViewChrome" style={{ top: this._collapsed ? -100 : 0 }}>
- <div className="collectionViewBaseChrome">
- <button className="collectionViewBaseChrome-collapse"
- style={{ top: this._collapsed ? 80 : 0, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
- title="Collapse collection chrome" onClick={this.toggleCollapse}>
- <FontAwesomeIcon icon="caret-up" size="2x" />
- </button>
- <select
- className="collectionViewBaseChrome-viewPicker"
- onPointerDown={stopPropagation}
- onChange={this.viewChanged}
- value={NumCast(this.props.CollectionView.props.Document.viewType)}>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
- <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option>
- </select>
- <div className="collectionViewBaseChrome-viewSpecs">
- <input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="Filter Documents"
- value={this.filterValue ? this.filterValue.script.originalScript : ""}
- onPointerDown={this.openViewSpecs} />
- <div className="collectionViewBaseChrome-viewSpecsMenu"
- onPointerDown={this.openViewSpecs}
- style={{
- height: this._viewSpecsOpen ? "fit-content" : "0px",
- overflow: this._viewSpecsOpen ? "initial" : "hidden"
- }}>
- {this._keyRestrictions.map(i => i[0])}
- <div className="collectionViewBaseChrome-viewSpecsMenu-row">
- <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
- CREATED WITHIN:
- </div>
- <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
- style={{ textTransform: "uppercase", textAlign: "center" }}
- value={this._dateWithinValue}
- onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
- <option value="1d">1 day of</option>
- <option value="3d">3 days of</option>
- <option value="1w">1 week of</option>
- <option value="2w">2 weeks of</option>
- <option value="1m">1 month of</option>
- <option value="2m">2 months of</option>
- <option value="6m">6 months of</option>
- <option value="1y">1 year of</option>
- </select>
- <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- id={this._datePickerElGuid}
- value={this._dateValue.toLocaleDateString()}
- onPointerDown={this.openDatePicker}
- placeholder="Value" />
+ <div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -100 : 0 }}>
+ <div className="collectionViewChrome">
+ <div className="collectionViewBaseChrome">
+ <button className="collectionViewBaseChrome-collapse"
+ style={{ top: this._collapsed ? 90 : 10, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
+ title="Collapse collection chrome" onClick={this.toggleCollapse}>
+ <FontAwesomeIcon icon="caret-up" size="2x" />
+ </button>
+ <select
+ className="collectionViewBaseChrome-viewPicker"
+ onPointerDown={stopPropagation}
+ onChange={this.viewChanged}
+ value={NumCast(this.props.CollectionView.props.Document.viewType)}>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
+ </select>
+ <div className="collectionViewBaseChrome-viewSpecs">
+ <input className="collectionViewBaseChrome-viewSpecsInput"
+ placeholder="FILTER DOCUMENTS"
+ value={this.filterValue ? this.filterValue.script.originalScript : ""}
+ onPointerDown={this.openViewSpecs} />
+ <div className="collectionViewBaseChrome-viewSpecsMenu"
+ onPointerDown={this.openViewSpecs}
+ style={{
+ height: this._viewSpecsOpen ? "fit-content" : "0px",
+ overflow: this._viewSpecsOpen ? "initial" : "hidden"
+ }}>
+ {this._keyRestrictions.map(i => i[0])}
+ <div className="collectionViewBaseChrome-viewSpecsMenu-row">
+ <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
+ CREATED WITHIN:
</div>
- <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
- ADD KEY RESTRICTION
+ <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+ style={{ textTransform: "uppercase", textAlign: "center" }}
+ value={this._dateWithinValue}
+ onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
+ <option value="1d">1 day of</option>
+ <option value="3d">3 days of</option>
+ <option value="1w">1 week of</option>
+ <option value="2w">2 weeks of</option>
+ <option value="1m">1 month of</option>
+ <option value="2m">2 months of</option>
+ <option value="6m">6 months of</option>
+ <option value="1y">1 year of</option>
+ </select>
+ <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+ id={this._datePickerElGuid}
+ value={this._dateValue.toLocaleDateString()}
+ onPointerDown={this.openDatePicker}
+ placeholder="Value" />
+ </div>
+ <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
+ <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
+ ADD KEY RESTRICTION
</button>
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
- APPLY FILTER
+ <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
+ APPLY FILTER
</button>
+ </div>
</div>
</div>
</div>
+ {this.subChrome()}
</div>
- {this.subChrome()}
</div>
);
}
@@ -287,7 +311,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
</button>
<div className="collectionStackingViewChrome-sectionFilter-cont">
<div className="collectionStackingViewChrome-sectionFilter-label">
- Group items by:
+ GROUP ITEMS BY:
</div>
<div className="collectionStackingViewChrome-sectionFilter">
<EditableView
@@ -322,108 +346,17 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
}
}
-
+interface SchemaChromeProps extends CollectionViewChromeProps {
+ toolbar: JSX.Element;
+}
@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
- @observable private _currentKey: string = "";
- @observable private suggestions: string[] = [];
-
- @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); }
- @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
-
- getKeySuggestions = async (value: string): Promise<string[]> => {
- value = value.toLowerCase();
- let docs: Doc | Doc[] | Promise<Doc> | Promise<Doc[]> | (() => DocLike)
- = () => DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldExt ? this.props.CollectionView.props.fieldExt : this.props.CollectionView.props.fieldKey]);
- if (typeof docs === "function") {
- docs = docs();
- }
- docs = await docs;
- if (docs instanceof Doc) {
- return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
- }
- }
-
- @action
- onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
- this._currentKey = newValue;
- }
-
- getSuggestionValue = (suggestion: string) => suggestion;
-
- renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
- }
-
- onSuggestionFetch = async ({ value }: { value: string }) => {
- const sugg = await this.getKeySuggestions(value);
- runInAction(() => {
- this.suggestions = sugg;
- });
- }
-
- @action
- onSuggestionClear = () => {
- this.suggestions = [];
- }
-
- setValue = (value: string) => {
- this.props.CollectionView.props.Document.sectionFilter = value;
- return true;
- }
-
- @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; }
- @action resetValue = () => { this._currentKey = this.sectionFilter; };
-
+export class CollectionSchemaViewChrome extends React.Component<SchemaChromeProps> {
render() {
return (
<div className="collectionStackingViewChrome-cont">
- <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}>
- <div className="collectionStackingViewChrome-sortLabel">
- Sort
- </div>
- <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
- <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
- </div>
- </button>
- <div className="collectionStackingViewChrome-sectionFilter-cont">
- <div className="collectionStackingViewChrome-sectionFilter-label">
- Group items by:
- </div>
- <div className="collectionStackingViewChrome-sectionFilter">
- <EditableView
- GetValue={() => this.sectionFilter}
- autosuggestProps={
- {
- resetValue: this.resetValue,
- value: this._currentKey,
- onChange: this.onKeyChange,
- autosuggestProps: {
- inputProps:
- {
- value: this._currentKey,
- onChange: this.onKeyChange
- },
- getSuggestionValue: this.getSuggestionValue,
- suggestions: this.suggestions,
- alwaysRenderSuggestions: true,
- renderSuggestion: this.renderSuggestion,
- onSuggestionsFetchRequested: this.onSuggestionFetch,
- onSuggestionsClearRequested: this.onSuggestionClear
- }
- }}
- oneLine
- SetValue={this.setValue}
- contents={this.sectionFilter ? this.sectionFilter : "N/A"}
- />
- </div>
- </div>
+ {this.props.toolbar}
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 00407d39a..cca199afa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -19,6 +19,11 @@
transform-origin: left top;
}
+.collectionFreeform-customText {
+ position: absolute;
+ text-align: center;
+}
+
.collectionfreeformview-container {
.collectionfreeformview>.jsx-parser {
position: inherit;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 71d8988bf..74535222f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,7 +31,12 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { ScriptBox } from "../../ScriptBox";
import { CompileScript } from "../../../util/Scripting";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faTable, faPaintBrush, faAsterisk } from "@fortawesome/free-solid-svg-icons";
+library.add(faEye, faTable, faPaintBrush);
export const panZoomSchema = createSchema({
panX: "number",
@@ -51,6 +56,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _lastY: number = 0;
private get _pwidth() { return this.props.PanelWidth(); }
private get _pheight() { return this.props.PanelHeight(); }
+ private inkKey = "ink";
@computed get contentBounds() {
let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
@@ -358,8 +364,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
let self = this;
let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
- resolvedDataDoc && Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
- let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+ let layoutDoc = childDocLayout;
+ if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
+ Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
+ let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+ layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
+ } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
return {
DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
Document: layoutDoc,
@@ -414,6 +424,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return result.result === undefined ? {} : result.result;
}
+ private viewDefToJSX(viewDef: any): JSX.Element | undefined {
+ if (viewDef.type === "text") {
+ const text = Cast(viewDef.text, "string");
+ const x = Cast(viewDef.x, "number");
+ const y = Cast(viewDef.y, "number");
+ const width = Cast(viewDef.width, "number");
+ const height = Cast(viewDef.height, "number");
+ const fontSize = Cast(viewDef.fontSize, "number");
+ if ([text, x, y].some(val => val === undefined)) {
+ return undefined;
+ }
+
+ return <div className="collectionFreeform-customText" style={{
+ transform: `translate(${x}px, ${y}px)`,
+ width, height, fontSize
+ }}>{text}</div>;
+ }
+ }
+
@computed.struct
get views() {
let curPage = FieldValue(this.Document.curPage, -1);
@@ -421,10 +450,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const script = this.Document.arrangeScript;
let state: any = undefined;
const docs = this.childDocs;
+ let elements: JSX.Element[] = [];
if (initScript) {
const initResult = initScript.script.run({ docs, collection: this.Document });
if (initResult.success) {
- state = initResult.result;
+ const result = initResult.result;
+ const { state: scriptState, views } = result;
+ state = scriptState;
+ if (Array.isArray(views)) {
+ elements = views.reduce<JSX.Element[]>((prev, ele) => {
+ const jsx = this.viewDefToJSX(ele);
+ jsx && prev.push(jsx);
+ return prev;
+ }, elements);
+ }
}
}
let docviews = docs.reduce((prev, doc) => {
@@ -439,7 +478,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
return prev;
- }, [] as JSX.Element[]);
+ }, elements);
setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
@@ -454,6 +493,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onContextMenu = () => {
ContextMenu.Instance.addItem({
description: "Arrange contents in grid",
+ icon: "table",
event: async () => {
const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
UndoManager.RunInBatch(() => {
@@ -479,42 +519,51 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
});
ContextMenu.Instance.addItem({
- description: "Add freeform arrangement",
- event: () => {
- let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction;
- const script = this.Document[key];
- let originalText: string | undefined = undefined;
- if (script) originalText = script.script.originalScript;
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
- const script = CompileScript(text, {
- params,
- requiredType,
- typecheck: false
- });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- return;
- }
- const docs = DocListCast(this.Document[this.props.fieldKey]);
- docs.map(d => d.transition = "transform 1s");
- this.Document[key] = new ScriptField(script);
- overlayDisposer();
- setTimeout(() => docs.map(d => d.transition = undefined), 1200);
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- }
+ description: "Analyze Strokes", event: async () => {
+ let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
+ if (!data) {
+ return;
+ }
+ let relevantKeys = ["inkAnalysis", "handwriting"];
+ CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData);
+ }, icon: "paint-brush"
});
}
+
private childViews = () => [
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
]
+
+ public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
+ return () => {
+ let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+ let overlayDisposer: () => void = emptyFunction;
+ const script = Cast(doc[key], ScriptField);
+ let originalText: string | undefined = undefined;
+ if (script) originalText = script.script.originalScript;
+ // tslint:disable-next-line: no-unnecessary-callback-wrapper
+ let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+ const script = CompileScript(text, {
+ params,
+ requiredType,
+ typecheck: false
+ });
+ if (!script.compiled) {
+ onError(script.errors.map(error => error.messageText).join("\n"));
+ return;
+ }
+ doc[key] = new ScriptField(script);
+ overlayDisposer();
+ }} />;
+ overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
+ };
+ addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+ addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+ };
+ }
+
render() {
const easing = () => this.props.Document.panTransformType === "Ease";
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss
new file mode 100644
index 000000000..97cc91128
--- /dev/null
+++ b/src/client/views/nodes/ButtonBox.scss
@@ -0,0 +1,10 @@
+.buttonBox-outerDiv {
+ width: 100%;
+ height: 100%;
+ pointer-events: all;
+}
+
+.buttonBox-mainButton {
+ width: 100%;
+ height: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
new file mode 100644
index 000000000..744611661
--- /dev/null
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -0,0 +1,75 @@
+import * as React from 'react';
+import { FieldViewProps, FieldView } from './FieldView';
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { ScriptField } from '../../../new_fields/ScriptField';
+import { DocComponent } from '../DocComponent';
+import { ContextMenu } from '../ContextMenu';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit } from '@fortawesome/free-regular-svg-icons';
+import { emptyFunction } from '../../../Utils';
+import { ScriptBox } from '../ScriptBox';
+import { CompileScript } from '../../util/Scripting';
+import { OverlayView } from '../OverlayView';
+import { Doc } from '../../../new_fields/Doc';
+
+import './ButtonBox.scss';
+import { observer } from 'mobx-react';
+
+library.add(faEdit);
+
+const ButtonSchema = createSchema({
+ onClick: ScriptField,
+ text: "string"
+});
+
+type ButtonDocument = makeInterface<[typeof ButtonSchema]>;
+const ButtonDocument = makeInterface(ButtonSchema);
+
+@observer
+export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) {
+ public static LayoutString() { return FieldView.LayoutString(ButtonBox); }
+
+ onClick = (e: React.MouseEvent) => {
+ const onClick = this.Document.onClick;
+ if (!onClick) {
+ return;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ onClick.script.run({ this: this.props.Document });
+ }
+
+ onContextMenu = () => {
+ ContextMenu.Instance.addItem({
+ description: "Edit OnClick script", icon: "edit", event: () => {
+ let overlayDisposer: () => void = emptyFunction;
+ const script = this.Document.onClick;
+ let originalText: string | undefined = undefined;
+ if (script) originalText = script.script.originalScript;
+ // tslint:disable-next-line: no-unnecessary-callback-wrapper
+ let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+ const script = CompileScript(text, {
+ params: { this: Doc.name },
+ typecheck: false,
+ editable: true
+ });
+ if (!script.compiled) {
+ onError(script.errors.map(error => error.messageText).join("\n"));
+ return;
+ }
+ this.Document.onClick = new ScriptField(script);
+ overlayDisposer();
+ }} />;
+ overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: `${this.Document.title || ""} OnClick` });
+ }
+ });
+ }
+
+ render() {
+ return (
+ <div className="buttonBox-outerDiv" onContextMenu={this.onContextMenu}>
+ <button className="buttonBox-mainButton" onClick={this.onClick}>{this.Document.text || "Button"}</button>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b09538d1a..389b9f68b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -82,6 +82,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
render() {
+ const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
return (
<div className="collectionFreeFormDocumentView-container"
style={{
@@ -90,7 +91,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
backgroundColor: "transparent",
borderRadius: this.borderRounding(),
transform: this.transform,
- transition: StrCast(this.props.Document.transition),
+ transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
width: this.width,
height: this.height,
zIndex: this.Document.zIndex || 0,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ef65c12cf..91d4fb524 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -11,6 +11,7 @@ import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
+import { ButtonBox } from "./ButtonBox";
import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
@@ -97,7 +98,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (this.props.renderDepth > 7) return (null);
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
- components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
bindings={this.CreateBindings()}
jsx={this.finalLayout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2999babf1..16cfeba3f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -37,6 +37,7 @@ import { RouteStore } from '../../../server/RouteStore';
import { FormattedTextBox } from './FormattedTextBox';
import { OverlayView } from '../OverlayView';
import { ScriptingRepl } from '../ScriptingRepl';
+import { ClientUtils } from '../../util/ClientUtils';
import { EditableView } from '../EditableView';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -59,6 +60,7 @@ library.add(fa.faCrosshairs);
library.add(fa.faDesktop);
library.add(fa.faUnlock);
library.add(fa.faLock);
+library.add(fa.faLaptopCode);
// const linkSchema = createSchema({
@@ -193,10 +195,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DocumentView.animateBetweenIconFunc(doc, width, height, stime, maximizing, cb);
}
else {
- Doc.GetProto(doc).isMinimized = !maximizing;
- Doc.GetProto(doc).isIconAnimating = undefined;
+ doc.isMinimized = !maximizing;
+ doc.isIconAnimating = undefined;
}
- Doc.GetProto(doc).willMaximize = false;
+ doc.willMaximize = false;
},
2);
}
@@ -295,12 +297,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (this._doubleTap && this.props.renderDepth) {
let fullScreenAlias = Doc.MakeAlias(this.props.Document);
fullScreenAlias.templates = new List<string>();
- if (this.props.Document.layout === this.props.Document.miniLayout) {
- Doc.ToggleDetailLayout(fullScreenAlias);
- }
+ Doc.UseDetailLayout(fullScreenAlias);
this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
SelectionManager.DeselectAll();
- this.props.Document.libraryBrush = undefined;
+ this.props.Document.libraryBrush = false;
}
else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
@@ -319,20 +319,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
// let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents
- let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc));
let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
if (altKey || ctrlKey) {
maxLocation = this.props.Document.maximizeLocation = (ctrlKey ? maxLocation : (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace"));
if (!maxLocation || maxLocation === "inPlace") {
- let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+ let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
- let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+ let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
if (!hasView) {
this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false));
}
- expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
+ expandedDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
}
}
if (maxLocation && maxLocation !== "inPlace" && CollectionDockingView.Instance) {
@@ -344,7 +343,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
} else {
let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
- this.collapseTargetsToPoint(scrpt, expandedProtoDocs);
+ this.collapseTargetsToPoint(scrpt, expandedDocs);
}
}
else if (linkedDocs.length) {
@@ -556,20 +555,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.removeDocument && this.props.removeDocument(this.props.Document);
}, icon: "window-restore"
});
- cm.addItem({
- description: "Find aliases", event: async () => {
- const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
- this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
- }, icon: "search"
- });
+ // cm.addItem({
+ // description: "Find aliases", event: async () => {
+ // const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+ // this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
+ // }, icon: "search"
+ // });
if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
}
- cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
+ cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
- cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
- cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ if (!ClientUtils.RELEASE) {
+ cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+ cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ }
cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
type User = { email: string, userDocumentId: string };
let usersMenu: ContextMenuProps[] = [];
@@ -611,7 +612,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
- onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = undefined; };
+ onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
isSelected = () => SelectionManager.IsSelected(this);
@action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ea6730cd0..ffaee8042 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -87,7 +87,8 @@ export class FieldView extends React.Component<FieldViewProps> {
return <p>{field.date.toLocaleString()}</p>;
}
else if (field instanceof Doc) {
- return <p><b>{field.title + " : id= " + field[Id]}</b></p>;
+ return <p><b>{field.title}</b></p>;
+ //return <p><b>{field.title + " : id= " + field[Id]}</b></p>;
// let returnHundred = () => 100;
// return (
// <DocumentContentsView Document={field}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0f60bd0fb..6541007d0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faImage, faFileAudio } from '@fortawesome/free-solid-svg-icons';
+import { faImage, faFileAudio, faPaintBrush, faAsterisk } from '@fortawesome/free-solid-svg-icons';
import { action, observable, computed, runInAction } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
@@ -27,13 +27,14 @@ import { Font } from '@react-pdf/renderer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
import FaceRectangles from './FaceRectangles';
+import { faEye } from '@fortawesome/free-regular-svg-icons';
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
const { Howl, Howler } = require('howler');
-library.add(faImage);
-library.add(faFileAudio);
+library.add(faImage, faEye, faPaintBrush);
+library.add(faFileAudio, faAsterisk);
export const pageSchema = createSchema({
@@ -175,7 +176,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
const url = Utils.prepend(files[0]);
// upload to server with known URL
let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", x: NumCast(self.props.Document.x), y: NumCast(self.props.Document.y), width: 200, height: 32 });
- audioDoc.embed = true;
+ audioDoc.treeViewExpandedView = "layout";
let audioAnnos = Cast(self.extensionDoc.audioAnnotations, listSpec(Doc));
if (audioAnnos === undefined) {
self.extensionDoc.audioAnnotations = new List([audioDoc]);
@@ -216,12 +217,12 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
});
let modes: ContextMenuProps[] = [];
- let dataDoc = Doc.GetProto(this.Document);
+ let dataDoc = Doc.GetProto(this.props.Document);
modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
- ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
- ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
+ ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" });
+ ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" });
}
}
@@ -355,7 +356,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let rotation = NumCast(this.dataDoc.rotation, 0);
let aspect = (rotation % 180) ? this.dataDoc[HeightSym]() / this.dataDoc[WidthSym]() : 1;
let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
- let srcpath = paths[Math.min(paths.length, this.Document.curPage || 0)];
+ let srcpath = paths[Math.min(paths.length - 1, this.Document.curPage || 0)];
if (!this.props.Document.ignoreAspect && !this.props.leaveNativeSize) this.resize(srcpath, this.props.Document);
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f80840f96..f2fef7f16 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -31,6 +31,8 @@ export interface PresViewProps {
Documents: List<Doc>;
}
+const expandedWidth = 400;
+
@observer
export class PresentationView extends React.Component<PresViewProps> {
public static Instance: PresentationView;
@@ -61,12 +63,25 @@ export class PresentationView extends React.Component<PresViewProps> {
@observable titleInputElement: HTMLInputElement | undefined;
@observable PresTitleChangeOpen: boolean = false;
+ @observable opacity = 1;
+ @observable persistOpacity = true;
+ @observable labelOpacity = 0;
+
//initilize class variables
constructor(props: PresViewProps) {
super(props);
PresentationView.Instance = this;
}
+ @action
+ toggle = (forcedValue: boolean | undefined) => {
+ if (forcedValue !== undefined) {
+ this.curPresentation.width = forcedValue ? expandedWidth : 0;
+ } else {
+ this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
+ }
+ }
+
//The first lifecycle function that gets called to set up the current presentation.
async componentWillMount() {
this.props.Documents.forEach(async (doc, index: number) => {
@@ -543,7 +558,7 @@ export class PresentationView extends React.Component<PresViewProps> {
this.curPresentation.data = new List([doc]);
}
- this.curPresentation.width = 400;
+ this.toggle(true);
}
//Function that sets the store of the children docs.
@@ -800,7 +815,7 @@ export class PresentationView extends React.Component<PresViewProps> {
let width = NumCast(this.curPresentation.width);
return (
- <div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}>
+ <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}>
<div className="presentationView-heading">
{this.renderSelectOrPresSelection()}
<button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button>
@@ -819,6 +834,18 @@ export class PresentationView extends React.Component<PresViewProps> {
{this.renderPlayPauseButton()}
<button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
</div>
+ <input
+ type="checkbox"
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.persistOpacity = e.target.checked;
+ this.opacity = this.persistOpacity ? 1 : 0.4;
+ })}
+ checked={this.persistOpacity}
+ style={{ position: "absolute", bottom: 5, left: 5 }}
+ onPointerEnter={action(() => this.labelOpacity = 1)}
+ onPointerLeave={action(() => this.labelOpacity = 0)}
+ />
+ <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p>
<PresentationViewList
mainDocument={this.curPresentation}
deleteDocument={this.RemoveDoc}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index a995140e2..5c2ced2eb 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -205,13 +205,13 @@ export class SearchItem extends React.Component<SearchItemProps> {
let doc1 = Cast(this.props.doc.anchor1, Doc, null);
let doc2 = Cast(this.props.doc.anchor2, Doc, null);
- doc1 && (doc1.libraryBrush = undefined);
- doc2 && (doc2.libraryBrush = undefined);
+ doc1 && (doc1.libraryBrush = false);
+ doc2 && (doc2.libraryBrush = false);
}
} else {
let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
docViews.forEach(element => {
- element.props.Document.libraryBrush = undefined;
+ element.props.Document.libraryBrush = false;
});
}
}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index eba1d4f04..bb6130992 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -1,6 +1,6 @@
import { observable, action } from "mobx";
-import { serializable, primitive, map, alias, list } from "serializr";
-import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr";
+import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper";
import { DocServer } from "../client/DocServer";
import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util";
import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast, StrCast } from "./Types";
@@ -68,8 +68,17 @@ export function DocListCast(field: FieldResult): Doc[] {
export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
+function fetchProto(doc: Doc) {
+ const proto = doc.proto;
+ if (proto instanceof Promise) {
+ return proto;
+ }
+}
+
+let updatingFromServer = false;
+
@scriptingGlobal
-@Deserializable("doc").withFields(["id"])
+@Deserializable("Doc", fetchProto).withFields(["id"])
export class Doc extends RefField {
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
@@ -102,7 +111,7 @@ export class Doc extends RefField {
proto: Opt<Doc>;
[key: string]: FieldResult;
- @serializable(alias("fields", map(autoObject())))
+ @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
}
@@ -122,6 +131,9 @@ export class Doc extends RefField {
private ___fields: any = {};
private [Update] = (diff: any) => {
+ if (updatingFromServer) {
+ return;
+ }
DocServer.UpdateField(this[Id], diff);
}
@@ -134,16 +146,18 @@ export class Doc extends RefField {
return "invalid";
}
- public [HandleUpdate](diff: any) {
+ public async [HandleUpdate](diff: any) {
const set = diff.$set;
if (set) {
for (const key in set) {
if (!key.startsWith("fields.")) {
continue;
}
- const value = SerializationHelper.Deserialize(set[key]);
+ const value = await SerializationHelper.Deserialize(set[key]);
const fKey = key.substring(7);
+ updatingFromServer = true;
this[fKey] = value;
+ updatingFromServer = false;
}
}
const unset = diff.$unset;
@@ -153,7 +167,9 @@ export class Doc extends RefField {
continue;
}
const fKey = key.substring(7);
+ updatingFromServer = true;
delete this[fKey];
+ updatingFromServer = false;
}
}
}
@@ -321,20 +337,22 @@ export namespace Doc {
}
export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string) {
- let extensionDoc = doc[fieldKey + "_ext"];
- if (extensionDoc === undefined) {
+ let docExtensionForField = doc[fieldKey + "_ext"] as Doc;
+ if (docExtensionForField === undefined) {
setTimeout(() => {
- let docExtensionForField = new Doc(doc[Id] + fieldKey, true);
- docExtensionForField.title = doc.title + ":" + fieldKey + ".ext";
- docExtensionForField.extendsDoc = doc;
+ docExtensionForField = new Doc(doc[Id] + fieldKey, true);
+ docExtensionForField.title = fieldKey + ".ext";
+ docExtensionForField.extendsDoc = doc; // this is used by search to map field matches on the extension doc back to the document it extends.
+ docExtensionForField.type = DocumentType.EXTENSION;
let proto: Doc | undefined = doc;
while (proto && !Doc.IsPrototype(proto)) {
proto = proto.proto;
}
(proto ? proto : doc)[fieldKey + "_ext"] = docExtensionForField;
}, 0);
- } else if (extensionDoc instanceof Doc && extensionDoc.extendsDoc === undefined) {
- setTimeout(() => (extensionDoc as Doc).extendsDoc = doc, 0);
+ } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already
+ docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0);
+ docExtensionForField.type === undefined && setTimeout(() => docExtensionForField.type = DocumentType.EXTENSION, 0);
}
}
export function MakeAlias(doc: Doc) {
@@ -344,11 +362,25 @@ export namespace Doc {
return Doc.MakeDelegate(doc); // bcz?
}
+ //
+ // Determines whether the combination of the layoutDoc and dataDoc represents
+ // a template relationship. If so, the layoutDoc will be expanded into a new
+ // document that inherits the properties of the original layout while allowing
+ // for individual layout properties to be overridden in the expanded layout.
+ //
+ export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
+ return BoolCast(layoutDoc.isTemplate) && dataDoc && layoutDoc !== dataDoc && !(layoutDoc.layout instanceof Doc);
+ }
+
+ //
+ // Returns an expanded template layout for a target data document.
+ // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
+ // using the template layout doc's id as the field key.
+ // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
+ // saves it on the data doc indexed by the template layout's id
+ //
export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
- let resolvedDataDoc = (templateLayoutDoc !== dataDoc) ? dataDoc : undefined;
- if (!dataDoc || !(templateLayoutDoc && !(Cast(templateLayoutDoc.layout, Doc) instanceof Doc) && resolvedDataDoc && resolvedDataDoc !== templateLayoutDoc)) {
- return templateLayoutDoc;
- }
+ if (!WillExpandTemplateLayout(templateLayoutDoc, dataDoc) || !dataDoc) return templateLayoutDoc;
// if we have a data doc that doesn't match the layout, then we're rendering a template.
// ... which means we change the layout to be an expanded view of the template layout.
// This allows the view override the template's properties and be referenceable as its own document.
@@ -357,17 +389,14 @@ export namespace Doc {
if (expandedTemplateLayout instanceof Doc) {
return expandedTemplateLayout;
}
- expandedTemplateLayout = dataDoc[templateLayoutDoc.title + templateLayoutDoc[Id]];
+ let expandedLayoutFieldKey = "Layout[" + templateLayoutDoc[Id] + "]";
+ expandedTemplateLayout = dataDoc[expandedLayoutFieldKey];
if (expandedTemplateLayout instanceof Doc) {
return expandedTemplateLayout;
}
- if (expandedTemplateLayout === undefined && BoolCast(templateLayoutDoc.isTemplate)) {
- setTimeout(() => {
- let expandedDoc = Doc.MakeDelegate(templateLayoutDoc);
- expandedDoc.title = templateLayoutDoc.title + "[" + StrCast(dataDoc.title).match(/\.\.\.[0-9]*/) + "]";
- expandedDoc.isExpandedTemplate = templateLayoutDoc;
- dataDoc[templateLayoutDoc.title + templateLayoutDoc[Id]] = expandedDoc;
- }, 0);
+ if (expandedTemplateLayout === undefined) {
+ setTimeout(() =>
+ dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "["+templateLayoutDoc.title + "]"), 0);
}
return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
}
@@ -396,12 +425,13 @@ export namespace Doc {
return copy;
}
- export function MakeDelegate(doc: Doc, id?: string): Doc;
- export function MakeDelegate(doc: Opt<Doc>, id?: string): Opt<Doc>;
- export function MakeDelegate(doc: Opt<Doc>, id?: string): Opt<Doc> {
+ export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
+ export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
+ export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
if (doc) {
const delegate = new Doc(id, true);
delegate.proto = doc;
+ title && (delegate.title = title);
return delegate;
}
return undefined;
@@ -430,7 +460,6 @@ export namespace Doc {
}
let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
if (backgroundLayout) {
- layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
}
let nw = Cast(fieldTemplate.nativeWidth, "number");
@@ -440,6 +469,7 @@ export namespace Doc {
layoutDelegate.layout = layout;
fieldTemplate.title = metaKey;
+ fieldTemplate.templateField = metaKey;
fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
fieldTemplate.backgroundLayout = backgroundLayout;
fieldTemplate.nativeWidth = nw;
@@ -455,4 +485,12 @@ export namespace Doc {
d.layout !== miniLayout ? miniLayout && (d.layout = d.miniLayout) : detailLayout && (d.layout = detailLayout);
if (d.layout === detailLayout) Doc.GetProto(d).nativeWidth = Doc.GetProto(d).nativeHeight = undefined;
}
+ export async function UseDetailLayout(d: Doc) {
+ let miniLayout = await PromiseValue(d.miniLayout);
+ let detailLayout = await PromiseValue(d.detailedLayout);
+ if (miniLayout && d.layout === miniLayout && detailLayout) {
+ d.layout = detailLayout;
+ d.nativeWidth = d.nativeHeight = undefined;
+ }
+ }
} \ No newline at end of file
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
index a436dcf2b..b5b3aa588 100644
--- a/src/new_fields/FieldSymbols.ts
+++ b/src/new_fields/FieldSymbols.ts
@@ -7,4 +7,4 @@ export const Id = Symbol("Id");
export const OnUpdate = Symbol("OnUpdate");
export const Parent = Symbol("Parent");
export const Copy = Symbol("Copy");
-export const ToScriptString = Symbol("Copy"); \ No newline at end of file
+export const ToScriptString = Symbol("ToScriptString"); \ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 39c6c8ce3..8f64c1c2e 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -19,6 +19,8 @@ export interface StrokeData {
page: number;
}
+export type InkData = Map<string, StrokeData>;
+
const pointSchema = createSimpleSchema({
x: true, y: true
});
@@ -31,9 +33,9 @@ const strokeDataSchema = createSimpleSchema({
@Deserializable("ink")
export class InkField extends ObjectField {
@serializable(map(object(strokeDataSchema)))
- readonly inkData: Map<string, StrokeData>;
+ readonly inkData: InkData;
- constructor(data?: Map<string, StrokeData>) {
+ constructor(data?: InkData) {
super();
this.inkData = data || new Map;
}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index a2133a990..0c7b77fa5 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,4 +1,4 @@
-import { Deserializable, autoObject } from "../client/util/SerializationHelper";
+import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/SerializationHelper";
import { Field } from "./Doc";
import { setter, getter, deleteProperty, updateFunction } from "./util";
import { serializable, alias, list } from "serializr";
@@ -254,7 +254,7 @@ class ListImpl<T extends Field> extends ObjectField {
[key: number]: T | (T extends RefField ? Promise<T> : never);
- @serializable(alias("fields", list(autoObject())))
+ @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
}
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index 38d874a68..14f08814e 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -48,7 +48,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
private failed = false;
private promise?: Promise<any>;
- value(): T | undefined | FieldWaiting {
+ value(): T | undefined | FieldWaiting<T> {
if (this.cache) {
return this.cache;
}
@@ -63,6 +63,6 @@ export class ProxyField<T extends RefField> extends ObjectField {
return field;
}));
}
- return this.promise;
+ return this.promise as any;
}
}
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
index 75ce4287f..f7bea8c94 100644
--- a/src/new_fields/RefField.ts
+++ b/src/new_fields/RefField.ts
@@ -1,10 +1,11 @@
import { serializable, primitive, alias } from "serializr";
import { Utils } from "../Utils";
import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols";
+import { afterDocDeserialize } from "../client/util/SerializationHelper";
export type FieldId = string;
export abstract class RefField {
- @serializable(alias("id", primitive()))
+ @serializable(alias("id", primitive({ afterDeserialize: afterDocDeserialize })))
private __id: FieldId;
readonly [Id]: FieldId;
@@ -13,7 +14,7 @@ export abstract class RefField {
this[Id] = this.__id;
}
- protected [HandleUpdate]?(diff: any): void;
+ protected [HandleUpdate]?(diff: any): void | Promise<void>;
abstract [ToScriptString](): string;
}
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index e5ec34f57..00b4dec2c 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -2,10 +2,11 @@ import { ObjectField } from "./ObjectField";
import { CompiledScript, CompileScript, scriptingGlobal } from "../client/util/Scripting";
import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols";
import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
-import { Deserializable } from "../client/util/SerializationHelper";
+import { Deserializable, autoObject } from "../client/util/SerializationHelper";
import { Doc } from "../new_fields/Doc";
import { Plugins } from "./util";
import { computedFn } from "mobx-utils";
+import { ProxyField } from "./Proxy";
function optional(propSchema: PropSchema) {
return custom(value => {
@@ -34,7 +35,16 @@ const scriptSchema = createSimpleSchema({
originalScript: true
});
-function deserializeScript(script: ScriptField) {
+async function deserializeScript(script: ScriptField) {
+ const captures: ProxyField<Doc> = (script as any).captures;
+ if (captures) {
+ const doc = (await captures.value())!;
+ const captured: any = {};
+ const keys = Object.keys(doc);
+ const vals = await Promise.all(keys.map(key => doc[key]) as any);
+ keys.forEach((key, i) => captured[key] = vals[i]);
+ (script.script.options as any).capturedVariables = captured;
+ }
const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
@@ -48,9 +58,17 @@ export class ScriptField extends ObjectField {
@serializable(object(scriptSchema))
readonly script: CompiledScript;
+ @serializable(autoObject())
+ private captures?: ProxyField<Doc>;
+
constructor(script: CompiledScript) {
super();
+ if (script && script.options.capturedVariables) {
+ const doc = Doc.assign(new Doc, script.options.capturedVariables);
+ this.captures = new ProxyField(doc);
+ }
+
this.script = script;
}
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 700269727..48b8fe3fa 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -15,7 +15,9 @@ source = "./source"
dist = "../../server/public/files"
db = MongoClient("localhost", 27017)["Dash"]
+target_collection = db.newDocuments
schema_guids = []
+common_proto_id = ""
def extract_links(fileName):
@@ -84,7 +86,7 @@ def write_schema(parse_results, display_fields, storage_key):
"height": 600,
"panX": 0,
"panY": 0,
- "zoomBasis": 0.5,
+ "zoomBasis": 1,
"zIndex": 2,
"libraryBrush": False,
"viewType": 2
@@ -92,7 +94,7 @@ def write_schema(parse_results, display_fields, storage_key):
"__type": "Doc"
}
- fields["proto"] = protofy("collectionProto")
+ fields["proto"] = protofy(common_proto_id)
fields[storage_key] = listify(proxify_guids(view_guids))
fields["schemaColumns"] = listify(display_fields)
fields["backgroundColor"] = "white"
@@ -106,8 +108,8 @@ def write_schema(parse_results, display_fields, storage_key):
fields["isPrototype"] = True
fields["page"] = -1
- db.newDocuments.insert_one(data_doc)
- db.newDocuments.insert_one(view_doc)
+ target_collection.insert_one(data_doc)
+ target_collection.insert_one(view_doc)
data_doc_guid = data_doc["_id"]
print(f"inserted view document ({view_doc_guid})")
@@ -136,7 +138,7 @@ def write_text_doc(content):
data_doc = {
"_id": data_doc_guid,
"fields": {
- "proto": protofy("textProto"),
+ "proto": protofy("commonImportProto"),
"data": {
"Data": '{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + content + '"}]}]},"selection":{"type":"text","anchor":1,"head":1}' + '}',
"__type": "RichTextField"
@@ -158,8 +160,8 @@ def write_text_doc(content):
"__type": "Doc"
}
- db.newDocuments.insert_one(view_doc)
- db.newDocuments.insert_one(data_doc)
+ target_collection.insert_one(view_doc)
+ target_collection.insert_one(data_doc)
return view_doc_guid
@@ -209,8 +211,8 @@ def write_image(folder, name):
"__type": "Doc"
}
- db.newDocuments.insert_one(view_doc)
- db.newDocuments.insert_one(data_doc)
+ target_collection.insert_one(view_doc)
+ target_collection.insert_one(data_doc)
return view_doc_guid
@@ -347,6 +349,22 @@ def proxify_guids(guids):
return list(map(lambda guid: {"fieldId": guid, "__type": "proxy"}, guids))
+def write_common_proto():
+ id = guid()
+ common_proto = {
+ "_id": id,
+ "fields": {
+ "proto": protofy("collectionProto"),
+ "title": "Common Import Proto",
+ },
+ "__type": "Doc"
+ }
+
+ target_collection.insert_one(common_proto)
+
+ return id
+
+
if os.path.exists(dist):
shutil.rmtree(dist)
while os.path.exists(dist):
@@ -354,6 +372,8 @@ while os.path.exists(dist):
os.mkdir(dist)
mkdir_if_absent(source)
+common_proto_id = write_common_proto()
+
candidates = 0
for file_name in os.listdir(source):
if file_name.endswith('.docx'):
@@ -372,7 +392,7 @@ parent_guid = write_schema({
}, ["title", "short_description", "original_price"], "data")
print("appending parent schema to main workspace...\n")
-db.newDocuments.update_one(
+target_collection.update_one(
{"fields.title": "WS collection 1"},
{"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}}
)
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 1c52a3f11..41eb1aa0c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -50,6 +50,7 @@ export class CurrentUserUtils {
if (doc.workspaces === undefined) {
const workspaces = Docs.Create.TreeDocument([], { title: "Workspaces", height: 100 });
workspaces.excludeFromLibrary = true;
+ workspaces.chromeStatus = "disabled";
workspaces.workspaceLibrary = true;
workspaces.boxShadow = "0 0";
doc.workspaces = workspaces;
@@ -57,6 +58,7 @@ export class CurrentUserUtils {
if (doc.recentlyClosed === undefined) {
const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 });
recentlyClosed.excludeFromLibrary = true;
+ recentlyClosed.chromeStatus = "disabled";
recentlyClosed.boxShadow = "0 0";
doc.recentlyClosed = recentlyClosed;
}
@@ -65,11 +67,13 @@ export class CurrentUserUtils {
sidebar.excludeFromLibrary = true;
sidebar.gridGap = 5;
sidebar.xMargin = 5;
+ sidebar.chromeStatus = "disabled";
sidebar.yMargin = 5;
Doc.GetProto(sidebar).backgroundColor = "#aca3a6";
sidebar.boxShadow = "1 1 3";
doc.sidebar = sidebar;
}
+ doc.chromeStatus = "disabled";
StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 5b086a2cf..40c0e7981 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -139,6 +139,16 @@ app.get("/pull", (req, res) =>
res.redirect("/");
}));
+app.get("/version", (req, res) => {
+ exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => {
+ if (err) {
+ res.send(err.message);
+ return;
+ }
+ res.send(stdout);
+ });
+});
+
// SEARCH
// GETTERS
@@ -284,18 +294,15 @@ addSecureRoute(
RouteStore.getCurrUser
);
+const ServicesApiKeyMap = new Map<string, string | undefined>([
+ ["face", process.env.FACE],
+ ["vision", process.env.VISION],
+ ["handwriting", process.env.HANDWRITING]
+]);
+
addSecureRoute(Method.GET, (user, res, req) => {
- let requested = req.params.requestedservice;
- switch (requested) {
- case "face":
- res.send(process.env.FACE);
- break;
- case "vision":
- res.send(process.env.VISION);
- break;
- default:
- res.send(undefined);
- }
+ let service = req.params.requestedservice;
+ res.send(ServicesApiKeyMap.get(service));
}, undefined, `${RouteStore.cognitiveServices}/:requestedservice`);
class NodeCanvasFactory {