From 2a313f28fcb8675223708b0657de7517a3281095 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 17 Apr 2024 12:27:21 -0400 Subject: restoring eslint - updates not complete yet --- src/fields/RichTextUtils.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'src/fields/RichTextUtils.ts') diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index b84a91709..d75d66bf8 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -1,21 +1,22 @@ import { AssertionError } from 'assert'; +import * as Color from 'color'; import { docs_v1 } from 'googleapis'; import { Fragment, Mark, Node } from 'prosemirror-model'; import { sinkListItem } from 'prosemirror-schema-list'; import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; -import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; -import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils'; +import { ClientUtils, DashColor } from '../ClientUtils'; +import { Utils } from '../Utils'; import { DocServer } from '../client/DocServer'; -import { Docs, DocUtils } from '../client/documents/Documents'; import { Networking } from '../client/Network'; +import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; +import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils'; +import { DocUtils, Docs } from '../client/documents/Documents'; import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox'; import { schema } from '../client/views/nodes/formattedText/schema_rts'; -import { DashColor, Utils } from '../Utils'; import { Doc, Opt } from './Doc'; import { Id } from './FieldSymbols'; import { RichTextField } from './RichTextField'; import { Cast, StrCast } from './Types'; -import * as Color from 'color'; export namespace RichTextUtils { const delimiter = '\n'; @@ -140,8 +141,8 @@ export namespace RichTextUtils { inlineObjectMap.set(object.objectId!, { title: embeddedObject.title || `Imported Image from ${document.title}`, width, - url: Utils.prepend(_m.client), - agnostic: Utils.prepend(agnostic.client), + url: ClientUtils.prepend(_m.client), + agnostic: ClientUtils.prepend(agnostic.client), }); } } @@ -401,7 +402,7 @@ export namespace RichTextUtils { DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); linkDoc.link_anchor_2 = exported; } - url = Utils.shareUrl(exported[Id]); + url = ClientUtils.shareUrl(exported[Id]); } } value = { url }; -- cgit v1.2.3-70-g09d2 From 9dc32440852c8af3575687d96f0442bf18542671 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 29 Apr 2024 23:51:24 -0400 Subject: more eslint --- package-lock.json | 1 + package.json | 1 + .../apis/google_docs/GoogleApiClientUtils.ts | 49 ++-- src/client/cognitive_services/CognitiveServices.ts | 46 ++-- src/client/util/CurrentUserUtils.ts | 26 +- src/client/util/DictationManager.ts | 7 +- src/client/util/HypothesisUtils.ts | 21 +- src/client/util/Scripting.ts | 4 +- src/client/util/SelectionManager.ts | 2 +- src/client/views/newlightbox/ExploreView/utils.ts | 21 +- .../newlightbox/components/Recommendation/utils.ts | 38 +-- src/client/views/nodes/MapBox/AnimationUtility.ts | 3 + src/client/views/nodes/MapBox/MapBox.tsx | 293 +++++++++++---------- src/fields/RichTextUtils.ts | 194 +++++++------- 14 files changed, 371 insertions(+), 335 deletions(-) (limited to 'src/fields/RichTextUtils.ts') diff --git a/package-lock.json b/package-lock.json index 642e978cd..80aef5232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "@types/find-in-files": "^0.5.3", "@types/fluent-ffmpeg": "^2.1.24", "@types/formidable": "3.4.5", + "@types/geojson": "^7946.0.14", "@types/google-maps": "^3.2.6", "@types/mapbox-gl": "^3.1.0", "@types/pdf-parse": "^1.1.4", diff --git a/package.json b/package.json index 90e3f39c0..d826c855e 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "@types/find-in-files": "^0.5.3", "@types/fluent-ffmpeg": "^2.1.24", "@types/formidable": "3.4.5", + "@types/geojson": "^7946.0.14", "@types/google-maps": "^3.2.6", "@types/mapbox-gl": "^3.1.0", "@types/pdf-parse": "^1.1.4", diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 3c2f923ea..0b303eacf 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,4 +1,7 @@ -import { docs_v1 } from 'googleapis'; +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-use-before-define */ +import { docs_v1 as docsV1 } from 'googleapis'; +// eslint-disable-next-line node/no-deprecated-api import { isArray } from 'util'; import { EditorState } from 'prosemirror-state'; import { Opt } from '../../../fields/Doc'; @@ -15,12 +18,12 @@ export namespace GoogleApiClientUtils { } export namespace Docs { - export type RetrievalResult = Opt; - export type UpdateResult = Opt; + export type RetrievalResult = Opt; + export type UpdateResult = Opt; export interface UpdateOptions { documentId: DocumentId; - requests: docs_v1.Schema$Request[]; + requests: docsV1.Schema$Request[]; } export enum WriteMode { @@ -32,7 +35,7 @@ export namespace GoogleApiClientUtils { export type Reference = DocumentId | CreateOptions; export interface Content { text: string | string[]; - requests: docs_v1.Schema$Request[]; + requests: docsV1.Schema$Request[]; } export type IdHandler = (id: DocumentId) => any; export type CreationResult = Opt; @@ -81,7 +84,7 @@ export namespace GoogleApiClientUtils { }, }; try { - const schema: docs_v1.Schema$Document = await Networking.PostToServer(path, parameters); + const schema: docsV1.Schema$Document = await Networking.PostToServer(path, parameters); return schema.documentId === null ? undefined : schema.documentId; } catch { return undefined; @@ -90,13 +93,13 @@ export namespace GoogleApiClientUtils { export namespace Utils { export type ExtractResult = { text: string; paragraphs: DeconstructedParagraph[] }; - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => { + export const extractText = (document: docsV1.Schema$Document, removeNewlines = false): ExtractResult => { const paragraphs = extractParagraphs(document); let text = paragraphs .map(paragraph => paragraph.contents .filter(content => !('inlineObjectId' in content)) - .map(run => (run as docs_v1.Schema$TextRun).content) + .map(run => (run as docsV1.Schema$TextRun).content) .join('') ) .join(''); @@ -105,9 +108,9 @@ export namespace GoogleApiClientUtils { return { text, paragraphs }; }; - export type ContentArray = (docs_v1.Schema$TextRun | docs_v1.Schema$InlineObjectElement)[]; + export type ContentArray = (docsV1.Schema$TextRun | docsV1.Schema$InlineObjectElement)[]; export type DeconstructedParagraph = { contents: ContentArray; bullet: Opt }; - const extractParagraphs = (document: docs_v1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => { + const extractParagraphs = (document: docsV1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => { const fragments: DeconstructedParagraph[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { @@ -136,7 +139,7 @@ export namespace GoogleApiClientUtils { return fragments; }; - export const endOf = (schema: docs_v1.Schema$Document): number | undefined => { + export const endOf = (schema: docsV1.Schema$Document): number | undefined => { if (schema.body && schema.body.content) { const paragraphs = schema.body.content.filter(el => el.paragraph); if (paragraphs.length) { @@ -150,6 +153,7 @@ export namespace GoogleApiClientUtils { } } } + return undefined; }; export const initialize = async (reference: Reference) => (typeof reference === 'string' ? reference : create(reference)); @@ -182,26 +186,26 @@ export namespace GoogleApiClientUtils { } }; - export const read = async (options: ReadOptions): Promise> => { - return retrieve({ documentId: options.documentId }).then(document => { + export const read = async (options: ReadOptions): Promise> => + retrieve({ documentId: options.documentId }).then(document => { if (document) { const title = document.title!; const body = Utils.extractText(document, options.removeNewlines).text; return { title, body }; } + return undefined; }); - }; - export const readLines = async (options: ReadOptions): Promise> => { - return retrieve({ documentId: options.documentId }).then(document => { + export const readLines = async (options: ReadOptions): Promise> => + retrieve({ documentId: options.documentId }).then(document => { if (document) { - const title = document.title; + const { title } = document; let bodyLines = Utils.extractText(document).text.split('\n'); options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length)); return { title: title ?? '', bodyLines }; } + return undefined; }); - }; export const setStyle = async (options: UpdateOptions) => { const replies: any = await update({ @@ -216,15 +220,16 @@ export namespace GoogleApiClientUtils { }; export const write = async (options: WriteOptions): Promise => { - const requests: docs_v1.Schema$Request[] = []; + const requests: docsV1.Schema$Request[] = []; const documentId = await Utils.initialize(options.reference); if (!documentId) { return undefined; } - let index = options.index; - const mode = options.mode; + let { index } = options; + const { mode } = options; if (!(index && mode === WriteMode.Insert)) { const schema = await retrieve({ documentId }); + // eslint-disable-next-line no-cond-assign if (!schema || !(index = Utils.endOf(schema))) { return undefined; } @@ -241,7 +246,7 @@ export namespace GoogleApiClientUtils { }); index = 1; } - const text = options.content.text; + const { text } = options.content; text.length && requests.push({ insertText: { diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 4ad03aab4..9808b6a01 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -1,9 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable camelcase */ +/* eslint-disable no-useless-catch */ +/* eslint-disable no-use-before-define */ import * as rp from 'request-promise'; import { Doc, FieldType } from '../../fields/Doc'; import { InkData } from '../../fields/InkField'; import { List } from '../../fields/List'; import { Cast } from '../../fields/Types'; import { UndoManager } from '../util/UndoManager'; +import { ClientUtils } from '../../ClientUtils'; type APIManager = { converter: BodyConverter; requester: RequestExecutor }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise; @@ -40,7 +45,7 @@ export enum Confidence { */ export namespace CognitiveServices { const ExecuteQuery = async (service: Service, manager: APIManager, data: D): Promise => { - let apiKey = process.env[service.toUpperCase()]; + const apiKey = process.env[service.toUpperCase()]; if (!apiKey) { console.log(`No API key found for ${service}: ensure youe root directory has .env file with _CLIENT_${service.toUpperCase()}.`); return undefined; @@ -51,7 +56,6 @@ export namespace CognitiveServices { results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); } catch (e) { throw e; - results = undefined; } return results; }; @@ -70,7 +74,7 @@ export namespace CognitiveServices { parameters = { returnFaceId: 'true', returnFaceLandmarks: 'false', - returnFaceAttributes: 'age,gender,headPose,smile,facialHair,glasses,' + 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise', + returnFaceAttributes: 'age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure,noise', }; break; case Service.ComputerVision: @@ -81,6 +85,7 @@ export namespace CognitiveServices { language: 'en', }; break; + default: } const options = { @@ -109,12 +114,10 @@ export namespace CognitiveServices { const results = await ExecuteQuery(service, Manager, url); if (!results) { toStore = 'Cognitive Services could not process the given image URL.'; + } else if (!results.length) { + toStore = converter(results); } else { - if (!results.length) { - toStore = converter(results); - } else { - toStore = results.length > 0 ? converter(results) : 'Empty list returned.'; - } + toStore = results.length > 0 ? converter(results) : 'Empty list returned.'; } target[storageKey] = toStore; @@ -148,7 +151,7 @@ export namespace CognitiveServices { const endpoint = serverAddress + '/inkrecognizer/v1.0-preview/recognize'; return new Promise((resolve, reject) => { - xhttp.onreadystatechange = function () { + xhttp.onreadystatechange = function (this: XMLHttpRequest) { if (this.readyState === 4) { const result = xhttp.responseText; switch (this.status) { @@ -159,6 +162,7 @@ export namespace CognitiveServices { return reject(result); } } + return undefined; }; xhttp.open('PUT', endpoint, true); @@ -211,15 +215,13 @@ export namespace CognitiveServices { export namespace BingSearch { export const Manager: APIManager = { - converter: (data: string) => { - return data; - }, + converter: (data: string) => data, requester: async (apiKey: string, query: string) => { const xhttp = new XMLHttpRequest(); const serverAddress = 'https://api.cognitive.microsoft.com'; const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query); const promisified = (resolve: any, reject: any) => { - xhttp.onreadystatechange = function () { + xhttp.onreadystatechange = function (this: XMLHttpRequest) { if (this.readyState === 4) { const result = xhttp.responseText; switch (this.status) { @@ -230,6 +232,7 @@ export namespace CognitiveServices { return reject(result); } } + return undefined; }; if (apiKey) { @@ -257,15 +260,13 @@ export namespace CognitiveServices { export namespace HathiTrust { export const Manager: APIManager = { - converter: (data: string) => { - return data; - }, + converter: (data: string) => data, requester: async (apiKey: string, query: string) => { const xhttp = new XMLHttpRequest(); const serverAddress = 'https://babel.hathitrust.org/cgi/htd/​'; const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query); const promisified = (resolve: any, reject: any) => { - xhttp.onreadystatechange = function () { + xhttp.onreadystatechange = function (this: XMLHttpRequest) { if (this.readyState === 4) { const result = xhttp.responseText; switch (this.status) { @@ -276,6 +277,7 @@ export namespace CognitiveServices { return reject(result); } } + return undefined; }; if (apiKey) { @@ -303,8 +305,8 @@ export namespace CognitiveServices { export namespace Text { export const Manager: APIManager = { - converter: (data: string) => { - return JSON.stringify({ + converter: (data: string) => + JSON.stringify({ documents: [ { id: 1, @@ -312,8 +314,7 @@ export namespace CognitiveServices { text: data, }, ], - }); - }, + }), requester: async (apiKey: string, body: string, service: Service) => { const serverAddress = 'https://eastus.api.cognitive.microsoft.com'; const endpoint = serverAddress + '/text/analytics/v2.1/keyPhrases'; @@ -367,11 +368,12 @@ export namespace CognitiveServices { const { keyterms, external_recommendations, kp_string } = await converter(results, data); target[keys[0]] = keyterms; if (isInternal) { - //await vectorize([data], dataDoc, isMainDoc); + // await vectorize([data], dataDoc, isMainDoc); await vectorize(kp_string, dataDoc, isMainDoc); } else { return { recs: external_recommendations, keyterms: keyterms }; } + return undefined; }; // export async function countFrequencies() diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 65dce34a5..eae5b2d6a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -735,19 +735,19 @@ pie title Minerals in my tap water { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, { title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} }, { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, - { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, - { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available - { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected - { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected - { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected - { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, + { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, + { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected + { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected + { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected + { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected ]; } diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 08fd80882..9026b368f 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -63,7 +63,7 @@ export namespace DictationManager { const intraSession = '. '; const interSession = ' ... '; - export let isListening = false; + let isListening = false; let isManuallyStopped = false; let current: string | undefined; @@ -235,7 +235,10 @@ export namespace DictationManager { export type DependentEntry = { expression: RegExp; action: DependentAction; restrictTo?: DocumentType[] }; export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); - export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); + export const RegisterDependent = (entry: DependentEntry) => { + const { expression, action, restrictTo } = entry; + return Dependent.push({ expression, action, restrictTo: restrictTo ?? [] }); + }; export const execute = async (phrase: string) => UndoManager.RunInBatch(async () => { diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index 6dc96f1b5..dd18b2533 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -15,6 +15,7 @@ export namespace Hypothesis { * If none exist, create and return a new WebDocument. */ export const getSourceWebDoc = async (uri: string) => { + // eslint-disable-next-line no-use-before-define const result = await findWebDoc(uri); console.log(result ? 'existing doc found' : 'existing doc NOT found'); return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _height: 512, _width: 400, data_useCors: true }); // create and return a new Web doc with given uri if no matching docs are found @@ -75,17 +76,19 @@ export namespace Hypothesis { /** * Send message to Hypothes.is client to edit an annotation to add a Dash hyperlink */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => { // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done - //!DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc); + //! DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc); - var success = false; + let success = false; const onSuccess = action(() => { console.log('Edit success!!'); success = true; + // eslint-disable-next-line no-use-before-define clearTimeout(interval); - //DocumentLinksButton.invisibleWebDoc = undefined; + // DocumentLinksButton.invisibleWebDoc = undefined; document.removeEventListener('editSuccess', onSuccess); }); @@ -107,7 +110,7 @@ export namespace Hypothesis { action(() => { if (!success) { clearInterval(interval); - //DocumentLinksButton.invisibleWebDoc = undefined; + // DocumentLinksButton.invisibleWebDoc = undefined; } }), 10000 @@ -121,12 +124,13 @@ export namespace Hypothesis { export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => { if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation - //!DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink + //! DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink - var success = false; + let success = false; const onSuccess = action(() => { console.log('Edit success!'); success = true; + // eslint-disable-next-line no-use-before-define clearTimeout(interval); // DocumentLinksButton.invisibleWebDoc = undefined; document.removeEventListener('editSuccess', onSuccess); @@ -149,7 +153,7 @@ export namespace Hypothesis { action(() => { if (!success) { clearInterval(interval); - //DocumentLinksButton.invisibleWebDoc = undefined; + // DocumentLinksButton.invisibleWebDoc = undefined; } }), 10000 @@ -161,10 +165,11 @@ export namespace Hypothesis { * Send message to Hypothes.is client to scroll to an annotation when it loads */ export const scrollToAnnotation = (annotationId: string, target: Doc) => { - var success = false; + let success = false; const onSuccess = () => { console.log('Scroll success!!'); document.removeEventListener('scrollSuccess', onSuccess); + // eslint-disable-next-line no-use-before-define clearInterval(interval); success = true; }; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index f7d7ba6a4..8df579e75 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,9 +1,9 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable import/no-webpack-loader-syntax */ // export const ts = (window as any).ts; // // @ts-ignore // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' -// // @ts-ignore // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' -// @ts-ignore // eslint-disable-next-line node/no-unpublished-import import * as ts from 'typescript'; import * as typescriptlib from '!!raw-loader!./type_decls.d'; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1328e90e9..7e8f42de9 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -75,7 +75,7 @@ export class SelectionManager { } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { +ScriptingGlobals.add(function SelectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { if (Doc.noviceMode && expertMode) return false; if (type === 'tab') { return SelectionManager.Views.lastElement()?._props.renderDepth === 0; diff --git a/src/client/views/newlightbox/ExploreView/utils.ts b/src/client/views/newlightbox/ExploreView/utils.ts index 7d9cf226d..2d1bd75a9 100644 --- a/src/client/views/newlightbox/ExploreView/utils.ts +++ b/src/client/views/newlightbox/ExploreView/utils.ts @@ -1,20 +1,21 @@ -import { IRecommendation } from "../components"; +import { IRecommendation } from '../components'; export interface IExploreView { - recs?: IRecommendation[], - bounds?: IBounds + recs?: IRecommendation[]; + // eslint-disable-next-line no-use-before-define + bounds?: IBounds; } export const emptyBounds = { max_x: 0, max_y: 0, min_x: 0, - min_y: 0 -} + min_y: 0, +}; export interface IBounds { - max_x: number, - max_y: number, - min_x: number, - min_y: number -} \ No newline at end of file + max_x: number; + max_y: number; + min_x: number; + min_y: number; +} diff --git a/src/client/views/newlightbox/components/Recommendation/utils.ts b/src/client/views/newlightbox/components/Recommendation/utils.ts index 796ce0eb0..4a55d394e 100644 --- a/src/client/views/newlightbox/components/Recommendation/utils.ts +++ b/src/client/views/newlightbox/components/Recommendation/utils.ts @@ -1,23 +1,23 @@ -import { DocumentType } from "../../../../documents/DocumentTypes" +import { DocumentType } from '../../../../documents/DocumentTypes'; export interface IRecommendation { - loading?: boolean - type?: DocumentType | string, - data?: string, - title?: string, - text?: string, - source?: string, - previewUrl?: string, + loading?: boolean; + type?: DocumentType | string; + data?: string; + title?: string; + text?: string; + source?: string; + previewUrl?: string; transcript?: { - text: string, - start: number, - duration: number - }[], + text: string; + start: number; + duration: number; + }[]; embedding?: { - x: number, - y: number - }, - distance?: number, - related_concepts?: string[], - docId?: string -} \ No newline at end of file + x: number; + y: number; + }; + distance?: number; + related_concepts?: string[]; + docId?: string; +} diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts index cf5315da3..3a2679ecf 100644 --- a/src/client/views/nodes/MapBox/AnimationUtility.ts +++ b/src/client/views/nodes/MapBox/AnimationUtility.ts @@ -1,6 +1,7 @@ import * as turf from '@turf/turf'; import { Position } from '@turf/turf'; import * as d3 from 'd3'; +// eslint-disable-next-line import/no-extraneous-dependencies import { Feature, GeoJsonProperties, Geometry } from 'geojson'; import mapboxgl, { MercatorCoordinate } from 'mapbox-gl'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; @@ -214,6 +215,7 @@ export class AnimationUtility { updateAnimationPhase: (newAnimationPhase: number) => void; updateFrameId: (newFrameId: number) => void; }) => + // eslint-disable-next-line no-async-promise-executor new Promise(async resolve => { let startTime: number | null = null; @@ -288,6 +290,7 @@ export class AnimationUtility { }); public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => + // eslint-disable-next-line no-async-promise-executor new Promise<{ bearing: number; altitude: number }>(async resolve => { let start: number | null; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 7009977d0..6495cf440 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,11 +1,14 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, FormControlLabel, TextField } from '@mui/material'; import * as turf from '@turf/turf'; import { IconButton, Size, Type } from 'browndash-components'; import * as d3 from 'd3'; +// eslint-disable-next-line import/no-extraneous-dependencies import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson'; -import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl'; +import mapboxgl, { LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl'; import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -21,7 +24,6 @@ import { DocUtils, Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { LinkManager } from '../../../util/LinkManager'; -import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { SidebarAnnos } from '../../SidebarAnnos'; @@ -54,8 +56,6 @@ import { MarkerIcons } from './MarkerIcons'; */ const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ'; -const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; -const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; type PopupInfo = { longitude: number; @@ -287,7 +287,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem setupMoveUpEvents( this, e, - (e, down, delta) => + (moveEv, down, delta) => runInAction(() => { const localDelta = this._props .ScreenToLocalTransform() @@ -362,10 +362,10 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const docView = this.DocumentView?.(); docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { - e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document; - e.annoDragData.linkSourceDoc.followLinkZoom = false; + dragComplete: dragEv => { + if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) { + dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document; + dragEv.annoDragData.linkSourceDoc.followLinkZoom = false; } }, }); @@ -437,7 +437,9 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem this.toggleSidebar(); options.didMove = true; } - return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + return new Promise>(res => { + DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + }); }; /* @@ -528,6 +530,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu); }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars recolorPin = (pin: Doc, color?: string) => { // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin)); // this.map_docToPinMap.delete(pin); @@ -582,6 +585,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem MapAnchorMenu.Instance.fadeOut(true); return mapRoute; } + return undefined; // TODO: Display error that can't create route to same location }, 'createmaproute'); @@ -654,9 +658,8 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem console.error(features); if (features && features.length > 0 && features[0].properties && features[0].geometry) { - const geometry = features[0].geometry as LineString; const { routeTitle } = features[0].properties; - const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle); + const routeDoc: Doc | undefined = this.allRoutes.find(rtDoc => rtDoc.title === routeTitle); this.deselectPinOrRoute(); // TODO: Also deselect route if selected if (routeDoc) { this._selectedPinOrRoute = routeDoc; @@ -700,7 +703,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem */ handleMapDblClick = async (e: MapLayerMouseEvent) => { e.preventDefault(); - const { lngLat }: LngLat = e; + const { lngLat } = e; const longitude: number = lngLat.lng; const latitude: number = lngLat.lat; @@ -836,6 +839,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem if (!this._isAnimating) { return this.mapboxMapViewState; } + return undefined; } @action @@ -919,69 +923,73 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem this.path = path; this._isAnimating = true; - runInAction(() => { - return new Promise(async resolve => { - const targetLngLat = { - lng: this.selectedRouteCoordinates[0][0], - lat: this.selectedRouteCoordinates[0][1], - }; - - const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current); - runInAction(() => this.setAnimationUtility(animationUtil)); + runInAction( + () => + // eslint-disable-next-line no-async-promise-executor + new Promise(async resolve => { + const targetLngLat = { + lng: this.selectedRouteCoordinates[0][0], + lat: this.selectedRouteCoordinates[0][1], + }; + + const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current); + runInAction(() => this.setAnimationUtility(animationUtil)); + + const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId); + const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase); + + if (status !== AnimationStatus.RESUME) { + const result = await animationUtil.flyInAndRotate({ + map: this._mapRef.current!, + // targetLngLat, + // duration 3000 + // startAltitude: 3000000, + // endAltitude: this.isStreetViewAnimation ? 80 : 12000, + // startBearing: 0, + // endBearing: -20, + // startPitch: 40, + // endPitch: this.isStreetViewAnimation ? 80 : 50, + updateFrameId, + }); + + console.log('Bearing: ', result.bearing); + console.log('Altitude: ', result.altitude); + } - const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId); - const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase); + runInAction(() => { + this._finishedFlyTo = true; + }); - if (status !== AnimationStatus.RESUME) { - const result = await animationUtil.flyInAndRotate({ + // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation + await animationUtil.animatePath({ map: this._mapRef.current!, - // targetLngLat, - // duration 3000 - // startAltitude: 3000000, - // endAltitude: this.isStreetViewAnimation ? 80 : 12000, - // startBearing: 0, - // endBearing: -20, - // startPitch: 40, - // endPitch: this.isStreetViewAnimation ? 80 : 50, + // path: this.path, + // startBearing: -20, + // startAltitude: this.isStreetViewAnimation ? 80 : 12000, + // pitch: this.isStreetViewAnimation ? 80: 50, + currentAnimationPhase: this._animationPhase, + updateAnimationPhase, updateFrameId, }); - console.log('Bearing: ', result.bearing); - console.log('Altitude: ', result.altitude); - } + // get the bounds of the linestring, use fitBounds() to animate to a final view + const bbox3d = turf.bbox(this.path); - runInAction(() => (this._finishedFlyTo = true)); - - // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation - await animationUtil.animatePath({ - map: this._mapRef.current!, - // path: this.path, - // startBearing: -20, - // startAltitude: this.isStreetViewAnimation ? 80 : 12000, - // pitch: this.isStreetViewAnimation ? 80: 50, - currentAnimationPhase: this._animationPhase, - updateAnimationPhase, - updateFrameId, - }); + const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]]; - // get the bounds of the linestring, use fitBounds() to animate to a final view - const bbox3d = turf.bbox(this.path); - - const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]]; - - this._mapRef.current!.fitBounds(bbox2d, { - duration: 3000, - pitch: 30, - bearing: 0, - padding: 120, - }); + this._mapRef.current!.fitBounds(bbox2d, { + duration: 3000, + pitch: 30, + bearing: 0, + padding: 120, + }); - setTimeout(() => { - this._isStreetViewAnimation = false; - resolve(); - }, 10000); - }); - }); + setTimeout(() => { + this._isStreetViewAnimation = false; + resolve(); + }, 10000); + }) + ); }; @action @@ -1010,53 +1018,49 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem } }; - getRouteAnimationOptions = (): JSX.Element => { - return ( - <> + getRouteAnimationOptions = (): JSX.Element => ( + <> + { + if (this._isAnimating && this._finishedFlyTo) { + this.pauseAnimation(); + } else if (this._animationPhase > 0) { + this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase + } else { + this.playAnimation(AnimationStatus.START); // Play from the beginning + } + }} + icon={this._isAnimating && this._finishedFlyTo ? : } + color="black" + size={Size.MEDIUM} + /> + {this._isAnimating && this._finishedFlyTo && ( { - if (this._isAnimating && this._finishedFlyTo) { - this.pauseAnimation(); - } else if (this._animationPhase > 0) { - this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase - } else { - this.playAnimation(AnimationStatus.START); // Play from the beginning - } + this.stopAnimation(false); + this.playAnimation(AnimationStatus.START); }} - icon={this._isAnimating && this._finishedFlyTo ? : } + icon={} color="black" size={Size.MEDIUM} /> - {this._isAnimating && this._finishedFlyTo && ( - { - this.stopAnimation(false); - this.playAnimation(AnimationStatus.START); - }} - icon={} - color="black" - size={Size.MEDIUM} - /> - )} - this.stopAnimation(true)} icon={} color="black" size={Size.MEDIUM} /> - <> -
-
|
- } /> -
|
- -
|
-
-
Select Line Color:
- this.setAnimationLineColor(color)} /> -
-
- - - ); - }; + )} + this.stopAnimation(true)} icon={} color="black" size={Size.MEDIUM} /> +
+
|
+ } /> +
|
+ +
|
+
+
Select Line Color:
+ this.setAnimationLineColor(color)} /> +
+
+ + ); @action hideRoute = () => { @@ -1068,7 +1072,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action toggleSettings = () => { - if (!this._isAnimating && this._animationPhase == 0) { + if (!this._isAnimating && this._animationPhase === 0) { this._featuresFromGeocodeResults = []; this._settingsOpen = !this._settingsOpen; } @@ -1125,7 +1129,9 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }; @action - onMapZoom = (e: ViewStateChangeEvent) => (this.dataDoc.map_zoom = e.viewState.zoom); + onMapZoom = (e: ViewStateChangeEvent) => { + this.dataDoc.map_zoom = e.viewState.zoom; + }; @action onMapMove = (e: ViewStateChangeEvent) => { @@ -1134,7 +1140,9 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }; @action - toggleShowTerrain = () => (this._showTerrain = !this._showTerrain); + toggleShowTerrain = () => { + this._showTerrain = !this._showTerrain; + }; getMarkerIcon = (pinDoc: Doc): JSX.Element | null => { const markerType = StrCast(pinDoc.markerType); @@ -1148,7 +1156,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const scale = this._props.NativeDimScaling?.() || 1; const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1; - const renderAnnotations = (childFilters?: () => string[]) => null; return (
() implem onWheel={e => e.stopPropagation()} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> -
{renderAnnotations(this.transparentFilter)}
- {renderAnnotations(this.opaqueFilter)} - {SnappingManager.IsDragging ? null : renderAnnotations()} {!this._routeToAnimate && (
this.handleSearchChange(e.target.value)} /> - } type={Type.TERT} onClick={e => this.toggleSettings()} /> + } type={Type.TERT} onClick={() => this.toggleSettings()} />
- } type={Type.TERT} onClick={e => this.toggleSettings()} /> + } type={Type.TERT} onClick={() => this.toggleSettings()} />
)} @@ -1212,22 +1216,21 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem )} {this._featuresFromGeocodeResults.length > 0 && (
- <> -

Choose a location for your pin:

- {this._featuresFromGeocodeResults - .filter(feature => feature.place_name) - .map((feature, idx) => ( -
{ - this.handleSearchChange(''); - this.addMarkerForFeature(feature); - }}> -
{feature.place_name}
-
- ))} - +

Choose a location for your pin:

+ {this._featuresFromGeocodeResults + .filter(feature => feature.place_name) + .map((feature, idx) => ( +
{ + this.handleSearchChange(''); + this.addMarkerForFeature(feature); + }}> +
{feature.place_name}
+
+ ))}
)} @@ -1254,7 +1257,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem - {!this._isAnimating && this._animationPhase == 0 && ( + {!this._isAnimating && this._animationPhase === 0 && ( )} {this._routeToAnimate && (this._isAnimating || this._animationPhase > 0) && ( @@ -1318,16 +1321,15 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem )} - <> - {!this._isAnimating && - this._animationPhase == 0 && - this.allPushpins // .filter(anno => !anno.layout_unrendered) - .map((pushpin, idx) => ( - ) => this.handleMarkerClick(e, pushpin)}> - {this.getMarkerIcon(pushpin)} - - ))} - + {!this._isAnimating && + this._animationPhase === 0 && + this.allPushpins // .filter(anno => !anno.layout_unrendered) + .map((pushpin, idx) => ( + // eslint-disable-next-line react/no-array-index-key + ) => this.handleMarkerClick(e, pushpin)}> + {this.getMarkerIcon(pushpin)} + + ))} {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => ( @@ -1338,12 +1340,13 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem
{ - return new RichTextField(ToProsemirrorState(plainText, oldState), plainText); - }; + export const Synthesize = (plainText: string, oldState?: RichTextField) => new RichTextField(ToProsemirrorState(plainText, oldState), plainText); export const ToPlainText = (state: EditorState) => { // Because we're working with plain text, just concatenate all paragraphs - const content = state.doc.content; + const { content } = state.doc; const paragraphs: Node[] = []; content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node)); @@ -113,9 +113,9 @@ export namespace RichTextUtils { agnostic: string; } - const parseInlineObjects = async (document: docs_v1.Schema$Document): Promise> => { + const parseInlineObjects = async (document: docsV1.Schema$Document): Promise> => { const inlineObjectMap = new Map(); - const inlineObjects = document.inlineObjects; + const { inlineObjects } = document; if (inlineObjects) { const objects = Object.keys(inlineObjects).map(objectId => inlineObjects[objectId]); @@ -171,10 +171,12 @@ export namespace RichTextUtils { const indentMap = new Map(); let globalOffset = 0; const nodes: Node[] = []; + // eslint-disable-next-line no-restricted-syntax for (const element of structured) { if (Array.isArray(element)) { lists.push(element); const positions: BulletPosition[] = []; + // eslint-disable-next-line no-loop-func const items = element.map(paragraph => { const item = listItem(state.schema, paragraph.contents); const sinks = paragraph.bullet!; @@ -188,41 +190,42 @@ export namespace RichTextUtils { }); indentMap.set(element, positions); nodes.push(list(state.schema, items)); + } else if (element.contents.some(child => 'inlineObjectId' in child)) { + const group = element.contents; + // eslint-disable-next-line no-loop-func + group.forEach((child, i) => { + let node: Opt; + if ('inlineObjectId' in child) { + node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); + } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { + node = paragraphNode(state.schema, [child]); + } + if (node) { + position += node.nodeSize; + nodes.push(node); + } + }); } else { - if (element.contents.some(child => 'inlineObjectId' in child)) { - const group = element.contents; - group.forEach((child, i) => { - let node: Opt; - if ('inlineObjectId' in child) { - node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); - } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { - node = paragraphNode(state.schema, [child]); - } - if (node) { - position += node.nodeSize; - nodes.push(node); - } - }); - } else { - const paragraph = paragraphNode(state.schema, element.contents); - nodes.push(paragraph); - position += paragraph.nodeSize; - } + const paragraph = paragraphNode(state.schema, element.contents); + nodes.push(paragraph); + position += paragraph.nodeSize; } } state = state.apply(state.tr.replaceWith(0, 2, nodes)); const sink = sinkListItem(state.schema.nodes.list_item); - const dispatcher = (tr: Transaction) => (state = state.apply(tr)); - for (const list of lists) { - for (const pos of indentMap.get(list)!) { + const dispatcher = (tr: Transaction) => { + state = state.apply(tr); + }; + lists.forEach(list => { + indentMap.get(list)?.forEach(pos => { const resolved = state.doc.resolve(pos.value); state = state.apply(state.tr.setSelection(new TextSelection(resolved))); for (let i = 0; i < pos.sinks; i++) { sink(state, dispatcher); } - } - } + }); + }); return { title, text, state }; }; @@ -234,7 +237,7 @@ export namespace RichTextUtils { const parseLists = (paragraphs: ListGroup) => { const groups: PreparedParagraphs = []; let group: ListGroup = []; - for (const paragraph of paragraphs) { + paragraphs.forEach(paragraph => { if (paragraph.bullet !== undefined) { group.push(paragraph); } else { @@ -244,26 +247,22 @@ export namespace RichTextUtils { } groups.push(paragraph); } - } + }); group.length && groups.push(group); return groups; }; - const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { - return schema.node('list_item', null, paragraphNode(schema, runs)); - }; + const listItem = (lschema: any, runs: docsV1.Schema$TextRun[]): Node => lschema.node('list_item', null, paragraphNode(lschema, runs)); - const list = (schema: any, items: Node[]): Node => { - return schema.node('ordered_list', { mapStyle: 'bullet' }, items); - }; + const list = (lschema: any, items: Node[]): Node => lschema.node('ordered_list', { mapStyle: 'bullet' }, items); - const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { - const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined); + const paragraphNode = (lschema: any, runs: docsV1.Schema$TextRun[]): Node => { + const children = runs.map(run => textNode(lschema, run)).filter(child => child !== undefined); const fragment = children.length ? Fragment.from(children) : undefined; - return schema.node('paragraph', null, fragment); + return lschema.node('paragraph', null, fragment); }; - const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => { + const imageNode = (lschema: any, image: ImageTemplate, textNote: Doc) => { const { url: src, width, agnostic } = image; let docId: string; const guid = Utils.GenerateDeterministicGuid(agnostic); @@ -276,30 +275,30 @@ export namespace RichTextUtils { } else { docId = backingDocId; } - return schema.node('image', { src, agnostic, width, docId, float: null }); + return lschema.node('image', { src, agnostic, width, docId, float: null }); }; - const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { + const textNode = (lschema: any, run: docsV1.Schema$TextRun) => { const text = run.content!.removeTrailingNewlines(); - return text.length ? schema.text(text, styleToMarks(schema, run.textStyle)) : undefined; + return text.length ? lschema.text(text, styleToMarks(lschema, run.textStyle)) : undefined; }; - const StyleToMark = new Map([ + const StyleToMark = new Map([ ['bold', 'strong'], ['italic', 'em'], ['foregroundColor', 'pFontColor'], ['fontSize', 'pFontSize'], ]); - const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { + const styleToMarks = (lschema: any, textStyle?: docsV1.Schema$TextStyle) => { if (!textStyle) { return undefined; } const marks: Mark[] = []; Object.keys(textStyle).forEach(key => { - let value: any; - const targeted = key as keyof docs_v1.Schema$TextStyle; - if ((value = textStyle[targeted])) { + const targeted = key as keyof docsV1.Schema$TextStyle; + const value = textStyle[targeted] as any; + if (value) { const attributes: any = {}; let converted = StyleToMark.get(targeted) || targeted; @@ -316,20 +315,20 @@ export namespace RichTextUtils { converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman'; } - const mapped = schema.marks[converted]; + const mapped = lschema.marks[converted]; if (!mapped) { alert(`No mapping found for ${converted}!`); return; } - const mark = schema.mark(mapped, attributes); + const mark = lschema.mark(mapped, attributes); mark && marks.push(mark); } }); return marks; }; - const MarkToStyle = new Map([ + const MarkToStyle = new Map([ ['strong', 'bold'], ['em', 'italic'], ['pFontColor', 'foregroundColor'], @@ -361,16 +360,18 @@ export namespace RichTextUtils { const ignored = ['user_mark']; - const marksToStyle = async (nodes: (Node | null)[]): Promise => { - const requests: docs_v1.Schema$Request[] = []; + const marksToStyle = async (nodes: (Node | null)[]): Promise => { + const requests: docsV1.Schema$Request[] = []; let position = 1; + // eslint-disable-next-line no-restricted-syntax for (const node of nodes) { if (node === null) { position += 2; + // eslint-disable-next-line no-continue continue; } const { marks, attrs, nodeSize } = node; - const textStyle: docs_v1.Schema$TextStyle = {}; + const textStyle: docsV1.Schema$TextStyle = {}; const information: LinkInformation = { startIndex: position, endIndex: position + nodeSize, @@ -378,36 +379,45 @@ export namespace RichTextUtils { }; let mark: Mark; const markMap = BuildMarkMap(marks); + // eslint-disable-next-line no-restricted-syntax for (const markName of Object.keys(schema.marks)) { + // eslint-disable-next-line no-cond-assign if (ignored.includes(markName) || !(mark = markMap[markName])) { + // eslint-disable-next-line no-continue continue; } - let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle); + let converted = MarkToStyle.get(markName) || (markName as keyof docsV1.Schema$TextStyle); let value: any = true; if (!converted) { + // eslint-disable-next-line no-continue continue; } + // eslint-disable-next-line @typescript-eslint/no-shadow const { attrs } = mark; switch (converted) { case 'link': - let url = attrs.allLinks.length ? attrs.allLinks[0].href : ''; - const delimiter = '/doc/'; - const alreadyShared = '?sharing=true'; - if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { - const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); - if (linkDoc instanceof Doc) { - let exported = (await Cast(linkDoc.link_anchor_2, Doc))!; - if (!exported.customLayout) { - exported = Doc.MakeEmbedding(exported); - DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); - linkDoc.link_anchor_2 = exported; + { + let url = attrs.allLinks.length ? attrs.allLinks[0].href : ''; + const docDelimeter = '/doc/'; + const alreadyShared = '?sharing=true'; + if (new RegExp(window.location.origin + docDelimeter).test(url) && !url.endsWith(alreadyShared)) { + // eslint-disable-next-line no-await-in-loop + const linkDoc = await DocServer.GetRefField(url.split(docDelimeter)[1]); + if (linkDoc instanceof Doc) { + // eslint-disable-next-line no-await-in-loop + let exported = (await Cast(linkDoc.link_anchor_2, Doc))!; + if (!exported.customLayout) { + exported = Doc.MakeEmbedding(exported); + DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); + linkDoc.link_anchor_2 = exported; + } + url = ClientUtils.shareUrl(exported[Id]); } - url = ClientUtils.shareUrl(exported[Id]); } + value = { url }; + textStyle.foregroundColor = fromRgb.blue; + textStyle.bold = true; } - value = { url }; - textStyle.foregroundColor = fromRgb.blue; - textStyle.bold = true; break; case 'fontSize': value = { magnitude: attrs.fontSize, unit: 'PT' }; @@ -417,9 +427,11 @@ export namespace RichTextUtils { break; case 'weightedFontFamily': value = { fontFamily: ExportFontFamilyMapping.get(markName) }; + break; + default: } - let matches: RegExpExecArray | null; - if ((matches = /p(\d+)/g.exec(markName)) !== null) { + const matches = /p(\d+)/g.exec(markName); + if (matches !== null) { converted = 'fontSize'; value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' }; } @@ -429,7 +441,7 @@ export namespace RichTextUtils { requests.push(EncodeStyleUpdate(information)); } if (node.type.name === 'image') { - const width = attrs.width; + const { width } = attrs; requests.push( await EncodeImage({ startIndex: position + nodeSize - 1, @@ -445,14 +457,16 @@ export namespace RichTextUtils { const BuildMarkMap = (marks: readonly Mark[]) => { const markMap: { [type: string]: Mark } = {}; - marks.forEach(mark => (markMap[mark.type.name] = mark)); + marks.forEach(mark => { + markMap[mark.type.name] = mark; + }); return markMap; }; interface LinkInformation { startIndex: number; endIndex: number; - textStyle: docs_v1.Schema$TextStyle; + textStyle: docsV1.Schema$TextStyle; } interface ImageInformation { @@ -462,36 +476,34 @@ export namespace RichTextUtils { } namespace fromRgb { - export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { - return { - color: { - rgbColor: { - red: red / 255, - green: green / 255, - blue: blue / 255, - }, + export const convert = (red: number, green: number, blue: number): docsV1.Schema$OptionalColor => ({ + color: { + rgbColor: { + red: red / 255, + green: green / 255, + blue: blue / 255, }, - }; - }; + }, + }); export const red = convert(255, 0, 0); export const green = convert(0, 255, 0); export const blue = convert(0, 0, 255); } - const fromHex = (color: string): docs_v1.Schema$OptionalColor => { + const fromHex = (color: string): docsV1.Schema$OptionalColor => { const c = DashColor(color); return fromRgb.convert(c.red(), c.green(), c.blue()); }; - const EncodeStyleUpdate = (information: LinkInformation): docs_v1.Schema$Request => { + const EncodeStyleUpdate = (information: LinkInformation): docsV1.Schema$Request => { const { startIndex, endIndex, textStyle } = information; return { updateTextStyle: { fields: '*', range: { startIndex, endIndex }, textStyle, - } as docs_v1.Schema$UpdateTextStyleRequest, + } as docsV1.Schema$UpdateTextStyleRequest, }; }; -- cgit v1.2.3-70-g09d2 From 098deaa68c8b9bb781748fbe0c1bd0104bab3596 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 30 Apr 2024 23:35:18 -0400 Subject: unwinding more import loops by splitting up Documents.ts into DocUtils.ts and moving crate functions to <>Box functions --- .eslintrc.json | 2 +- .../apis/google_docs/GooglePhotosClientUtils.ts | 3 +- src/client/documents/DocFromField.ts | 50 + src/client/documents/DocUtils.ts | 883 ++++++++++++++ src/client/documents/Documents.ts | 1270 +------------------- src/client/util/CurrentUserUtils.ts | 8 +- src/client/util/DictationManager.ts | 5 +- src/client/util/DocumentManager.ts | 5 +- src/client/util/DragManager.ts | 21 +- src/client/util/GroupManager.tsx | 6 +- src/client/util/GroupMemberView.tsx | 4 +- src/client/util/LinkFollower.ts | 16 +- src/client/util/RTFMarkup.tsx | 22 +- src/client/util/ReplayMovements.ts | 2 +- src/client/util/SettingsManager.tsx | 17 +- src/client/util/SharingManager.tsx | 19 +- src/client/views/DashboardView.tsx | 47 +- src/client/views/DocComponent.tsx | 7 +- src/client/views/DocumentButtonBar.tsx | 7 +- src/client/views/DocumentDecorations.tsx | 5 +- src/client/views/GestureOverlay.tsx | 43 +- src/client/views/GlobalKeyHandler.ts | 11 +- src/client/views/InkStrokeProperties.ts | 3 +- src/client/views/InkingStroke.tsx | 79 +- src/client/views/LightboxView.tsx | 10 +- src/client/views/MainView.tsx | 10 +- src/client/views/MarqueeAnnotator.tsx | 8 +- src/client/views/PreviewCursor.tsx | 3 +- src/client/views/PropertiesButtons.tsx | 5 +- .../views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesDocContextSelector.tsx | 3 +- src/client/views/PropertiesView.tsx | 5 +- src/client/views/SidebarAnnos.tsx | 5 +- src/client/views/TemplateMenu.tsx | 3 +- .../views/collections/CollectionCarousel3DView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 26 +- .../collections/CollectionMasonryViewFieldRow.tsx | 2 +- .../views/collections/CollectionNoteTakingView.tsx | 12 +- .../collections/CollectionNoteTakingViewColumn.tsx | 12 +- .../views/collections/CollectionPileView.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 12 +- .../views/collections/CollectionStackingView.tsx | 11 +- .../CollectionStackingViewFieldColumn.tsx | 10 +- src/client/views/collections/CollectionSubView.tsx | 12 +- .../views/collections/CollectionTreeView.tsx | 5 +- src/client/views/collections/CollectionView.tsx | 23 +- src/client/views/collections/TabDocView.tsx | 5 +- src/client/views/collections/TreeView.tsx | 8 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 17 +- .../collections/collectionFreeForm/MarqueeView.tsx | 9 +- .../collectionGrid/CollectionGridView.tsx | 5 +- .../collectionSchema/CollectionSchemaView.tsx | 6 +- .../collections/collectionSchema/SchemaRowBox.tsx | 4 +- src/client/views/global/globalScripts.ts | 6 +- src/client/views/linking/LinkMenuItem.tsx | 3 +- .../views/newlightbox/ButtonMenu/ButtonMenu.tsx | 2 +- src/client/views/newlightbox/NewLightboxView.tsx | 10 +- src/client/views/nodes/AudioBox.tsx | 11 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 6 +- src/client/views/nodes/ComparisonBox.tsx | 21 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 27 +- src/client/views/nodes/DocumentLinksButton.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 39 +- src/client/views/nodes/EquationBox.tsx | 18 +- src/client/views/nodes/FieldView.tsx | 27 +- src/client/views/nodes/FocusViewOptions.ts | 24 + src/client/views/nodes/FontIconBox/FontIconBox.tsx | 17 +- src/client/views/nodes/FunctionPlotBox.tsx | 11 +- src/client/views/nodes/ImageBox.tsx | 13 +- src/client/views/nodes/KeyValueBox.tsx | 10 +- src/client/views/nodes/KeyValuePair.tsx | 3 +- src/client/views/nodes/LabelBox.tsx | 12 +- src/client/views/nodes/LinkBox.tsx | 18 + src/client/views/nodes/LinkDocPreview.tsx | 3 +- src/client/views/nodes/LoadingBox.tsx | 6 + src/client/views/nodes/MapBox/MapBox.tsx | 16 +- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 2 +- src/client/views/nodes/MapBox/MapPushpinBox.tsx | 7 + .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 11 +- src/client/views/nodes/OpenWhere.ts | 25 + src/client/views/nodes/PDFBox.tsx | 14 +- .../nodes/PhysicsBox/PhysicsSimulationBox.tsx | 383 +++--- .../views/nodes/RecordingBox/RecordingBox.tsx | 5 + src/client/views/nodes/ScreenshotBox.tsx | 10 +- src/client/views/nodes/ScriptingBox.tsx | 7 + src/client/views/nodes/VideoBox.tsx | 17 +- src/client/views/nodes/WebBox.tsx | 15 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 6 + .../views/nodes/formattedText/DashDocView.tsx | 5 +- .../views/nodes/formattedText/DashFieldView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 56 +- .../formattedText/ProsemirrorExampleTransfer.ts | 4 +- .../views/nodes/formattedText/RichTextRules.ts | 3 +- .../views/nodes/generativeFill/GenerativeFill.tsx | 5 +- src/client/views/nodes/trails/PresBox.tsx | 15 +- src/client/views/nodes/trails/PresElementBox.tsx | 7 +- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 3 +- src/client/views/pdf/PDFViewer.tsx | 11 +- src/client/views/search/SearchBox.tsx | 12 +- src/client/views/selectedDoc/SelectedDocView.tsx | 2 +- src/fields/Doc.ts | 76 ++ src/fields/RichTextUtils.ts | 3 +- src/fields/ScriptField.ts | 4 + src/mobile/MobileInterface.tsx | 3 +- 105 files changed, 1900 insertions(+), 1896 deletions(-) create mode 100644 src/client/documents/DocFromField.ts create mode 100644 src/client/documents/DocUtils.ts create mode 100644 src/client/views/nodes/FocusViewOptions.ts create mode 100644 src/client/views/nodes/OpenWhere.ts (limited to 'src/fields/RichTextUtils.ts') diff --git a/.eslintrc.json b/.eslintrc.json index 6d851a64a..2e4da56b8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -57,7 +57,7 @@ "react/destructuring-assignment": 0, "no-restricted-globals": ["error", "event"], "no-param-reassign": ["error", { "props": false }], - "import/no-cycle": 0, + // "import/no-cycle": 0, "no-alert": 0, "radix": "off" }, diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 1eb977813..fdc185a8e 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -10,7 +10,8 @@ import { RichTextUtils } from '../../../fields/RichTextUtils'; import { Cast, ImageCast, StrCast } from '../../../fields/Types'; import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes'; import { Networking } from '../../Network'; -import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents'; +import { Docs, DocumentOptions } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox'; import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager'; diff --git a/src/client/documents/DocFromField.ts b/src/client/documents/DocFromField.ts new file mode 100644 index 000000000..1c0a9755b --- /dev/null +++ b/src/client/documents/DocFromField.ts @@ -0,0 +1,50 @@ +/* eslint-disable prefer-destructuring */ +/* eslint-disable default-param-last */ +/* eslint-disable no-use-before-define */ +import { Doc, DocListCast } from '../../fields/Doc'; +import { InkField } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { AudioField, ImageField, PdfField, VideoField } from '../../fields/URLField'; +import { InkingStroke } from '../views/InkingStroke'; +import { CollectionView } from '../views/collections/CollectionView'; +import { AudioBox } from '../views/nodes/AudioBox'; +import { ImageBox } from '../views/nodes/ImageBox'; +import { PDFBox } from '../views/nodes/PDFBox'; +import { VideoBox } from '../views/nodes/VideoBox'; +import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; +import { Docs, DocumentOptions } from './Documents'; + +export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { + let created: Doc | undefined; + const field = target[fieldKey]; + const resolved = options ?? {}; + if (field instanceof ImageField) { + created = Docs.Create.ImageDocument(field.url.href, resolved); + created.layout = ImageBox.LayoutString(fieldKey); + } else if (field instanceof Doc) { + created = field; + } else if (field instanceof VideoField) { + created = Docs.Create.VideoDocument(field.url.href, resolved); + created.layout = VideoBox.LayoutString(fieldKey); + } else if (field instanceof PdfField) { + created = Docs.Create.PdfDocument(field.url.href, resolved); + created.layout = PDFBox.LayoutString(fieldKey); + } else if (field instanceof AudioField) { + created = Docs.Create.AudioDocument(field.url.href, resolved); + created.layout = AudioBox.LayoutString(fieldKey); + } else if (field instanceof InkField) { + created = Docs.Create.InkDocument(field.inkData, resolved); + created.layout = InkingStroke.LayoutString(fieldKey); + } else if (field instanceof List && field[0] instanceof Doc) { + created = Docs.Create.StackingDocument(DocListCast(field), resolved); + created.layout = CollectionView.LayoutString(fieldKey); + } else { + created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved }); + created.layout = FormattedTextBox.LayoutString(fieldKey); + } + if (created) { + created.title = fieldKey; + proto && created.proto && (created.proto = Doc.GetProto(proto)); + } + return created; +} diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts new file mode 100644 index 000000000..e93bbc41b --- /dev/null +++ b/src/client/documents/DocUtils.ts @@ -0,0 +1,883 @@ +/* eslint-disable prefer-destructuring */ +/* eslint-disable default-param-last */ +/* eslint-disable no-use-before-define */ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { saveAs } from 'file-saver'; +import * as JSZip from 'jszip'; +import { action, runInAction } from 'mobx'; +import { ClientUtils } from '../../ClientUtils'; +import * as JSZipUtils from '../../JSZipUtils'; +import { decycle } from '../../decycler/decycler'; +import { DateField } from '../../fields/DateField'; +import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, SetActiveAudioLinker, StrListCast } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; +import { Id } from '../../fields/FieldSymbols'; +import { InkDataFieldName, InkField } from '../../fields/InkField'; +import { List, ListFieldName } from '../../fields/List'; +import { ProxyField } from '../../fields/Proxy'; +import { RichTextField } from '../../fields/RichTextField'; +import { ComputedField, FollowLinkScript, ScriptField } from '../../fields/ScriptField'; +import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField'; +import { SharingPermissions } from '../../fields/util'; +import { Upload } from '../../server/SharedMediaTypes'; +import { DocServer } from '../DocServer'; +import { Networking } from '../Network'; +import { LinkManager } from '../util/LinkManager'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { SerializationHelper } from '../util/SerializationHelper'; +import { UndoManager, undoable } from '../util/UndoManager'; +import { ContextMenu } from '../views/ContextMenu'; +import { ContextMenuProps } from '../views/ContextMenuItem'; +import { FieldViewProps } from '../views/nodes/FieldView'; +import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; +import { LoadingBox } from '../views/nodes/LoadingBox'; +import { OpenWhere } from '../views/nodes/OpenWhere'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import { DocumentType } from './DocumentTypes'; +import { Docs, DocumentOptions } from './Documents'; + +const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore + +const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); + +export namespace DocUtils { + function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean { + let value = valueIn; + const hasFunctionFilter = ClientUtils.HasFunctionFilter(value); + if (hasFunctionFilter) { + return hasFunctionFilter(StrCast(doc[key])); + } + if (key === LinkedTo) { + // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) + const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); + const matchLink = (val: string, anchor: Doc) => { + const linkedToExp = (val ?? '').split('='); + if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val; + return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; + }; + // prettier-ignore + return (value === Doc.FilterNone && !allLinks.length) || + (value === Doc.FilterAny && !!allLinks.length) || + (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || + matchLink(value,DocCast(link.link_anchor_2)) )); + } + if (typeof value === 'string') { + value = value.replace(`,${ClientUtils.noRecursionHack}`, ''); + } + const fieldVal = doc[key]; + // prettier-ignore + if ((value === Doc.FilterAny && fieldVal !== undefined) || + (value === Doc.FilterNone && fieldVal === undefined)) { + return true; + } + const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings + if (vals.length) { + return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + } + return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + } + /** + * @param docs + * @param childFilters + * @param childFiltersByRanges + * @param parentCollection + * Given a list of docs and childFilters, @returns the list of Docs that match those filters + */ + export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) { + if (!childFilters?.length && !childFiltersByRanges?.length) { + return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one + } + + const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields + childFilters.forEach(filter => { + const fields = filter.split(Doc.FilterSep); + const key = fields[0]; + const value = fields[1]; + const modifiers = fields[2]; + if (!filterFacets[key]) { + filterFacets[key] = {}; + } + filterFacets[key][value] = modifiers; + }); + + const filteredDocs = childFilters.length + ? childDocs.filter(d => { + if (d.z) return true; + // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter + if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { + return false; + } + const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]); + // eslint-disable-next-line no-restricted-syntax + for (const facetKey of facetKeys) { + const facet = filterFacets[facetKey]; + + // facets that match some value in the field of the document (e.g. some text field) + const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); + + // facets that have a check next to them + const checks = Object.keys(facet).filter(value => facet[value] === 'check'); + + // metadata facets that exist + const exists = Object.keys(facet).filter(value => facet[value] === 'exists'); + + // facets that unset metadata (a hack for making cookies work) + const unsets = Object.keys(facet).filter(value => facet[value] === 'unset'); + + // facets that specify that a field must not match a specific value + const xs = Object.keys(facet).filter(value => facet[value] === 'x'); + + if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; + const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value)); + const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value)); + const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length; + const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined; + const satisfiesMatchFacets = !matches.length + ? true + : matches.some(value => { + if (facetKey.startsWith('*')) { + // fields starting with a '*' are used to match families of related fields. ie, *modificationDate will match text_modificationDate, data_modificationDate, etc + const allKeys = Array.from(Object.keys(d)); + allKeys.push(...Object.keys(Doc.GetProto(d))); + const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); + return keys.some(key => Field.toString(d[key] as FieldType).includes(value)); + } + return Field.toString(d[facetKey] as FieldType).includes(value); + }); + // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria + if (parentCollection?.childFilters_boolean === 'OR') { + if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; + } + // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria + else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; + } + return parentCollection?.childFilters_boolean !== 'OR'; + }) + : childDocs; + const rangeFilteredDocs = filteredDocs.filter(d => { + for (let i = 0; i < childFiltersByRanges.length; i += 3) { + const key = childFiltersByRanges[i]; + const min = Number(childFiltersByRanges[i + 1]); + const max = Number(childFiltersByRanges[i + 2]); + const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null); + if (val === undefined) { + // console.log("Should 'undefined' pass range filter or not?") + } else if (val < min || val > max) return false; + } + return true; + }); + return rangeFilteredDocs; + } + + export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; + + export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { + broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore + return DocUtils.ActiveRecordings.map(audio => { + const sourceDoc = getSourceDoc(); + return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' }); + }); + } + + SetActiveAudioLinker(MakeLinkToActiveAudio); + + export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) { + if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; + if (target.doc === Doc.UserDoc()) return undefined; + + const makeLink = action((linkDoc: Doc, showAt?: number[]) => { + if (showAt) { + LinkManager.Instance.currentLink = linkDoc; + + TaskCompletionBox.textDisplayed = 'Link Created'; + TaskCompletionBox.popupX = showAt[0]; + TaskCompletionBox.popupY = showAt[1] - 33; + TaskCompletionBox.taskCompleted = true; + + LinkDescriptionPopup.Instance.popupX = showAt[0]; + LinkDescriptionPopup.Instance.popupY = showAt[1]; + LinkDescriptionPopup.Instance.display = true; + + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { + LinkDescriptionPopup.Instance.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { + LinkDescriptionPopup.Instance.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } + + setTimeout( + action(() => { + TaskCompletionBox.taskCompleted = false; + }), + 2500 + ); + } + return linkDoc; + }); + + const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1'; + const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2'; + + return makeLink( + Docs.Create.LinkDocument( + source, + target, + { + acl_Guest: SharingPermissions.Augment, + _acl_Guest: SharingPermissions.Augment, + title: ComputedField.MakeFunction('generateLinkTitle(this)') as any, + link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, + link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, + link_displayLine: linkSettings.link_displayLine, + link_relationship: linkSettings.link_relationship, + link_description: linkSettings.link_description, + x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any, + y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any, + link_autoMoveAnchors: true, + _lockedPosition: true, + _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box) + _layout_showTitle: '', + // _layout_showCaption: 'link_description', + // _layout_showTitle: 'link_relationship', + }, + id + ), + showPopup + ); + } + + export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) { + scripts && + Object.keys(scripts).forEach(key => { + const script = scripts[key]; + if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { + (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, { + self: Doc.name, + this: Doc.name, + dragData: Doc.DocDragDataName, + value: 'any', + _readOnly_: 'boolean', + scriptContext: 'any', + documentView: Doc.name, + heading: Doc.name, + checked: 'boolean', + containingTreeView: Doc.name, + altKey: 'boolean', + ctrlKey: 'boolean', + shiftKey: 'boolean', + }); + } + }); + funcs && + Object.keys(funcs) + .filter(key => !key.endsWith('-setter')) + .forEach(key => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { + const setFunc = Cast(funcs[key + '-setter'], 'string', null); + (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: Doc.DocDragDataName }, { _readOnly_: true }, setFunc) : undefined; + } + }); + return doc; + } + export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) { + if (doc) { + const compareValues = (val1: any, val2: any) => { + if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { + return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); + } + return val1 === val2; + }; + Object.entries(reqdOpts).forEach(pair => { + const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc); + if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) { + targetDoc[pair[0]] = pair[1]; + } + }); + items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item)); + items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item)); + } + return doc; + } + export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { + // eslint-disable-next-line no-return-assign + return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); + } + + /** + * + * @param type the type of file. + * @param path the path to the file. + * @param options the document options. + * @param overwriteDoc the placeholder loading doc. + * @returns + */ + export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise> { + let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise) | undefined; + if (type.indexOf('image') !== -1) { + ctor = Docs.Create.ImageDocument; + if (!options._width) options._width = 300; + } + if (type.indexOf('video') !== -1) { + ctor = Docs.Create.VideoDocument; + if (!options._width) options._width = 600; + if (!options._height) options._height = ((options._width as number) * 2) / 3; + } + if (type.indexOf('audio') !== -1) { + ctor = Docs.Create.AudioDocument; + } + if (type.indexOf('pdf') !== -1) { + ctor = Docs.Create.PdfDocument; + if (!options._width) options._width = 400; + if (!options._height) options._height = ((options._width as number) * 1200) / 927; + } + if (type.indexOf('csv') !== -1) { + ctor = Docs.Create.DataVizDocument; + if (!options._width) options._width = 400; + if (!options._height) options._height = ((options._width as number) * 1200) / 927; + } + // TODO:al+glr + // if (type.indexOf("map") !== -1) { + // ctor = Docs.Create.MapDocument; + // if (!options._width) options._width = 800; + // if (!options._height) options._height = (options._width as number) * 3 / 4; + // } + if (type.indexOf('html') !== -1) { + if (path.includes(window.location.hostname)) { + const s = path.split('/'); + const id = s[s.length - 1]; + return DocServer.GetRefField(id).then(field => { + if (field instanceof Doc) { + const embedding = Doc.MakeEmbedding(field); + embedding.x = (options.x as number) || 0; + embedding.y = (options.y as number) || 0; + embedding._width = (options._width as number) || 300; + embedding._height = (options._height as number) || (options._width as number) || 300; + return embedding; + } + return undefined; + }); + } + ctor = Docs.Create.WebDocument; + // eslint-disable-next-line no-param-reassign + options = { ...options, _width: 400, _height: 512, title: path }; + } + + return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined; + } + + export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { + const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) + .filter(btnDoc => !btnDoc.hidden) + .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) + .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title) + .map(dragDoc => ({ + description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), + event: undoable(() => { + const newDoc = DocUtils.copyDragFactory(dragDoc); + if (newDoc) { + newDoc.author = ClientUtils.CurrentUserEmail(); + newDoc.x = x; + newDoc.y = y; + Doc.SetSelectOnLoad(newDoc); + if (pivotField) { + newDoc[pivotField] = pivotValue; + } + docAdder?.(newDoc); + } + }, StrCast(dragDoc.title)), + icon: Doc.toIcon(dragDoc), + })) as ContextMenuProps[]; + ContextMenu.Instance.addItem({ + description: 'Create document', + subitems: documentList, + icon: 'file', + }); + !simpleMenu && + ContextMenu.Instance.addItem({ + description: 'Styled Notes', + subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({ + description: ':' + StrCast(note.title), + event: undoable(() => { + const textDoc = Docs.Create.TextDocument('', { + _width: 200, + x, + y, + _layout_autoHeight: note._layout_autoHeight !== false, + title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1), + }); + textDoc.layout_fieldKey = 'layout_' + note.title; + textDoc[textDoc.layout_fieldKey] = note; + if (pivotField) { + textDoc[pivotField] = pivotValue; + } + docTextAdder(textDoc); + }, 'create quick note'), + icon: StrCast(note.icon) as IconProp, + })) as ContextMenuProps[], + icon: 'sticky-note', + }); + const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data) + .filter(btnDoc => !btnDoc.hidden) + .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) + .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title) + .map(dragDoc => ({ + description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), + event: undoable(() => { + const newDoc = DocUtils.delegateDragFactory(dragDoc); + if (newDoc) { + newDoc.author = ClientUtils.CurrentUserEmail(); + newDoc.x = x; + newDoc.y = y; + Doc.SetSelectOnLoad(newDoc); + if (pivotField) { + newDoc[pivotField] = pivotValue; + } + docAdder?.(newDoc); + } + }, StrCast(dragDoc.title)), + icon: Doc.toIcon(dragDoc), + })) as ContextMenuProps[]; + ContextMenu.Instance.addItem({ + description: 'User Templates', + subitems: userDocList, + icon: 'file', + }); + } + + // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) + + /** + * Applies a template to a Doc and logs the action with the UndoManager + * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon'). + * Alternatively, the signature can be omitted and the template can be provided. + * @param doc the Doc to apply the template to. + * @param creator a function that will create the template if it doesn't exist + * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide) + * @param template the template to use (optional if templateSignature is provided) + * @returns doc + */ + export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) { + const batch = UndoManager.StartBatch('makeCustomViewClicked'); + createCustomView(doc, creator, templateSignature || StrCast(template?.title), template); + batch.end(); + return doc; + } + export function findTemplate(templateName: string, type: string) { + let docLayoutTemplate: Opt; + const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data); + const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data); + const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data); + const userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data); + const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data); + const allTemplates = iconViews + .concat(templBtns) + .concat(noteTypes) + .concat(userTypes) + .concat(clickFuncs) + .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc) + .filter(doc => doc.isTemplateDoc); + // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized + // first try to find a template that matches the specific document type (_). otherwise, fallback to a general match on + !docLayoutTemplate && + allTemplates.forEach(tempDoc => { + StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc); + }); + !docLayoutTemplate && + allTemplates.forEach(tempDoc => { + StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc); + }); + return docLayoutTemplate; + } + export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { + const templateName = templateSignature.replace(/\(.*\)/, ''); + doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? '')); + // eslint-disable-next-line no-param-reassign + docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type)); + + const customName = 'layout_' + templateSignature; + const _width = NumCast(doc._width); + const _height = NumCast(doc._height); + const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false }; + + if (docLayoutTemplate) { + if (docLayoutTemplate !== doc[customName]) { + Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + } + } else { + let fieldTemplate: Opt; + if (doc.data instanceof RichTextField || typeof doc.data === 'string') { + fieldTemplate = Docs.Create.TextDocument('', options); + } else if (doc.data instanceof PdfField) { + fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options); + } else if (doc.data instanceof VideoField) { + fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options); + } else if (doc.data instanceof AudioField) { + fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options); + } else if (doc.data instanceof ImageField) { + fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options); + } + const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); + fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); + docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + } + } + export function makeCustomView(doc: Doc, custom: boolean, layout: string) { + Doc.setNativeView(doc); + if (custom) { + makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined); + } + } + export function iconify(doc: Doc) { + const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null); + DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined); + if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', ''); + } + + export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) { + runInAction(() => { + docList.forEach((doc, i) => { + const d = doc; + DocUtils.iconify(d); + d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size; + d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size; + d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + }); + }); + if (create) { + const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false }); + newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size; + newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size; + newCollection._width = newCollection._height = size * 2; + return newCollection; + } + return undefined; + } + export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) { + const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from'); + if (!portalLink) { + DocUtils.MakeLink( + doc, + Docs.Create.FreeformDocument([], { + _width: NumCast(layoutDoc._width) + 10, + _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10), + _isLightbox: true, + _layout_fitWidth: true, + title: StrCast(doc.title) + ' [Portal]', + }), + { link_relationship: 'portal to:portal from' } + ); + } + doc.followLinkLocation = OpenWhere.lightbox; + doc.onClick = FollowLinkScript(); + } + + export function LeavePushpin(doc: Doc, annotationField: string) { + if (doc.followLinkToggle) return undefined; + const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null); + const hasContextAnchor = LinkManager.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context)); + if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { + const pushpin = Docs.Create.FontIconDocument({ + title: '', + annotationOn: Cast(doc.annotationOn, Doc, null), + followLinkToggle: true, + icon: 'map-pin', + x: Cast(doc.x, 'number', null), + y: Cast(doc.y, 'number', null), + backgroundColor: '#ACCEF7', + layout_hideAllLinks: true, + _width: 15, + _height: 15, + _xPadding: 0, + onClick: FollowLinkScript(), + _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), + }); + Doc.AddDocToList(context, annotationField, pushpin); + DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, ''); + doc._timecodeToShow = undefined; + return pushpin; + } + return undefined; + } + + // /** + // * + // * @param dms Degree Minute Second format exif gps data + // * @param ref ref that determines negativity of decimal coordinates + // * @returns a decimal format of gps latitude / longitude + // */ + // function getDecimalfromDMS(dms?: number[], ref?: string) { + // if (dms && ref) { + // let degrees = dms[0] / dms[1]; + // let minutes = dms[2] / dms[3] / 60.0; + // let seconds = dms[4] / dms[5] / 3600.0; + + // if (['S', 'W'].includes(ref)) { + // degrees = -degrees; minutes = -minutes; seconds = -seconds + // } + // return (degrees + minutes + seconds).toFixed(5); + // } + // } + + function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) { + let dd = degrees + minutes / 60 + seconds / (60 * 60); + if (direction === 'S' || direction === 'W') { + dd *= -1; + } // Don't do anything for N or E + return dd; + } + + export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) { + const proto = protoIn; + if (Upload.isImageInformation(result)) { + const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); + const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase(); + proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); + proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + if (NumCast(proto.data_nativeOrientation) >= 5) { + proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + } + proto.data_exif = JSON.stringify(result.exifData?.data); + proto.data_contentSize = result.contentSize; + // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates + const latitude = result.exifData?.data?.GPSLatitude; + const latitudeDirection = result.exifData?.data?.GPSLatitudeRef; + const longitude = result.exifData?.data?.GPSLongitude; + const longitudeDirection = result.exifData?.data?.GPSLongitudeRef; + if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) { + proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); + proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); + } + } + } + + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) { + if (result instanceof Error) { + alert(`Upload failed: ${result.message}`); + return; + } + const full = { ...options, _width: 400, title: name }; + const pathname = result.accessPaths.agnostic.client; + const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); + if (doc) { + const proto = Doc.GetProto(doc); + proto.text = result.rawText; + !(result instanceof Error) && DocUtils.assignImageInfo(result, proto); + if (Upload.isVideoInformation(result)) { + proto.data_duration = result.duration; + } + if (overwriteDoc) { + LoadingBox.removeCurrentlyLoading(overwriteDoc); + } + generatedDocuments.push(doc); + } + } + + export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { + const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); + const tbox = Docs.Create.TextDocument('', { + annotationOn, + backgroundColor, + x, + y, + title, + ...(defaultTextTemplate + ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance + : { + _width: width || 200, + _height: 35, + _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), + _layout_fitWidth: true, + _layout_autoHeight: true, + }), + }); + + if (defaultTextTemplate) { + tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); + Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template + tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values) + } + return tbox; + } + + export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { + const generatedDocuments: Doc[] = []; + Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => { + const { + source: { newFilename, mimetype }, + result, + } = upfiles.lastElement(); + if ((result as any).message) { + if (overwriteDoc) { + overwriteDoc.isLoading = false; + overwriteDoc.loadingError = (result as any).message; + LoadingBox.removeCurrentlyLoading(overwriteDoc); + } + } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc); + }); + } + + /** + * uploadFilesToDocs will take in an array of Files, and creates documents for the + * new files. + * + * @param files an array of files that will be uploaded + * @param options options to use while uploading + * @returns + */ + export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { + const generatedDocuments: Doc[] = []; + + // These files do not have overwriteDocs, so we do not set the guid and let the client generate one. + const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file })); + + const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs); + upfiles.forEach(({ source: { newFilename, mimetype }, result }) => { + newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options); + }); + return generatedDocuments; + } + + export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { + const generatedDocuments: Doc[] = []; + // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid. + Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => { + const { + source: { newFilename, mimetype }, + result, + } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } }; + if ((result as any).message) { + if (overwriteDoc) { + overwriteDoc.loadingError = (result as any).message; + LoadingBox.removeCurrentlyLoading(overwriteDoc); + } + } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc); + }); + } + + // copies the specified drag factory document + export function copyDragFactory(dragFactory: Doc) { + if (!dragFactory) return undefined; + const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); + if (ndoc && dragFactory.dragFactory_count !== undefined) { + dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1; + Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true); + } + + return ndoc; + } + export function delegateDragFactory(dragFactory: Doc) { + const ndoc = Doc.MakeDelegateWithProto(dragFactory); + if (ndoc && dragFactory.dragFactory_count !== undefined) { + dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1; + Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(); + } + return ndoc; + } + + export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') { + const { clone, map, linkMap } = await Doc.MakeClone(doc); + const proms = new Set(); + function replacer(key: any, value: any) { + if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; + if (value?.__type === 'image') { + const extension = value.url.replace(/.*\./, ''); + proms.add(value.url.replace('.' + extension, '_o.' + extension)); + return SerializationHelper.Serialize(new ImageField(value.url)); + } + if (value?.__type === 'pdf') { + proms.add(value.url); + return SerializationHelper.Serialize(new PdfField(value.url)); + } + if (value?.__type === 'audio') { + proms.add(value.url); + return SerializationHelper.Serialize(new AudioField(value.url)); + } + if (value?.__type === 'video') { + proms.add(value.url); + return SerializationHelper.Serialize(new VideoField(value.url)); + } + if ( + value instanceof Doc || + value instanceof ScriptField || + value instanceof RichTextField || + value instanceof InkField || + value instanceof CsvField || + value instanceof WebField || + value instanceof DateField || + value instanceof ProxyField || + value instanceof ComputedField + ) { + return SerializationHelper.Serialize(value); + } + if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' }; + return value; + } + + const docs: { [id: string]: any } = {}; + const links: { [id: string]: any } = {}; + Array.from(map.entries()).forEach(f => { + docs[f[0]] = f[1]; + }); + Array.from(linkMap.entries()).forEach(l => { + links[l[0]] = l[1]; + }); + const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer)); + + const zip = new JSZip(); + let count = 0; + const promArr = Array.from(proms) + .filter(url => url?.startsWith('/files')) + .map(url => url.replace('/', '')); // window.location.origin)); + console.log(promArr.length); + if (!promArr.length) { + zip.file('docs.json', jsonDocs); + zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); + } else + promArr.forEach((url, i) => { + // loading a file and add it in a zip file + JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => { + if (err) throw err; // or handle the error + // // Generate a directory within the Zip file structure + // const assets = zip.folder("assets"); + // assets.file(filename, data, {binary: true}); + const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%'); + zip.file(assetPathOnServer, data, { binary: true }); + console.log(' => ' + url); + if (++count === promArr.length) { + zip.file('docs.json', jsonDocs); + zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); + } + }); + }); + } +} + +ScriptingGlobals.add('Docs', Docs); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { + return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory; +}); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function makeDelegate(proto: any) { + const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); + return d; +}); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function generateLinkTitle(link: Doc) { + const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : ''; + const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : ''; + const relation = link.link_relationship || 'to'; + return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`; +}); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0e7f911c7..a3fea1ce4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,79 +1,27 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable default-param-last */ /* eslint-disable no-use-before-define */ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { saveAs } from 'file-saver'; -import * as JSZip from 'jszip'; -import { action, reaction, runInAction } from 'mobx'; +import { reaction } from 'mobx'; import { basename } from 'path'; import { ClientUtils, OmitKeys } from '../../ClientUtils'; -import * as JSZipUtils from '../../JSZipUtils'; -import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc'; -import { DocData, Initializing } from '../../fields/DocSymbols'; -import { Id } from '../../fields/FieldSymbols'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc'; +import { Initializing } from '../../fields/DocSymbols'; import { HtmlField } from '../../fields/HtmlField'; -import { InkDataFieldName, InkField } from '../../fields/InkField'; -import { List, ListFieldName } from '../../fields/List'; -import { ProxyField } from '../../fields/Proxy'; +import { InkField } from '../../fields/InkField'; +import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { ScriptCast, StrCast } from '../../fields/Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField'; -import { SharingPermissions, inheritParentAcls } from '../../fields/util'; +import { SharingPermissions } from '../../fields/util'; import { PointData } from '../../pen-gestures/GestureTypes'; -import { Upload } from '../../server/SharedMediaTypes'; import { DocServer } from '../DocServer'; -import { Networking } from '../Network'; -import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; -import { FollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SerializationHelper } from '../util/SerializationHelper'; -import { UndoManager, undoable } from '../util/UndoManager'; -import { ContextMenu } from '../views/ContextMenu'; -import { ContextMenuProps } from '../views/ContextMenuItem'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { CollectionView } from '../views/collections/CollectionView'; -import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; -import { AudioBox, mediaState } from '../views/nodes/AudioBox'; -import { ComparisonBox } from '../views/nodes/ComparisonBox'; -import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; -import { OpenWhere } from '../views/nodes/DocumentView'; -import { EquationBox } from '../views/nodes/EquationBox'; -import { FieldViewProps } from '../views/nodes/FieldView'; -import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; -import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox'; -import { ImageBox } from '../views/nodes/ImageBox'; -import { KeyValueBox } from '../views/nodes/KeyValueBox'; -import { LabelBox } from '../views/nodes/LabelBox'; -import { LinkBox } from '../views/nodes/LinkBox'; -import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; -import { LoadingBox } from '../views/nodes/LoadingBox'; -import { MapBox } from '../views/nodes/MapBox/MapBox'; -import { MapPushpinBox } from '../views/nodes/MapBox/MapPushpinBox'; -import { PDFBox } from '../views/nodes/PDFBox'; -import { PhysicsSimulationBox } from '../views/nodes/PhysicsBox/PhysicsSimulationBox'; -import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; -import { ScreenshotBox } from '../views/nodes/ScreenshotBox'; -import { ScriptingBox } from '../views/nodes/ScriptingBox'; -import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; -import { VideoBox } from '../views/nodes/VideoBox'; -import { WebBox } from '../views/nodes/WebBox'; -import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox'; -import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../views/nodes/trails/PresBox'; -import { PresElementBox } from '../views/nodes/trails/PresElementBox'; -import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; -const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore - -const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); class EmptyBox { public static LayoutString() { return ''; @@ -146,7 +94,7 @@ class DocInfo extends FInfo { } class DimInfo extends FInfo { fieldType? = FInfoFieldType.enumeration; - values? = [DimUnit.Pixel, DimUnit.Ratio]; + values? = []; // DimUnit.Pixel, DimUnit.Ratio]; readOnly = false; filterable = false; override searchable = () => false; @@ -202,7 +150,7 @@ type STRt = StrInfo | string; type LISTt = ListInfo | List; type DOCt = DocInfo | Doc; type RTFt = RtfInfo | RichTextField; -type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; +type DIMt = DimInfo; // | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type COLLt = CTypeInfo | CollectionViewType; type DROPt = DAInfo | dropActionType; @@ -245,6 +193,7 @@ export class DocumentOptions { _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false); _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false); + acl?: STRt = new StrInfo('unused except as a display category in KeyValueBox'); acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions _acl_Guest?: string; // public permissions type?: DTYPEt = new DTypeInfo('type of document', true); @@ -253,6 +202,7 @@ export class DocumentOptions { title?: STRt = new StrInfo('title of document', true); title_custom?: BOOLt = new BoolInfo('whether title is a default or has been intentionally set'); caption?: RichTextField; + systemIcon?: STRt = new StrInfo("name of icon to use to represent document's type"); author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document', false); @@ -272,6 +222,12 @@ export class DocumentOptions { _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged"); _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed'); + dataViz_title?: string; + dataViz_line?: string; + dataViz_pie?: string; + dataViz_histogram?: string; + dataViz?: string; + layout?: string | Doc; // default layout string or template document layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box'); layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document', false); @@ -325,6 +281,7 @@ export class DocumentOptions { _text_fontSize?: string; _text_fontFamily?: string; _text_fontWeight?: string; + fontSize?: string; _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views infoWindowOpen?: BOOLt = new BoolInfo('whether info window corresponding to pin is open (on MapDocuments)'); @@ -333,7 +290,7 @@ export class DocumentOptions { _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false); stroke_width?: NUMt = new NumInfo('width of an ink stroke', false); stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke'); - mediaState?: STRt = new StrInfo(`status of audio/video media document: ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false); + mediaState?: STRt = new StrInfo(`status of audio/video media document:`); // ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false); recording?: BOOLt = new BoolInfo('whether WebCam is recording or not'); slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)'); autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.'); @@ -403,6 +360,7 @@ export class DocumentOptions { // STOPPING HERE // freeform properties + freeform?: STRt = new StrInfo(''); _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections'); _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped'); _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views'); @@ -433,6 +391,7 @@ export class DocumentOptions { flexGap?: NUMt = new NumInfo('Linear view flex gap'); flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse'; + link?: string; link_description?: string; // added for links link_relationship?: string; // type of relatinoship a link represents link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors'); @@ -528,120 +487,12 @@ export namespace Docs { type PrototypeMap = Map; const defaultDataKey = 'data'; - const TemplateMap: TemplateMap = new Map([ - [ - DocumentType.RTF, - { - layout: { view: FormattedTextBox, dataField: 'text' }, - options: { - acl: '', - _height: 35, - _xMargin: 10, - _yMargin: 10, - layout_nativeDimEditable: true, - layout_reflowVertical: true, - layout_reflowHorizontal: true, - defaultDoubleClick: 'ignore', - systemIcon: 'BsFileEarmarkTextFill', - }, - }, - ], - [ - DocumentType.SEARCH, - { - layout: { view: SearchBox, dataField: defaultDataKey }, - options: { acl: '', _width: 400 }, - }, - ], - [ - DocumentType.IMG, - { - layout: { view: ImageBox, dataField: defaultDataKey }, - options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' }, - }, - ], - [ - DocumentType.WEB, - { - layout: { view: WebBox, dataField: defaultDataKey }, - options: { acl: '', _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' }, - }, - ], + export const TemplateMap: TemplateMap = new Map([ [ - DocumentType.COL, - { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { - acl: '', - _layout_fitWidth: true, - freeform: '', - _freeform_panX: 0, - _freeform_panY: 0, - _freeform_scale: 1, - layout_nativeDimEditable: true, - layout_reflowHorizontal: true, - layout_reflowVertical: true, - systemIcon: 'BsFillCollectionFill', - }, - }, - ], - [ - DocumentType.KVP, - { - layout: { view: KeyValueBox, dataField: defaultDataKey }, - options: { acl: '', _layout_fitWidth: true, _height: 150 }, - }, - ], - [ - DocumentType.VID, - { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' }, - }, - ], - [ - DocumentType.AUDIO, - { - layout: { view: AudioBox, dataField: defaultDataKey }, - options: { acl: '', _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' }, - }, - ], - [ - DocumentType.REC, - { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' }, - }, - ], - [ - DocumentType.PDF, - { - layout: { view: PDFBox, dataField: defaultDataKey }, - options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' }, - }, - ], - [ - DocumentType.MAP, - { - layout: { view: MapBox, dataField: defaultDataKey }, - options: { acl: '', map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' }, - }, - ], - [ - DocumentType.LINK, + DocumentType.GROUPDB, { - layout: { view: LinkBox, dataField: 'link' }, - options: { - acl: '', - childDontRegisterViews: true, - layout_hideLinkAnchors: true, - _height: 1, - _width: 1, - link: '', - link_description: '', - color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area - _dropPropertiesToRemove: new List(['onClick']), - }, + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { acl: '', title: 'Global Group Database' }, }, ], [ @@ -652,175 +503,18 @@ export namespace Docs { options: { acl: '', title: 'Global Script Database' }, }, ], - [ - DocumentType.SCRIPTING, - { - layout: { view: ScriptingBox, dataField: defaultDataKey }, - options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' }, - }, - ], - [ - DocumentType.LABEL, - { - layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true }, - }, - ], - [ - DocumentType.EQUATION, - { - layout: { view: EquationBox, dataField: 'text' }, - options: { acl: '', fontSize: '14px', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript - }, - ], - [ - DocumentType.FUNCPLOT, - { - layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { acl: '', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true }, - }, - ], - [ - DocumentType.BUTTON, - { - layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true }, - }, - ], - [ - DocumentType.PRES, - { - layout: { view: PresBox, dataField: defaultDataKey }, - options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true }, - }, - ], - [ - DocumentType.FONTICON, - { - layout: { view: FontIconBox, dataField: 'icon' }, - options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 }, - }, - ], - [ - DocumentType.WEBCAM, - { - layout: { view: RecordingBox, dataField: defaultDataKey }, - options: { acl: '', systemIcon: 'BsFillCameraVideoFill' }, - }, - ], - [ - DocumentType.PRESELEMENT, - { - layout: { view: PresElementBox, dataField: defaultDataKey }, - options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, - }, - ], + [ DocumentType.CONFIG, - { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true }, - }, - ], - [ - DocumentType.INK, - { - // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method - layout: { view: InkingStroke, dataField: 'stroke' }, - options: { - acl: '', - systemIcon: 'BsFillPencilFill', // - layout_nativeDimEditable: true, - layout_reflowVertical: true, - layout_reflowHorizontal: true, - layout_hideDecorationTitle: true, // don't show title when selected - fitWidth: false, - layout_isSvg: true, - }, - }, - ], - [ - DocumentType.SCREENSHOT, - { - layout: { view: ScreenshotBox, dataField: defaultDataKey }, - options: { acl: '', layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' }, - }, - ], - [ - DocumentType.COMPARISON, - { - data: '', - layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { - acl: '', - backgroundColor: 'gray', - dropAction: dropActionType.move, - waitForDoubleClickToClick: 'always', - layout_reflowHorizontal: true, - layout_reflowVertical: true, - layout_nativeDimEditable: true, - systemIcon: 'BsLayoutSplit', - }, - }, - ], - [ - DocumentType.GROUPDB, { layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { acl: '', title: 'Global Group Database' }, - }, - ], - [ - DocumentType.DATAVIZ, - { - layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { - acl: '', - dataViz_title: '', - dataViz_line: '', - dataViz_pie: '', - dataViz_histogram: '', - dataViz: 'table', - _layout_fitWidth: true, - layout_reflowHorizontal: true, - layout_reflowVertical: true, - layout_nativeDimEditable: true, - }, - }, - ], - [ - DocumentType.LOADING, - { - layout: { view: LoadingBox, dataField: '' }, - options: { acl: '', _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true }, - }, - ], - [ - DocumentType.SIMULATION, - { - data: '', - layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 }, - options: { acl: '', _height: 100, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' }, - }, - ], - [ - DocumentType.PUSHPIN, - { - layout: { view: MapPushpinBox, dataField: defaultDataKey }, - options: { acl: '' }, + options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true }, }, ], [ DocumentType.MAPROUTE, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { acl: '' }, - }, - ], - [ - DocumentType.CALENDAR, - { - layout: { view: CalendarBox, dataField: defaultDataKey }, + layout: { view: EmptyBox, dataField: defaultDataKey }, options: { acl: '' }, }, ], @@ -1005,7 +699,7 @@ export namespace Docs { } Doc.assign(viewDoc, viewProps, true, true); if (![DocumentType.LINK, DocumentType.CONFIG, DocumentType.LABEL].includes(viewDoc.type as any)) { - DocUtils.MakeLinkToActiveAudio(() => viewDoc); + CreateLinkToActiveAudio(() => viewDoc); } updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); @@ -1039,7 +733,7 @@ export namespace Docs { */ // eslint-disable-next-line default-param-last export function ScriptingDocument(script: Opt | null, options: DocumentOptions = {}, fieldKey?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `` /* ScriptingBox.LayoutString(fieldKey) */ : undefined }); } // eslint-disable-next-line default-param-last @@ -1309,34 +1003,6 @@ export namespace Docs { return ret; } - export type DocConfig = { - doc: Doc; - initialWidth?: number; - path?: Doc[]; - }; - - export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = 'row') { - const layoutConfig = { - content: [ - { - type: type, - content: [...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))], - }, - ], - }; - const doc = DockDocument( - configs.map(c => c.doc), - JSON.stringify(layoutConfig), - ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options }, - id - ); - configs.forEach(c => { - Doc.SetContainer(c.doc, doc); - inheritParentAcls(doc, c.doc, false); - }); - return doc; - } - export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) { return InstanceFromProto(proto, undefined, options); } @@ -1346,879 +1012,3 @@ export namespace Docs { } } } - -export namespace DocUtils { - function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean { - let value = valueIn; - const hasFunctionFilter = ClientUtils.HasFunctionFilter(value); - if (hasFunctionFilter) { - return hasFunctionFilter(StrCast(doc[key])); - } - if (key === LinkedTo) { - // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) - const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); - const matchLink = (val: string, anchor: Doc) => { - const linkedToExp = (val ?? '').split('='); - if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val; - return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; - }; - // prettier-ignore - return (value === Doc.FilterNone && !allLinks.length) || - (value === Doc.FilterAny && !!allLinks.length) || - (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || - matchLink(value,DocCast(link.link_anchor_2)) )); - } - if (typeof value === 'string') { - value = value.replace(`,${ClientUtils.noRecursionHack}`, ''); - } - const fieldVal = doc[key]; - // prettier-ignore - if ((value === Doc.FilterAny && fieldVal !== undefined) || - (value === Doc.FilterNone && fieldVal === undefined)) { - return true; - } - const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings - if (vals.length) { - return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring - } - return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring - } - /** - * @param docs - * @param childFilters - * @param childFiltersByRanges - * @param parentCollection - * Given a list of docs and childFilters, @returns the list of Docs that match those filters - */ - export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) { - if (!childFilters?.length && !childFiltersByRanges?.length) { - return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one - } - - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - childFilters.forEach(filter => { - const fields = filter.split(Doc.FilterSep); - const key = fields[0]; - const value = fields[1]; - const modifiers = fields[2]; - if (!filterFacets[key]) { - filterFacets[key] = {}; - } - filterFacets[key][value] = modifiers; - }); - - const filteredDocs = childFilters.length - ? childDocs.filter(d => { - if (d.z) return true; - // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter - if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { - return false; - } - const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]); - // eslint-disable-next-line no-restricted-syntax - for (const facetKey of facetKeys) { - const facet = filterFacets[facetKey]; - - // facets that match some value in the field of the document (e.g. some text field) - const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); - - // facets that have a check next to them - const checks = Object.keys(facet).filter(value => facet[value] === 'check'); - - // metadata facets that exist - const exists = Object.keys(facet).filter(value => facet[value] === 'exists'); - - // facets that unset metadata (a hack for making cookies work) - const unsets = Object.keys(facet).filter(value => facet[value] === 'unset'); - - // facets that specify that a field must not match a specific value - const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - - if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; - const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value)); - const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length; - const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined; - const satisfiesMatchFacets = !matches.length - ? true - : matches.some(value => { - if (facetKey.startsWith('*')) { - // fields starting with a '*' are used to match families of related fields. ie, *modificationDate will match text_modificationDate, data_modificationDate, etc - const allKeys = Array.from(Object.keys(d)); - allKeys.push(...Object.keys(Doc.GetProto(d))); - const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); - return keys.some(key => Field.toString(d[key] as FieldType).includes(value)); - } - return Field.toString(d[facetKey] as FieldType).includes(value); - }); - // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria - if (parentCollection?.childFilters_boolean === 'OR') { - if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; - } - // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria - else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; - } - return parentCollection?.childFilters_boolean !== 'OR'; - }) - : childDocs; - const rangeFilteredDocs = filteredDocs.filter(d => { - for (let i = 0; i < childFiltersByRanges.length; i += 3) { - const key = childFiltersByRanges[i]; - const min = Number(childFiltersByRanges[i + 1]); - const max = Number(childFiltersByRanges[i + 2]); - const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null); - if (val === undefined) { - // console.log("Should 'undefined' pass range filter or not?") - } else if (val < min || val > max) return false; - } - return true; - }); - return rangeFilteredDocs; - } - - export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; - - export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { - broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore - return DocUtils.ActiveRecordings.map(audio => { - const sourceDoc = getSourceDoc(); - return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' }); - }); - } - - export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) { - if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; - if (target.doc === Doc.UserDoc()) return undefined; - - const makeLink = action((linkDoc: Doc, showAt?: number[]) => { - if (showAt) { - LinkManager.Instance.currentLink = linkDoc; - - TaskCompletionBox.textDisplayed = 'Link Created'; - TaskCompletionBox.popupX = showAt[0]; - TaskCompletionBox.popupY = showAt[1] - 33; - TaskCompletionBox.taskCompleted = true; - - LinkDescriptionPopup.Instance.popupX = showAt[0]; - LinkDescriptionPopup.Instance.popupY = showAt[1]; - LinkDescriptionPopup.Instance.display = true; - - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { - LinkDescriptionPopup.Instance.popupX -= 190; - TaskCompletionBox.popupX -= 40; - } - if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { - LinkDescriptionPopup.Instance.popupY -= 40; - TaskCompletionBox.popupY -= 40; - } - - setTimeout( - action(() => { - TaskCompletionBox.taskCompleted = false; - }), - 2500 - ); - } - return linkDoc; - }); - - const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1'; - const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2'; - - return makeLink( - Docs.Create.LinkDocument( - source, - target, - { - acl_Guest: SharingPermissions.Augment, - _acl_Guest: SharingPermissions.Augment, - title: ComputedField.MakeFunction('generateLinkTitle(this)') as any, - link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, - link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, - link_displayLine: linkSettings.link_displayLine, - link_relationship: linkSettings.link_relationship, - link_description: linkSettings.link_description, - x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any, - y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any, - link_autoMoveAnchors: true, - _lockedPosition: true, - _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box) - _layout_showTitle: '', - // _layout_showCaption: 'link_description', - // _layout_showTitle: 'link_relationship', - }, - id - ), - showPopup - ); - } - - export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) { - scripts && - Object.keys(scripts).forEach(key => { - const script = scripts[key]; - if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { - (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, { - self: Doc.name, - this: Doc.name, - dragData: DragManager.DocumentDragData.name, - value: 'any', - _readOnly_: 'boolean', - scriptContext: 'any', - documentView: Doc.name, - heading: Doc.name, - checked: 'boolean', - containingTreeView: Doc.name, - altKey: 'boolean', - ctrlKey: 'boolean', - shiftKey: 'boolean', - }); - } - }); - funcs && - Object.keys(funcs) - .filter(key => !key.endsWith('-setter')) - .forEach(key => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { - const setFunc = Cast(funcs[key + '-setter'], 'string', null); - (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined; - } - }); - return doc; - } - export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) { - if (doc) { - const compareValues = (val1: any, val2: any) => { - if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { - return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); - } - return val1 === val2; - }; - Object.entries(reqdOpts).forEach(pair => { - const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc); - if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) { - targetDoc[pair[0]] = pair[1]; - } - }); - items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item)); - items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item)); - } - return doc; - } - export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { - // eslint-disable-next-line no-return-assign - return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); - } - - export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { - let created: Doc | undefined; - const field = target[fieldKey]; - const resolved = options ?? {}; - if (field instanceof ImageField) { - created = Docs.Create.ImageDocument(field.url.href, resolved); - created.layout = ImageBox.LayoutString(fieldKey); - } else if (field instanceof Doc) { - created = field; - } else if (field instanceof VideoField) { - created = Docs.Create.VideoDocument(field.url.href, resolved); - created.layout = VideoBox.LayoutString(fieldKey); - } else if (field instanceof PdfField) { - created = Docs.Create.PdfDocument(field.url.href, resolved); - created.layout = PDFBox.LayoutString(fieldKey); - } else if (field instanceof AudioField) { - created = Docs.Create.AudioDocument(field.url.href, resolved); - created.layout = AudioBox.LayoutString(fieldKey); - } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(field.inkData, resolved); - created.layout = InkingStroke.LayoutString(fieldKey); - } else if (field instanceof List && field[0] instanceof Doc) { - created = Docs.Create.StackingDocument(DocListCast(field), resolved); - created.layout = CollectionView.LayoutString(fieldKey); - } else { - created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved }); - created.layout = FormattedTextBox.LayoutString(fieldKey); - } - if (created) { - created.title = fieldKey; - proto && created.proto && (created.proto = Doc.GetProto(proto)); - } - return created; - } - - /** - * - * @param type the type of file. - * @param path the path to the file. - * @param options the document options. - * @param overwriteDoc the placeholder loading doc. - * @returns - */ - export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise> { - let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise) | undefined; - if (type.indexOf('image') !== -1) { - ctor = Docs.Create.ImageDocument; - if (!options._width) options._width = 300; - } - if (type.indexOf('video') !== -1) { - ctor = Docs.Create.VideoDocument; - if (!options._width) options._width = 600; - if (!options._height) options._height = ((options._width as number) * 2) / 3; - } - if (type.indexOf('audio') !== -1) { - ctor = Docs.Create.AudioDocument; - } - if (type.indexOf('pdf') !== -1) { - ctor = Docs.Create.PdfDocument; - if (!options._width) options._width = 400; - if (!options._height) options._height = ((options._width as number) * 1200) / 927; - } - if (type.indexOf('csv') !== -1) { - ctor = Docs.Create.DataVizDocument; - if (!options._width) options._width = 400; - if (!options._height) options._height = ((options._width as number) * 1200) / 927; - } - // TODO:al+glr - // if (type.indexOf("map") !== -1) { - // ctor = Docs.Create.MapDocument; - // if (!options._width) options._width = 800; - // if (!options._height) options._height = (options._width as number) * 3 / 4; - // } - if (type.indexOf('html') !== -1) { - if (path.includes(window.location.hostname)) { - const s = path.split('/'); - const id = s[s.length - 1]; - return DocServer.GetRefField(id).then(field => { - if (field instanceof Doc) { - const embedding = Doc.MakeEmbedding(field); - embedding.x = (options.x as number) || 0; - embedding.y = (options.y as number) || 0; - embedding._width = (options._width as number) || 300; - embedding._height = (options._height as number) || (options._width as number) || 300; - return embedding; - } - return undefined; - }); - } - ctor = Docs.Create.WebDocument; - // eslint-disable-next-line no-param-reassign - options = { ...options, _width: 400, _height: 512, title: path }; - } - - return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined; - } - - export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { - const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) - .filter(btnDoc => !btnDoc.hidden) - .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title) - .map(dragDoc => ({ - description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable(() => { - const newDoc = DocUtils.copyDragFactory(dragDoc); - if (newDoc) { - newDoc.author = ClientUtils.CurrentUserEmail(); - newDoc.x = x; - newDoc.y = y; - EquationBox.SelectOnLoad = newDoc[Id]; - if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc); - if (pivotField) { - newDoc[pivotField] = pivotValue; - } - docAdder?.(newDoc); - } - }, StrCast(dragDoc.title)), - icon: Doc.toIcon(dragDoc), - })) as ContextMenuProps[]; - ContextMenu.Instance.addItem({ - description: 'Create document', - subitems: documentList, - icon: 'file', - }); - !simpleMenu && - ContextMenu.Instance.addItem({ - description: 'Styled Notes', - subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({ - description: ':' + StrCast(note.title), - event: undoable(() => { - const textDoc = Docs.Create.TextDocument('', { - _width: 200, - x, - y, - _layout_autoHeight: note._layout_autoHeight !== false, - title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1), - }); - textDoc.layout_fieldKey = 'layout_' + note.title; - textDoc[textDoc.layout_fieldKey] = note; - if (pivotField) { - textDoc[pivotField] = pivotValue; - } - docTextAdder(textDoc); - }, 'create quick note'), - icon: StrCast(note.icon) as IconProp, - })) as ContextMenuProps[], - icon: 'sticky-note', - }); - const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data) - .filter(btnDoc => !btnDoc.hidden) - .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title) - .map(dragDoc => ({ - description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable(() => { - const newDoc = DocUtils.delegateDragFactory(dragDoc); - if (newDoc) { - newDoc.author = ClientUtils.CurrentUserEmail(); - newDoc.x = x; - newDoc.y = y; - EquationBox.SelectOnLoad = newDoc[Id]; - if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc); - if (pivotField) { - newDoc[pivotField] = pivotValue; - } - docAdder?.(newDoc); - } - }, StrCast(dragDoc.title)), - icon: Doc.toIcon(dragDoc), - })) as ContextMenuProps[]; - ContextMenu.Instance.addItem({ - description: 'User Templates', - subitems: userDocList, - icon: 'file', - }); - } - - // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) - - /** - * Applies a template to a Doc and logs the action with the UndoManager - * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon'). - * Alternatively, the signature can be omitted and the template can be provided. - * @param doc the Doc to apply the template to. - * @param creator a function that will create the template if it doesn't exist - * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide) - * @param template the template to use (optional if templateSignature is provided) - * @returns doc - */ - export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) { - const batch = UndoManager.StartBatch('makeCustomViewClicked'); - createCustomView(doc, creator, templateSignature || StrCast(template?.title), template); - batch.end(); - return doc; - } - export function findTemplate(templateName: string, type: string) { - let docLayoutTemplate: Opt; - const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data); - const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data); - const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data); - const userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data); - const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data); - const allTemplates = iconViews - .concat(templBtns) - .concat(noteTypes) - .concat(userTypes) - .concat(clickFuncs) - .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc) - .filter(doc => doc.isTemplateDoc); - // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized - // first try to find a template that matches the specific document type (_). otherwise, fallback to a general match on - !docLayoutTemplate && - allTemplates.forEach(tempDoc => { - StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc); - }); - !docLayoutTemplate && - allTemplates.forEach(tempDoc => { - StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc); - }); - return docLayoutTemplate; - } - export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { - const templateName = templateSignature.replace(/\(.*\)/, ''); - doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? '')); - // eslint-disable-next-line no-param-reassign - docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type)); - - const customName = 'layout_' + templateSignature; - const _width = NumCast(doc._width); - const _height = NumCast(doc._height); - const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false }; - - if (docLayoutTemplate) { - if (docLayoutTemplate !== doc[customName]) { - Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); - } - } else { - let fieldTemplate: Opt; - if (doc.data instanceof RichTextField || typeof doc.data === 'string') { - fieldTemplate = Docs.Create.TextDocument('', options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options); - } - const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); - docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); - } - } - export function makeCustomView(doc: Doc, custom: boolean, layout: string) { - Doc.setNativeView(doc); - if (custom) { - makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined); - } - } - export function iconify(doc: Doc) { - const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null); - DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined); - if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', ''); - } - - export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) { - runInAction(() => { - docList.forEach((doc, i) => { - const d = doc; - DocUtils.iconify(d); - d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size; - d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size; - d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - }); - }); - if (create) { - const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size; - newCollection._width = newCollection._height = size * 2; - return newCollection; - } - return undefined; - } - export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) { - const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from'); - if (!portalLink) { - DocUtils.MakeLink( - doc, - Docs.Create.FreeformDocument([], { - _width: NumCast(layoutDoc._width) + 10, - _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10), - _isLightbox: true, - _layout_fitWidth: true, - title: StrCast(doc.title) + ' [Portal]', - }), - { link_relationship: 'portal to:portal from' } - ); - } - doc.followLinkLocation = OpenWhere.lightbox; - doc.onClick = FollowLinkScript(); - } - - export function LeavePushpin(doc: Doc, annotationField: string) { - if (doc.followLinkToggle) return undefined; - const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null); - const hasContextAnchor = LinkManager.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context)); - if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { - const pushpin = Docs.Create.FontIconDocument({ - title: '', - annotationOn: Cast(doc.annotationOn, Doc, null), - followLinkToggle: true, - icon: 'map-pin', - x: Cast(doc.x, 'number', null), - y: Cast(doc.y, 'number', null), - backgroundColor: '#ACCEF7', - layout_hideAllLinks: true, - _width: 15, - _height: 15, - _xPadding: 0, - onClick: FollowLinkScript(), - _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), - }); - Doc.AddDocToList(context, annotationField, pushpin); - DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, ''); - doc._timecodeToShow = undefined; - return pushpin; - } - return undefined; - } - - // /** - // * - // * @param dms Degree Minute Second format exif gps data - // * @param ref ref that determines negativity of decimal coordinates - // * @returns a decimal format of gps latitude / longitude - // */ - // function getDecimalfromDMS(dms?: number[], ref?: string) { - // if (dms && ref) { - // let degrees = dms[0] / dms[1]; - // let minutes = dms[2] / dms[3] / 60.0; - // let seconds = dms[4] / dms[5] / 3600.0; - - // if (['S', 'W'].includes(ref)) { - // degrees = -degrees; minutes = -minutes; seconds = -seconds - // } - // return (degrees + minutes + seconds).toFixed(5); - // } - // } - - function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) { - let dd = degrees + minutes / 60 + seconds / (60 * 60); - if (direction === 'S' || direction === 'W') { - dd *= -1; - } // Don't do anything for N or E - return dd; - } - - export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) { - const proto = protoIn; - if (Upload.isImageInformation(result)) { - const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase(); - proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); - proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (NumCast(proto.data_nativeOrientation) >= 5) { - proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - } - proto.data_exif = JSON.stringify(result.exifData?.data); - proto.data_contentSize = result.contentSize; - // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates - const latitude = result.exifData?.data?.GPSLatitude; - const latitudeDirection = result.exifData?.data?.GPSLatitudeRef; - const longitude = result.exifData?.data?.GPSLongitude; - const longitudeDirection = result.exifData?.data?.GPSLongitudeRef; - if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) { - proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); - proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); - } - } - } - - async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) { - if (result instanceof Error) { - alert(`Upload failed: ${result.message}`); - return; - } - const full = { ...options, _width: 400, title: name }; - const pathname = result.accessPaths.agnostic.client; - const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); - if (doc) { - const proto = Doc.GetProto(doc); - proto.text = result.rawText; - !(result instanceof Error) && DocUtils.assignImageInfo(result, proto); - if (Upload.isVideoInformation(result)) { - proto.data_duration = result.duration; - } - if (overwriteDoc) { - LoadingBox.removeCurrentlyLoading(overwriteDoc); - } - generatedDocuments.push(doc); - } - } - - export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { - const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); - const tbox = Docs.Create.TextDocument('', { - annotationOn, - backgroundColor, - x, - y, - title, - ...(defaultTextTemplate - ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance - : { - _width: width || 200, - _height: 35, - _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), - _layout_fitWidth: true, - _layout_autoHeight: true, - }), - }); - - if (defaultTextTemplate) { - tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); - Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template - tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values) - } - return tbox; - } - - export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { - const generatedDocuments: Doc[] = []; - Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => { - const { - source: { newFilename, mimetype }, - result, - } = upfiles.lastElement(); - if ((result as any).message) { - if (overwriteDoc) { - overwriteDoc.isLoading = false; - overwriteDoc.loadingError = (result as any).message; - LoadingBox.removeCurrentlyLoading(overwriteDoc); - } - } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc); - }); - } - - /** - * uploadFilesToDocs will take in an array of Files, and creates documents for the - * new files. - * - * @param files an array of files that will be uploaded - * @param options options to use while uploading - * @returns - */ - export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { - const generatedDocuments: Doc[] = []; - - // These files do not have overwriteDocs, so we do not set the guid and let the client generate one. - const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file })); - - const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs); - upfiles.forEach(({ source: { newFilename, mimetype }, result }) => { - newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options); - }); - return generatedDocuments; - } - - export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { - const generatedDocuments: Doc[] = []; - // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid. - Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => { - const { - source: { newFilename, mimetype }, - result, - } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } }; - if ((result as any).message) { - if (overwriteDoc) { - overwriteDoc.loadingError = (result as any).message; - LoadingBox.removeCurrentlyLoading(overwriteDoc); - } - } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc); - }); - } - - // copies the specified drag factory document - export function copyDragFactory(dragFactory: Doc) { - if (!dragFactory) return undefined; - const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - if (ndoc && dragFactory.dragFactory_count !== undefined) { - dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1; - Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true); - } - - return ndoc; - } - export function delegateDragFactory(dragFactory: Doc) { - const ndoc = Doc.MakeDelegateWithProto(dragFactory); - if (ndoc && dragFactory.dragFactory_count !== undefined) { - dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1; - Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(); - } - return ndoc; - } - - export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') { - const { clone, map, linkMap } = await Doc.MakeClone(doc); - const proms = new Set(); - function replacer(key: any, value: any) { - if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; - if (value?.__type === 'image') { - const extension = value.url.replace(/.*\./, ''); - proms.add(value.url.replace('.' + extension, '_o.' + extension)); - return SerializationHelper.Serialize(new ImageField(value.url)); - } - if (value?.__type === 'pdf') { - proms.add(value.url); - return SerializationHelper.Serialize(new PdfField(value.url)); - } - if (value?.__type === 'audio') { - proms.add(value.url); - return SerializationHelper.Serialize(new AudioField(value.url)); - } - if (value?.__type === 'video') { - proms.add(value.url); - return SerializationHelper.Serialize(new VideoField(value.url)); - } - if ( - value instanceof Doc || - value instanceof ScriptField || - value instanceof RichTextField || - value instanceof InkField || - value instanceof CsvField || - value instanceof WebField || - value instanceof DateField || - value instanceof ProxyField || - value instanceof ComputedField - ) { - return SerializationHelper.Serialize(value); - } - if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' }; - return value; - } - - const docs: { [id: string]: any } = {}; - const links: { [id: string]: any } = {}; - Array.from(map.entries()).forEach(f => { - docs[f[0]] = f[1]; - }); - Array.from(linkMap.entries()).forEach(l => { - links[l[0]] = l[1]; - }); - const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer)); - - const zip = new JSZip(); - let count = 0; - const promArr = Array.from(proms) - .filter(url => url?.startsWith('/files')) - .map(url => url.replace('/', '')); // window.location.origin)); - console.log(promArr.length); - if (!promArr.length) { - zip.file('docs.json', jsonDocs); - zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); - } else - promArr.forEach((url, i) => { - // loading a file and add it in a zip file - JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => { - if (err) throw err; // or handle the error - // // Generate a directory within the Zip file structure - // const assets = zip.folder("assets"); - // assets.file(filename, data, {binary: true}); - const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%'); - zip.file(assetPathOnServer, data, { binary: true }); - console.log(' => ' + url); - if (++count === promArr.length) { - zip.file('docs.json', jsonDocs); - zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); - } - }); - }); - } -} - -ScriptingGlobals.add('Docs', Docs); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { - return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory; -}); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function makeDelegate(proto: any) { - const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); - return d; -}); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function generateLinkTitle(link: Doc) { - const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : ''; - const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : ''; - const relation = link.link_relationship || 'to'; - return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`; -}); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 00279e7e1..96f5172bc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,28 +8,28 @@ import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; -import { ScriptField } from "../../fields/ScriptField"; +import { FollowLinkScript, ScriptField } from "../../fields/ScriptField"; import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types"; import { WebField, nullAudio } from "../../fields/URLField"; import { SetCachedGroups, SharingPermissions } from "../../fields/util"; import { Gestures } from "../../pen-gestures/GestureTypes"; import { DocServer } from "../DocServer"; +import { DocUtils } from '../documents/DocUtils'; import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; -import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents"; +import { Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents"; import { DashboardView } from "../views/DashboardView"; import { OverlayView } from "../views/OverlayView"; import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView"; import { Colors } from "../views/global/globalEnums"; import { mediaState } from "../views/nodes/AudioBox"; -import { OpenWhere } from "../views/nodes/DocumentView"; import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { LabelBox } from "../views/nodes/LabelBox"; +import { OpenWhere } from "../views/nodes/OpenWhere"; import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; import { DragManager } from "./DragManager"; import { dropActionType } from "./DropActionTypes"; import { MakeTemplate } from "./DropConverter"; -import { FollowLinkScript } from "./LinkFollower"; import { LinkManager, UPDATE_SERVER_CACHE } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; import { ColorScheme } from "./SettingsManager"; diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 9026b368f..97ee628e2 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -4,6 +4,7 @@ import * as interpreter from 'words-to-numbers'; import type {} from '@types/dom-speech-recognition'; import { ClientUtils } from '../../ClientUtils'; import { Doc, Opt } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { listSpec } from '../../fields/Schema'; @@ -12,10 +13,10 @@ import { AudioField, ImageField } from '../../fields/URLField'; import { DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { DictationOverlay } from '../views/DictationOverlay'; -import { DocumentView, OpenWhere } from '../views/nodes/DocumentView'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { OpenWhere } from '../views/nodes/OpenWhere'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; -import { DocData } from '../../fields/DocSymbols'; /** * This namespace provides a singleton instance of a manager that diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index a893fe627..8f0c2b0bf 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,10 +10,11 @@ import { GetEffectiveAcl } from '../../fields/util'; import { CollectionViewType } from '../documents/DocumentTypes'; import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; -import { DocumentView, DocumentViewInternal, OpenWhere } from '../views/nodes/DocumentView'; -import { FocusViewOptions } from '../views/nodes/FieldView'; +import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView'; +import { FocusViewOptions } from '../views/nodes/FocusViewOptions'; import { KeyValueBox } from '../views/nodes/KeyValueBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; +import { OpenWhere } from '../views/nodes/OpenWhere'; import { PresBox } from '../views/nodes/trails'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 3890b7845..34f54be2e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -18,14 +18,13 @@ import { action, observable, runInAction } from 'mobx'; import { ClientUtils } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; import { DateField } from '../../fields/DateField'; -import { Doc, FieldType, Opt, StrListCast } from '../../fields/Doc'; +import { CreateLinkToActiveAudio, Doc, FieldType, Opt, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; import { ScriptCast } from '../../fields/Types'; -import { DocUtils, Docs } from '../documents/Documents'; -import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView'; +import { Docs } from '../documents/Documents'; import { DocumentView } from '../views/nodes/DocumentView'; import { dropActionType } from './DropActionTypes'; import { ScriptingGlobals } from './ScriptingGlobals'; @@ -74,6 +73,7 @@ export function SetupDrag(_reference: React.RefObject, docFunc: () } export namespace DragManager { + export const dragClassName = 'collectionFreeFormDocumentView-container'; let dragDiv: HTMLDivElement; let dragLabel: HTMLDivElement; export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>; @@ -139,11 +139,11 @@ export namespace DragManager { constructor(dragDoc: Doc[], dropAction?: dropActionType) { this.draggedDocuments = dragDoc; this.droppedDocuments = []; - this.draggedViews = []; this.offset = [0, 0]; this.dropAction = dropAction; } - draggedViews: DocumentView[]; + dragEnding?: () => void; + dragStarting?: () => void; draggedDocuments: Doc[]; droppedDocuments: Doc[]; treeViewDoc?: Doc; @@ -157,6 +157,7 @@ export namespace DragManager { removeDocument?: RemoveFunction; isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts } + Doc.SetDocDragDataName(DocumentDragData.name); export class LinkDragData { constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc) { this.linkDragView = dragView; @@ -224,12 +225,12 @@ export namespace DragManager { export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) { const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField()); - dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); + dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc); return dropDoc; }; const finishDrag = async (e: DragCompleteEvent) => { const { docDragData } = e; - setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.())); + setTimeout(() => dragData.dragEnding?.()); onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; @@ -265,7 +266,7 @@ export namespace DragManager { }; dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, finishDrag); - dragData.draggedViews.forEach(view => view.props.dragStarting?.()); + dragData.dragStarting?.(); return true; } @@ -418,7 +419,7 @@ export namespace DragManager { // if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu) let rotation: number | undefined; for (let parEle: HTMLElement | null | undefined = ele.parentElement; parEle; parEle = parEle?.parentElement) { - if (parEle.className === CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName) { + if (parEle.className === DragManager.dragClassName) { rotation = (rotation ?? 0) + Number(parEle.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0); } parEle = parEle.parentElement; @@ -630,7 +631,7 @@ export namespace DragManager { dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false)); }; const cleanupDrag = action((undo: boolean) => { - (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.()); + (dragData as DocumentDragData).dragEnding?.(); hideDragShowOriginalElements(false); document.removeEventListener('pointermove', moveHandler, true); document.removeEventListener('pointerup', upHandler, true); diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 8d84dbad8..e1a503ac9 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -19,8 +19,8 @@ import { ObservableReactComponent } from '../views/ObservableReactComponent'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import './GroupManager.scss'; import { GroupMemberView } from './GroupMemberView'; -import { SettingsManager } from './SettingsManager'; import { SharingManager, User } from './SharingManager'; +import { SnappingManager } from './SnappingManager'; /** * Interface for options for the react-select component @@ -289,7 +289,7 @@ export class GroupManager extends ObservableReactComponent<{}> { */ private get groupCreationModal() { const contents = ( -
+

New Group @@ -384,7 +384,7 @@ export class GroupManager extends ObservableReactComponent<{}> { const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; return ( -

+
{this.groupCreationModal} {this.currentGroup ? ( { const hasEditAccess = GroupManager.Instance.hasEditAccess(this.group); return !this.group ? null : ( -
+
containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext))) { - PresBox.OpenPresMinimized(target, [0, 0]); + if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) { + alert('OPEN Pres minimized'); + // PresBox.OpenPresMinimized(target, [0, 0]); } finished?.(); } else { @@ -138,9 +137,6 @@ ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true }; }); -export function FollowLinkScript() { - return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' }); -} export function IsFollowLinkScript(field: FieldResult) { return ScriptCast(field)?.script.originalScript.includes('return followLink('); } diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index 05fb849fd..248fda7e3 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -2,7 +2,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { MainViewModal } from '../views/MainViewModal'; -import { SettingsManager } from './SettingsManager'; +import { SnappingManager } from './SnappingManager'; @observer export class RTFMarkup extends React.Component<{}> { @@ -10,6 +10,10 @@ export class RTFMarkup extends React.Component<{}> { static Instance: RTFMarkup; @observable private isOpen = false; // whether the SharingManager modal is open or not + public setOpen = action((status: boolean) => { + this.isOpen = status; + }); + constructor(props: {}) { super(props); makeObservable(this); @@ -21,7 +25,7 @@ export class RTFMarkup extends React.Component<{}> { */ @computed get cheatSheet() { return ( -
+

(@wiki:phrase) {` display wikipedia page for entered text (terminate with carriage return)`} @@ -122,24 +126,14 @@ export class RTFMarkup extends React.Component<{}> { ); } - @action - public open = () => { - this.isOpen = true; - }; - - @action - public close = () => { - this.isOpen = false; - }; - render() { return ( this.setOpen(false)} /> ); } diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index 2c8fdf483..dcc6aabab 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -2,7 +2,7 @@ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { Doc, IdToDoc } from '../../fields/Doc'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; -import { OpenWhereMod } from '../views/nodes/DocumentView'; +import { OpenWhereMod } from '../views/nodes/OpenWhere'; import { VideoBox } from '../views/nodes/VideoBox'; import { DocumentManager } from './DocumentManager'; import { Movement, Presentation } from './TrackMovements'; diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 8347844f7..8cb97fe50 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -14,9 +14,7 @@ import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; -import { GestureOverlay } from '../views/GestureOverlay'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; import { SnappingManager } from './SnappingManager'; @@ -256,25 +254,14 @@ export class SettingsManager extends React.Component<{}> { size={Size.XSMALL} color={SettingsManager.userColor} /> - { - FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels; - }} - toggleStatus={FontIconBox.ShowIconLabels} - size={Size.XSMALL} - color={SettingsManager.userColor} - /> { - GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures; + Doc.UserDoc().recognizeGestures = !Doc.UserDoc().recognizeGestures; }} - toggleStatus={GestureOverlay.RecognizeGestures} + toggleStatus={BoolCast(Doc.UserDoc().recognizeGestures)} size={Size.XSMALL} color={SettingsManager.userColor} /> diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 19a7948e6..b0140b73c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -18,7 +18,6 @@ import { Id } from '../../fields/FieldSymbols'; import { StrCast } from '../../fields/Types'; import { GetEffectiveAcl, SharingPermissions, TraceMobx, distributeAcls, normalizeEmail } from '../../fields/util'; import { DocServer } from '../DocServer'; -import { DictationOverlay } from '../views/DictationOverlay'; import { MainViewModal } from '../views/MainViewModal'; import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; @@ -27,8 +26,8 @@ import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; import { SearchUtil } from './SearchUtil'; import { SelectionManager } from './SelectionManager'; -import { SettingsManager } from './SettingsManager'; import './SharingManager.scss'; +import { SnappingManager } from './SnappingManager'; import { undoable } from './UndoManager'; export interface User { @@ -238,14 +237,14 @@ export class SharingManager extends React.Component<{}> { const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : ( -

+
{StrCast(group.title)}
  {group instanceof Doc ? ( } size={Size.XSMALL} - color={SettingsManager.userColor} + color={SnappingManager.userColor} onClick={action(() => { GroupManager.Instance.currentGroup = group; })} @@ -279,10 +278,10 @@ export class SharingManager extends React.Component<{}> {
-

+

window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}> window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
@@ -290,10 +289,10 @@ export class SharingManager extends React.Component<{}> { {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}

-
-
{admin ? (
@@ -335,7 +334,7 @@ export class SharingManager extends React.Component<{}> {
-
@@ -514,7 +513,6 @@ export class SharingManager extends React.Component<{}> { setTimeout( action(() => { // this.copied = false; - DictationOverlay.Instance.hasActiveModal = false; this.targetDoc = undefined; }), 500 @@ -528,7 +526,6 @@ export class SharingManager extends React.Component<{}> { runInAction(() => { this.targetDocView = target; this.targetDoc = targetDoc || target?.Document; - DictationOverlay.Instance.hasActiveModal = true; this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Augment; this.upgradeNested = true; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index c4855d2e7..d92a73b63 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -15,9 +15,10 @@ import { PrefetchProxy } from '../../fields/Proxy'; import { listSpec } from '../../fields/Schema'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, ImageCast, StrCast } from '../../fields/Types'; -import { normalizeEmail } from '../../fields/util'; +import { SharingPermissions, inheritParentAcls, normalizeEmail } from '../../fields/util'; import { DocServer } from '../DocServer'; -import { DocUtils, Docs, DocumentOptions } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; +import { Docs, DocumentOptions } from '../documents/Documents'; import { dropActionType } from '../util/DropActionTypes'; import { HistoryUtil } from '../util/History'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; @@ -36,11 +37,51 @@ enum DashboardGroup { SharedDashboards, } +export type DocConfig = { + doc: Doc; + initialWidth?: number; + path?: Doc[]; +}; + // DashboardView is the view with the dashboard previews, rendered when the app first loads @observer export class DashboardView extends ObservableReactComponent<{}> { public static _urlState: HistoryUtil.DocUrl; + public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) { + return { + type: 'react-component', + component: 'DocumentFrameRenderer', + title: document.title, + width: width, + props: { + documentId: document[Id], + keyValue, + panelName, // name of tab that can be used to close or replace its contents + }, + }; + } + static StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = 'row') { + const layoutConfig = { + content: [ + { + type: type, + content: [...configs.map(config => DashboardView.makeDocumentConfig(config.doc, undefined, config.initialWidth))], + }, + ], + }; + const doc = Docs.Create.DockDocument( + configs.map(c => c.doc), + JSON.stringify(layoutConfig), + ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options }, + id + ); + configs.forEach(c => { + Doc.SetContainer(c.doc, doc); + inheritParentAcls(doc, c.doc, false); + }); + return doc; + } constructor(props: any) { super(props); makeObservable(this); @@ -389,7 +430,7 @@ export class DashboardView extends ObservableReactComponent<{}> { const title = name || `Dashboard ${dashboardCount}`; const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row'); + const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row'); Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true); dashboardDoc.pane_count = 1; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 391c2f694..a0e3d2ddd 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -12,8 +12,10 @@ import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; import { ObservableReactComponent } from './ObservableReactComponent'; import { PinProps } from './PinFuncs'; -import { DocumentView, OpenWhere } from './nodes/DocumentView'; -import { FieldViewProps, FocusViewOptions } from './nodes/FieldView'; +import { DocumentView } from './nodes/DocumentView'; +import { FocusViewOptions } from './nodes/FocusViewOptions'; +import { FieldViewProps } from './nodes/FieldView'; +import { OpenWhere } from './nodes/OpenWhere'; // import { DocUtils } from '../documents/Documents'; /** @@ -41,6 +43,7 @@ export interface ViewBoxInterface { onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) + playTrail?: (docs: Doc[]) => void; playFrom?: (time: number, endTime?: number) => void; Pause?: () => void; // pause a media document (eg, audio/video) IsPlaying?: () => boolean; // is a media document playing diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c5358dffe..dd744f272 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -14,7 +14,7 @@ import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from ' import { emptyFunction } from '../../Utils'; import { Doc } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; -import { DocUtils } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; @@ -22,15 +22,16 @@ import { IsFollowLinkScript } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { UndoManager, undoBatch } from '../util/UndoManager'; -import { PinProps } from './DocComponent'; import './DocumentButtonBar.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { PinProps } from './PinFuncs'; import { TemplateMenu } from './TemplateMenu'; import { TabDocView } from './collections/TabDocView'; import { Colors } from './global/globalEnums'; import { LinkPopup } from './linking/LinkPopup'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; +import { OpenWhere } from './nodes/OpenWhere'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; @observer diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 1e6eb1aeb..ef493fb69 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -33,9 +33,10 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { Colors } from './global/globalEnums'; import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView'; -import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { KeyValueBox } from './nodes/KeyValueBox'; +import { OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; interface DocumentDecorationsProps { @@ -448,7 +449,7 @@ export class DocumentDecorations extends ObservableReactComponent CollectionFreeFormDocumentView.from(docView)?.CollectionFreeFormView?.dragStarting(false, false)); + SelectionManager.Views.forEach(docView => CollectionFreeFormView.from(docView)?.dragStarting(false, false)); }; projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index bf5b51a2d..0d1573ea3 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -4,17 +4,6 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, Opt } from '../../fields/Doc'; -import { InkData, InkTool } from '../../fields/InkField'; -import { BoolCast, NumCast } from '../../fields/Types'; -import MobileInkOverlay from '../../mobile/MobileInkOverlay'; -import { Gestures } from '../../pen-gestures/GestureTypes'; -import { GestureUtils } from '../../pen-gestures/GestureUtils'; -import { MobileInkOverlayContent } from '../../server/Message'; -import { InteractionUtils } from '../util/InteractionUtils'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { Transform } from '../util/Transform'; -import './GestureOverlay.scss'; import { ActiveArrowEnd, ActiveArrowScale, @@ -24,12 +13,24 @@ import { ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, + Doc, + Opt, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, -} from './InkingStroke'; +} from '../../fields/Doc'; +import { InkData, InkTool } from '../../fields/InkField'; +import { NumCast } from '../../fields/Types'; +// import MobileInkOverlay from '../../mobile/MobileInkOverlay'; +import { Gestures } from '../../pen-gestures/GestureTypes'; +import { GestureUtils } from '../../pen-gestures/GestureUtils'; +// import { MobileInkOverlayContent } from '../../server/Message'; +import { InteractionUtils } from '../util/InteractionUtils'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { Transform } from '../util/Transform'; +import './GestureOverlay.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; import { DocumentView } from './nodes/DocumentView'; @@ -49,13 +50,6 @@ export class GestureOverlay extends ObservableReactComponent = undefined; @observable public SavedColor?: string = undefined; @observable public SavedWidth?: number = undefined; @@ -80,7 +74,7 @@ export class GestureOverlay extends ObservableReactComponent(); private _d1: Doc | undefined; @@ -158,7 +152,7 @@ export class GestureOverlay extends ObservableReactComponent 2 && GestureUtils.GestureRecognizer.Recognize([points]); let actionPerformed = false; - if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) { + if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) { switch (result.Name) { case Gestures.Line: case Gestures.Triangle: @@ -502,15 +496,10 @@ export class GestureOverlay extends ObservableReactComponent { - this.showMobileInkOverlay = content.enableOverlay; - }; - render() { return (
- {this.showMobileInkOverlay ? : null} + {/* {this.showMobileInkOverlay ? : null} */} {this.elements}
{}); nudge = (x: number, y: number, label: string) => { - const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge); - nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label); + const nudgeable = SelectionManager.Views.some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge); + nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label); return { stopPropagation: nudgeable, preventDefault: nudgeable }; }; @@ -105,7 +106,7 @@ export class KeyManager { case 'g': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { const selected = SelectionManager.Views; - const cv = selected.reduce((col, dv) => (!col || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), undefined as undefined | CollectionFreeFormView); + const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView); cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs), 'grouping'); } break; @@ -205,7 +206,7 @@ export class KeyManager { switch (keyname) { case 'ƒ': case 'f': - UndoManager.RunInBatch(() => SelectionManager.Views?.[0]?.CollectionFreeFormDocumentView?.float(), 'float'); + UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(SelectionManager.Views?.[0])?.float(), 'float'); break; default: } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 98cf33c41..109a9cad4 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -13,6 +13,7 @@ import { DocumentManager } from '../util/DocumentManager'; import { undoBatch } from '../util/UndoManager'; import { FitOneCurve } from '../util/bezierFit'; import { InkingStroke } from './InkingStroke'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentView } from './nodes/DocumentView'; export class InkStrokeProperties { @@ -380,7 +381,7 @@ export class InkStrokeProperties { }; snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => { - const containingCollection = inkView.CollectionFreeFormView; + const containingCollection = CollectionFreeFormView.from(inkView); const containingDocView = containingCollection?.DocumentView?.(); containingCollection?.childDocs .filter(doc => doc.type === DocumentType.INK) diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index cf609d8f9..884512539 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -30,6 +30,7 @@ import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { CognitiveServices } from '../cognitive_services/CognitiveServices'; import { Docs } from '../documents/Documents'; +import { DocumentType } from '../documents/DocumentTypes'; import { InteractionUtils } from '../util/InteractionUtils'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; @@ -491,67 +492,17 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() ); } } -export function ActiveInkPen(): Doc { - return Doc.UserDoc(); -} -export function ActiveInkColor(): string { - return StrCast(ActiveInkPen()?.activeInkColor, 'black'); -} -export function ActiveFillColor(): string { - return StrCast(ActiveInkPen()?.activeFillColor, ''); -} -export function ActiveIsInkMask(): boolean { - return BoolCast(ActiveInkPen()?.activeIsInkMask, false); -} -export function ActiveInkHideTextLabels(): boolean { - return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); -} -export function ActiveArrowStart(): string { - return StrCast(ActiveInkPen()?.activeArrowStart, ''); -} -export function ActiveArrowEnd(): string { - return StrCast(ActiveInkPen()?.activeArrowEnd, ''); -} -export function ActiveArrowScale(): number { - return NumCast(ActiveInkPen()?.activeArrowScale, 1); -} -export function ActiveDash(): string { - return StrCast(ActiveInkPen()?.activeDash, '0'); -} -export function ActiveInkWidth(): number { - return Number(ActiveInkPen()?.activeInkWidth); -} -export function ActiveInkBezierApprox(): string { - return StrCast(ActiveInkPen()?.activeInkBezier); -} - -export function SetActiveInkWidth(width: string): void { - !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); -} -export function SetActiveBezierApprox(bezier: string): void { - ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier); -} -export function SetActiveInkColor(value: string) { - ActiveInkPen() && (ActiveInkPen().activeInkColor = value); -} -export function SetActiveIsInkMask(value: boolean) { - ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value); -} -export function SetActiveInkHideTextLabels(value: boolean) { - ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value); -} -export function SetActiveFillColor(value: string) { - ActiveInkPen() && (ActiveInkPen().activeFillColor = value); -} -export function SetActiveArrowStart(value: string) { - ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); -} -export function SetActiveArrowEnd(value: string) { - ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); -} -export function SetActiveArrowScale(value: number) { - ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); -} -export function SetActiveDash(dash: string): void { - !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); -} +Docs.Prototypes.TemplateMap.set(DocumentType.INK, { + // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method + layout: { view: InkingStroke, dataField: 'stroke' }, + options: { + acl: '', + systemIcon: 'BsFillPencilFill', // + _layout_nativeDimEditable: true, + _layout_reflowVertical: true, + _layout_reflowHorizontal: true, + layout_hideDecorationTitle: true, // don't show title when selected + _layout_fitWidth: false, + layout_isSvg: true, + }, +}); diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 7bf6bb9e5..020525ef8 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -9,11 +9,10 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc'; +import { CreateLinkToActiveAudio, Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { Cast, NumCast, toList } from '../../fields/Types'; -import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; @@ -27,7 +26,8 @@ import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; import { TabDocView } from './collections/TabDocView'; -import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; interface LightboxViewProps { PanelWidth: number; @@ -88,7 +88,7 @@ export class LightboxView extends ObservableReactComponent { savedKeys.forEach(key => { this._savedState[key] = Doc.Get(doc, key, true); }); - const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); + const l = CreateLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); this._history.push({ doc, target }); @@ -136,7 +136,7 @@ export class LightboxView extends ObservableReactComponent { const target = (this._docTarget = this._future.pop()); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { - const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); + const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target }); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d3dd51ac6..aec553baa 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -11,7 +11,7 @@ import * as React from 'react'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { DocCast, StrCast, toList } from '../../fields/Types'; import { DocServer } from '../DocServer'; @@ -55,28 +55,28 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionMenu } from './collections/CollectionMenu'; import { TabDocView } from './collections/TabDocView'; import './collections/TreeView.scss'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; import { AudioBox } from './nodes/AudioBox'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from './nodes/DocumentView'; import { ImageEditorData as ImageEditor } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu'; import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu'; +import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; -import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; import GenerativeFill from './nodes/generativeFill/GenerativeFill'; import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { TopBar } from './topbar/TopBar'; -import { CollectionFreeFormView } from './collections/collectionFreeForm'; const _global = (window /* browser */ || global) /* node */ as any; const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @@ -242,7 +242,7 @@ export class MainView extends ObservableReactComponent<{}> { window.addEventListener('paste', KeyManager.Instance.paste as any); document.addEventListener('dash', (e: any) => { // event used by chrome plugin to tell Dash which document to focus on - const id = FormattedTextBox.GetDocFromUrl(e.detail); + const id = GetDocFromUrl(e.detail); DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null)); }); document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener); diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index ea73a53a9..06cae6d04 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -4,16 +4,16 @@ import * as React from 'react'; import { Doc, Opt } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; +import { FollowLinkScript } from '../../fields/ScriptField'; import { NumCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; import { unimplementedFunction, Utils } from '../../Utils'; -import { Docs, DocUtils } from '../documents/Documents'; +import { Docs } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; import { DragManager } from '../util/DragManager'; -import { FollowLinkScript } from '../util/LinkFollower'; import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; import './MarqueeAnnotator.scss'; import { DocumentView } from './nodes/DocumentView'; -import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ObservableReactComponent } from './ObservableReactComponent'; import { AnchorMenu } from './pdf/AnchorMenu'; @@ -197,7 +197,7 @@ export class MarqueeAnnotator extends ObservableReactComponent { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - FormattedTextBox.SetSelectOnLoad(target); + Doc.SetSelectOnLoad(target); return target; }; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index fddb40624..034ade50b 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { lightOrDark, returnFalse } from '../../ClientUtils'; import { Doc, Opt } from '../../fields/Doc'; -import { DocUtils, Docs, DocumentOptions } from '../documents/Documents'; +import { Docs, DocumentOptions } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; import { ImageUtils } from '../util/Import & Export/ImageUtils'; import { Transform } from '../util/Transform'; import { UndoManager, undoBatch } from '../util/UndoManager'; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index d83fea2a5..cbbf48c75 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -20,8 +20,8 @@ import { DocData } from '../../fields/DocSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, ScriptCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; +import { DocUtils } from '../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; -import { DocUtils } from '../documents/Documents'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; @@ -30,7 +30,8 @@ import { undoBatch, undoable } from '../util/UndoManager'; import { InkingStroke } from './InkingStroke'; import './PropertiesButtons.scss'; import { Colors } from './global/globalEnums'; -import { DocumentView, OpenWhere } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { OpenWhere } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @observer diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index e079b5cde..a806995f2 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -12,7 +12,7 @@ import { SettingsManager } from '../util/SettingsManager'; import './PropertiesDocBacklinksSelector.scss'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { LinkMenu } from './linking/LinkMenu'; -import { OpenWhere } from './nodes/DocumentView'; +import { OpenWhere } from './nodes/OpenWhere'; type PropertiesDocBacklinksSelectorProps = { Document: Doc; diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 2d04f2fe3..722b1f90a 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -11,7 +11,8 @@ import { DocFocusOrOpen } from '../util/DocumentManager'; import { ObservableReactComponent } from './ObservableReactComponent'; import './PropertiesDocContextSelector.scss'; import { CollectionDockingView } from './collections/CollectionDockingView'; -import { DocumentView, OpenWhere } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { OpenWhere } from './nodes/OpenWhere'; type PropertiesDocContextSelectorProps = { DocView?: DocumentView; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index f4ded8367..ff5dcd1b8 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -14,7 +14,7 @@ import { ColorResult, SketchPicker } from 'react-color'; import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, Field, FieldType, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; +import { Doc, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; @@ -41,9 +41,10 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import { PropertiesSection } from './PropertiesSection'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; -import { DocumentView, OpenWhere } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; +import { OpenWhere } from './nodes/OpenWhere'; import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; const _global = (window /* browser */ || global) /* node */ as any; diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 6195dcde8..3648e613d 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -12,7 +12,8 @@ import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { DocCast, NumCast, StrCast } from '../../fields/Types'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; -import { DocUtils, Docs } from '../documents/Documents'; +import { Docs } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; import { LinkManager } from '../util/LinkManager'; import { SearchUtil } from '../util/SearchUtil'; import { Transform } from '../util/Transform'; @@ -83,7 +84,7 @@ export class SidebarAnnos extends ObservableReactComponent void) => { this._flush = this._flush ?? UndoManager.StartBatch('golden layout drag'); - const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) }; + const config = dragDocs.length === 1 ? DashboardView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => DashboardView.makeDocumentConfig(doc)) }; const dragSource = CollectionDockingView.Instance?._goldenLayout.createDragSource(document.createElement('div'), config); this.tabDragStart(dragSource, finishDrag); dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }); @@ -139,7 +125,7 @@ export class CollectionDockingView extends CollectionSubView() { public static ReplaceTab(document: Doc, mods: OpenWhereMod, stack: any, panelName: string, addToSplit?: boolean, keyValue?: boolean): boolean { const instance = CollectionDockingView.Instance; if (!instance) return false; - const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue); + const newConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue); if (!panelName && stack) { const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config); const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout); @@ -180,7 +166,7 @@ export class CollectionDockingView extends CollectionSubView() { const instance = CollectionDockingView.Instance; const glayRoot = instance._goldenLayout.root; if (!instance) return false; - const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue); + const docContentConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue); CollectionDockingView.Instance._flush = CollectionDockingView.Instance._flush ?? UndoManager.StartBatch('Add Split'); setTimeout(CollectionDockingView.Instance.endUndoBatch, 100); @@ -461,7 +447,7 @@ export class CollectionDockingView extends CollectionSubView() { if (content) { const _width = DivWidth(content); const _height = DivHeight(content); - return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => { + return UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => { const proto = this.dataDoc; // Cast(img.proto, Doc, null)!; proto.thumb_nativeWidth = _width; proto.thumb_nativeHeight = _height; diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 8803f6f79..9a6f1e2eb 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -166,7 +166,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent { const key = this._props.pivotField; doc[key] = this.getValue(this._props.heading); - FormattedTextBox.SetSelectOnLoad(doc); + Doc.SetSelectOnLoad(doc); return this._props.addDocument?.(doc); }, this._props.addDocument, @@ -195,7 +197,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent { - const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); + const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); if (created) { if (this._props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this._props.Document); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 6ccbd6208..e02570d3e 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -8,11 +8,11 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { ScriptField } from '../../../fields/ScriptField'; import { NumCast, StrCast, toList } from '../../../fields/Types'; import { emptyFunction } from '../../../Utils'; -import { DocUtils } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; import { dropActionType } from '../../util/DropActionTypes'; import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { OpenWhere } from '../nodes/DocumentView'; +import { OpenWhere } from '../nodes/OpenWhere'; import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import './CollectionPileView.scss'; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7adf44a5c..50a66aa41 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -13,7 +13,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { ComputedField, ScriptField } from '../../../fields/ScriptField'; +import { ComputedField, FollowLinkScript, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { emptyFunction, formatTime } from '../../../Utils'; @@ -21,21 +21,23 @@ import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; -import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower'; +import { IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { CollectionSubView } from './CollectionSubView'; import { LightboxView } from '../LightboxView'; import { AudioWaveform } from '../nodes/audio/AudioWaveform'; -import { DocumentView, OpenWhere } from '../nodes/DocumentView'; -import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FocusFuncType, StyleProviderFuncType } from '../nodes/FieldView'; +import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { LabelBox } from '../nodes/LabelBox'; +import { OpenWhere } from '../nodes/OpenWhere'; import { VideoBox } from '../nodes/VideoBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackedTimeline.scss'; +import { CollectionSubView } from './CollectionSubView'; export type CollectionStackedTimelineProps = { Play: () => void; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ad9960989..8ae0f2832 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -15,8 +15,9 @@ import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/DocUtils'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { SettingsManager } from '../../util/SettingsManager'; @@ -28,8 +29,8 @@ import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; -import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { FieldViewProps } from '../nodes/FieldView'; +import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { StyleProp } from '../StyleProvider'; import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; @@ -296,7 +297,7 @@ export class CollectionStackingView extends CollectionSubView (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0); const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; - FormattedTextBox.SetSelectOnLoad(newDoc); + Doc.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; return this._props.addDocument?.(newDoc) || false; }; @@ -240,7 +242,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< const height = this._ele ? DivHeight(this._ele) : 0; DocUtils.addDocumentCreatorMenuItems( doc => { - FormattedTextBox.SetSelectOnLoad(doc); + Doc.SetSelectOnLoad(doc); return this._props.addDocument?.(doc); }, this._props.addDocument, @@ -255,7 +257,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< docItems.push({ description: ':' + fieldKey, event: () => { - const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); + const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); if (created) { if (this._props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this._props.Document); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index f3dedaedf..7c08aedb1 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import * as rp from 'request-promise'; import { ClientUtils, returnFalse } from '../../../ClientUtils'; import CursorField from '../../../fields/CursorField'; -import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc'; import { AclPrivate } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -15,8 +15,9 @@ import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; +import { DocUtils } from '../../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; -import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents'; +import { Docs, DocumentOptions } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; @@ -26,7 +27,6 @@ import { UndoManager, undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldViewProps } from '../nodes/FieldView'; import { LoadingBox } from '../nodes/LoadingBox'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; export interface CollectionViewProps extends React.PropsWithChildren { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) @@ -317,10 +317,10 @@ export function CollectionSubView() { const addDocument = (doc: Doc | Doc[]) => this.addDocument(doc); if (html) { - if (FormattedTextBox.IsFragment(html)) { - const href = FormattedTextBox.GetHref(html); + if (RTFIsFragment(html)) { + const href = GetHrefFromHTML(html); if (href) { - const docId = FormattedTextBox.GetDocFromUrl(href); + const docId = GetDocFromUrl(href); if (docId) { // prosemirror text containing link to dash document DocServer.GetRefField(docId).then(f => { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 365b5acfd..d015e73ad 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -12,7 +12,8 @@ import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, Utils } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; @@ -189,7 +190,7 @@ export class CollectionTreeView extends CollectionSubView 0 && prev) { - FormattedTextBox.SetSelectOnLoad(prev); + Doc.SetSelectOnLoad(prev); DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); } return true; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index d6cbe0dab..7cadd072b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -7,14 +7,15 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { ObjectField } from '../../../fields/ObjectField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { CollectionViewType } from '../../documents/DocumentTypes'; -import { DocUtils } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; -import { OpenWhere } from '../nodes/DocumentView'; import { FieldView } from '../nodes/FieldView'; +import { OpenWhere } from '../nodes/OpenWhere'; import { CollectionCalendarView } from './CollectionCalendarView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; @@ -234,3 +235,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const fieldKey = Doc.LayoutFieldKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); - FormattedTextBox.SetSelectOnLoad(child); + Doc.SetSelectOnLoad(child); TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeView_Open = true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 653a01a04..f55d5a23f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,7 +9,7 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc'; +import { ActiveInkWidth, Doc, DocListCast, Field, FieldType, Opt, SetActiveInkColor, SetActiveInkWidth } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField'; @@ -23,8 +23,9 @@ import { TraceMobx } from '../../../../fields/util'; import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { DocUtils } from '../../../documents/DocUtils'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; @@ -39,13 +40,15 @@ import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; import { GestureOverlay } from '../../GestureOverlay'; -import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; +import { InkingStroke } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; -import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; -import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; +import { DocumentView } from '../../nodes/DocumentView'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; +import { OpenWhere } from '../../nodes/OpenWhere'; import { PinDocView, PinProps } from '../../PinFuncs'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; @@ -285,7 +288,7 @@ export class CollectionFreeFormView extends CollectionSubView this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this._props.isAnyChildContentActive(); addLiveTextBox = (newDoc: Doc) => { - FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed + Doc.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newDoc); }; selectDocuments = (docs: Doc[]) => { @@ -1052,7 +1055,7 @@ export class CollectionFreeFormView extends CollectionSubView { const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 }); - FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed + Doc.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed Doc.AddDocToList(this.Document, this._props.fieldKey, text); this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY)))); }) diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index ee79812a1..406a7d626 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -13,7 +13,8 @@ import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents'; +import { DocUtils } from '../../../documents/DocUtils'; +import { Docs, DocumentOptions, FInfo } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; @@ -26,7 +27,8 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; -import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 61afe08cf..32b48e4d1 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -5,8 +5,8 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg'; import { FaExternalLinkAlt } from 'react-icons/fa'; -import { emptyFunction } from '../../../../Utils'; import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Doc } from '../../../../fields/Doc'; import { BoolCast } from '../../../../fields/Types'; import { DragManager } from '../../../util/DragManager'; @@ -15,8 +15,8 @@ import { Transform } from '../../../util/Transform'; import { undoable } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { OpenWhere } from '../../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; +import { OpenWhere } from '../../nodes/OpenWhere'; import { CollectionSchemaView } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 231bac541..d17d4ff7c 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -1,7 +1,7 @@ import { Colors } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { aggregateBounds } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; +import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, Doc, Opt, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; @@ -13,7 +13,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; -import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; +import { InkingStroke } from '../InkingStroke'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; @@ -111,7 +111,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { if (NumCast(selected?.Document.z) >= 1) return true; return false; } - selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); + selected ? CollectionFreeFormDocumentView.from(selected)?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); return undefined; }); diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 278d30d6a..df3accb0d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -20,8 +20,9 @@ import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { LinkInfo } from '../nodes/LinkDocPreview'; +import { OpenWhere } from '../nodes/OpenWhere'; import './LinkMenuItem.scss'; interface LinkMenuItemProps { diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx index bce2b296f..72d81e9eb 100644 --- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx +++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx @@ -5,7 +5,7 @@ import { InkTool } from '../../../../fields/InkField'; import { SelectionManager } from '../../../util/SelectionManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { OpenWhereMod } from '../../nodes/DocumentView'; +import { OpenWhereMod } from '../../nodes/OpenWhere'; import { NewLightboxView } from '../NewLightboxView'; import './ButtonMenu.scss'; import { IButtonMenu } from './utils'; diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx index 6616abad1..dc6bf3e9c 100644 --- a/src/client/views/newlightbox/NewLightboxView.tsx +++ b/src/client/views/newlightbox/NewLightboxView.tsx @@ -6,10 +6,9 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; -import { DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { LinkManager } from '../../util/LinkManager'; import { SelectionManager } from '../../util/SelectionManager'; @@ -20,7 +19,8 @@ import { LightboxView } from '../LightboxView'; import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { TabDocView } from '../collections/TabDocView'; -import { DocumentView, OpenWhere } from '../nodes/DocumentView'; +import { DocumentView } from '../nodes/DocumentView'; +import { OpenWhere } from '../nodes/OpenWhere'; import { ExploreView } from './ExploreView'; import { IBounds, emptyBounds } from './ExploreView/utils'; import { NewLightboxHeader } from './Header'; @@ -108,7 +108,7 @@ export class NewLightboxView extends React.Component { Doc.ActiveTool = InkTool.None; SnappingManager.SetExploreMode(false); } else { - const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); + const l = CreateLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); // TabDocView.PinDoc(doc, { hidePresBox: true }); @@ -150,7 +150,7 @@ export class NewLightboxView extends React.Component { const target = (NewLightboxView._docTarget = this._future?.pop()); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { - const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); + const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target }); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 1f618135f..4697491e0 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -13,7 +13,9 @@ import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DateCast, NumCast } from '../../../fields/Types'; import { AudioField, nullAudio } from '../../../fields/URLField'; import { formatTime } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/DocUtils'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; @@ -24,8 +26,8 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import './AudioBox.scss'; -import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; +import { OpenWhere } from './OpenWhere'; /** * AudioBox @@ -761,3 +763,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.AUDIO, { + layout: { view: AudioBox, dataField: 'data' }, + options: { acl: '', _height: 100, _layout_fitWidth: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' }, +}); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9c4d748bd..691d07e31 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -11,14 +11,16 @@ import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import './CollectionFreeFormDocumentView.scss'; -import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; +import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldViewProps } from './FieldView'; +import { OpenWhere } from './OpenWhere'; /// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface @@ -48,7 +50,7 @@ export class CollectionFreeFormDocumentView extends DocComponent() ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.COMPARISON, { + data: '', + layout: { view: ComparisonBox, dataField: 'data' }, + options: { + acl: '', + backgroundColor: 'gray', + dropAction: dropActionType.move, + waitForDoubleClickToClick: 'always', + _layout_reflowHorizontal: true, + _layout_reflowVertical: true, + _layout_nativeDimEditable: true, + systemIcon: 'BsLayoutSplit', + }, +}); diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 113a857c3..ecfdcc229 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -13,22 +13,26 @@ import { List } from '../../../../fields/List'; import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; -import { DocUtils, Docs } from '../../../documents/Documents'; +import { DocUtils } from '../../../documents/DocUtils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { ContextMenu } from '../../ContextMenu'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { MarqueeAnnotator } from '../../MarqueeAnnotator'; +import { PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup'; import { DocumentView } from '../DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { LineChart } from './components/LineChart'; import { PieChart } from './components/PieChart'; import { TableBox } from './components/TableBox'; -import { ContextMenu } from '../../ContextMenu'; export enum DataVizView { TABLE = 'table', @@ -522,3 +526,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() im ); } } +Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, { + layout: { view: DataVizBox, dataField: 'data' }, + options: { + acl: '', + dataViz_title: '', + dataViz_line: '', + dataViz_pie: '', + dataViz_histogram: '', + dataViz: 'table', + _layout_fitWidth: true, + _layout_reflowHorizontal: true, + _layout_reflowVertical: true, + _layout_nativeDimEditable: true, + }, +}); diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 9b4e36509..977899589 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -10,13 +10,13 @@ import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; -import { DocUtils } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; import { UndoManager, undoBatch } from '../../util/UndoManager'; -import { PinProps } from '../DocComponent'; import { ObservableReactComponent } from '../ObservableReactComponent'; +import { PinProps } from '../PinFuncs'; import './DocumentLinksButton.scss'; import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a603de10b..e8d1e582e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -16,20 +16,20 @@ import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { PrefetchProxy } from '../../../fields/Proxy'; import { listSpec } from '../../../fields/Schema'; -import { ScriptField } from '../../../fields/ScriptField'; +import { FollowLinkScript, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; +import { DocUtils } from '../../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; -import { DocUtils, Docs } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; -import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SearchUtil } from '../../util/SearchUtil'; @@ -48,38 +48,14 @@ import { AudioAnnoState, StyleProp } from '../StyleProvider'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; -import { FieldViewProps, FieldViewSharedProps, FocusViewOptions } from './FieldView'; +import { FieldViewProps, FieldViewSharedProps } from './FieldView'; +import { FocusViewOptions } from './FocusViewOptions'; import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; +import { OpenWhere } from './OpenWhere'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; -export enum OpenWhereMod { - none = '', - left = 'left', - right = 'right', - top = 'top', - bottom = 'bottom', - keyvalue = 'keyValue', -} -export enum OpenWhere { - lightbox = 'lightbox', - add = 'add', - addLeft = 'add:left', - addRight = 'add:right', - addBottom = 'add:bottom', - close = 'close', - toggle = 'toggle', - toggleRight = 'toggle:right', - replace = 'replace', - replaceRight = 'replace:right', - replaceLeft = 'replace:left', - inParent = 'inParent', - inParentFromScreen = 'inParentFromScreen', - overlay = 'overlay', - addRightKeyvalue = 'add:right:keyValue', -} - export interface DocumentViewProps extends FieldViewSharedProps { hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected @@ -292,7 +268,8 @@ export class DocumentViewInternal extends DocComponent docView.props.dragEnding?.(); + dragData.dragStarting = () => docView.props.dragStarting?.(); dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction); (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData); DragManager.StartDocumentDrag( diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 9c216cba4..32d08fbe7 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -3,10 +3,12 @@ import { action, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DivHeight, DivWidth } from '../../../ClientUtils'; -import { Id } from '../../../fields/FieldSymbols'; +import { Doc } from '../../../fields/Doc'; import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DocUtils, Docs } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { LightboxView } from '../LightboxView'; @@ -19,7 +21,6 @@ export class EquationBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } - public static SelectOnLoad: string = ''; _ref: React.RefObject = React.createRef(); constructor(props: FieldViewProps) { @@ -29,12 +30,12 @@ export class EquationBox extends ViewBoxBaseComponent() { componentDidMount() { this._props.setContentViewBox?.(this); - if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) { + if (Doc.SelectOnLoad === this.Document && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) { this._props.select(false); this._ref.current!.mathField.focus(); this.dataDoc.text === 'x' && this._ref.current!.mathField.select(); - EquationBox.SelectOnLoad = ''; + Doc.SetSelectOnLoad(undefined); } reaction( () => StrCast(this.dataDoc.text), @@ -69,7 +70,7 @@ export class EquationBox extends ViewBoxBaseComponent() { x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10, }); - EquationBox.SelectOnLoad = nextEq[Id]; + Doc.SetSelectOnLoad(nextEq); this._props.addDocument?.(nextEq); e.stopPropagation(); } @@ -130,3 +131,8 @@ export class EquationBox extends ViewBoxBaseComponent() { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, { + layout: { view: EquationBox, dataField: 'text' }, + options: { acl: '', fontSize: '14px', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript +}); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ab0850790..c6c77d8d2 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -10,29 +10,12 @@ import { ScriptField } from '../../../fields/ScriptField'; import { WebField } from '../../../fields/URLField'; import { dropActionType } from '../../util/DropActionTypes'; import { Transform } from '../../util/Transform'; -import { PinProps, ViewBoxInterface } from '../DocComponent'; -import { DocumentView, OpenWhere } from './DocumentView'; +import { ViewBoxInterface } from '../DocComponent'; +import { PinProps } from '../PinFuncs'; +import { DocumentView } from './DocumentView'; +import { FocusViewOptions } from './FocusViewOptions'; +import { OpenWhere } from './OpenWhere'; -export interface FocusViewOptions { - willPan?: boolean; // determines whether to pan to target document - willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document - zoomScale?: number; // percent of containing frame to zoom into document - zoomTime?: number; - didMove?: boolean; // whether a document was changed during the showDocument process - docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy - instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom) - preview?: boolean; // whether changes should be previewed by the componentView or written to the document - effect?: Doc; // animation effect for focus // bcz: needs to be changed to something more generic than a Doc - noSelect?: boolean; // whether target should be selected after focusing - playAudio?: boolean; // whether to play audio annotation on focus - playMedia?: boolean; // whether to play start target videos - openLocation?: OpenWhere; // where to open a missing document - zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections - toggleTarget?: boolean; // whether to toggle target on and off - easeFunc?: 'linear' | 'ease'; // transition method for scrolling - pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode) - contextPath?: Doc[]; // path of inner documents that will also be focused -} export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt; // eslint-disable-next-line no-use-before-define export type StyleProviderFuncType = (doc: Opt, props: Opt, property: string) => any; diff --git a/src/client/views/nodes/FocusViewOptions.ts b/src/client/views/nodes/FocusViewOptions.ts new file mode 100644 index 000000000..bb0d2b03c --- /dev/null +++ b/src/client/views/nodes/FocusViewOptions.ts @@ -0,0 +1,24 @@ +import { Doc } from '../../../fields/Doc'; +import { Transform } from '../../util/Transform'; +import { OpenWhere } from './OpenWhere'; + +export interface FocusViewOptions { + willPan?: boolean; // determines whether to pan to target document + willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document + zoomScale?: number; // percent of containing frame to zoom into document + zoomTime?: number; + didMove?: boolean; // whether a document was changed during the showDocument process + docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy + instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom) + preview?: boolean; // whether changes should be previewed by the componentView or written to the document + effect?: Doc; // animation effect for focus // bcz: needs to be changed to something more generic than a Doc + noSelect?: boolean; // whether target should be selected after focusing + playAudio?: boolean; // whether to play audio annotation on focus + playMedia?: boolean; // whether to play start target videos + openLocation?: OpenWhere; // where to open a missing document + zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections + toggleTarget?: boolean; // whether to toggle target on and off + easeFunc?: 'linear' | 'ease'; // transition method for scrolling + pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode) + contextPath?: Doc[]; // path of inner documents that will also be focused +} diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 1b2aefbe2..d83690cdd 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -9,6 +9,7 @@ import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUt import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction } from '../../../../Utils'; +import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -18,8 +19,8 @@ import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { SelectedDocView } from '../../selectedDoc'; import { StyleProp } from '../../StyleProvider'; -import { OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; +import { OpenWhere } from '../OpenWhere'; import './FontIconBox.scss'; import TrailsIcon from './TrailsIcon'; @@ -51,15 +52,6 @@ export class FontIconBox extends ViewBoxBaseComponent() { super(props); makeObservable(this); } - // - // This controls whether fontIconButtons will display labels under their icons or not - // - public static get ShowIconLabels() { - return BoolCast(Doc.UserDoc()._showLabel); - } - public static set ShowIconLabels(show: boolean) { - Doc.UserDoc()._showLabel = show; - } @observable noTooltip = false; @@ -397,3 +389,8 @@ export class FontIconBox extends ViewBoxBaseComponent() { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.FONTICON, { + layout: { view: FontIconBox, dataField: 'icon' }, + options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 }, +}); diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 31faa7ac3..f32d39aaf 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -7,12 +7,14 @@ import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { Cast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DocUtils, Docs } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import { PinProps, PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { FieldView, FieldViewProps } from './FieldView'; @observer @@ -138,3 +140,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.FUNCPLOT, { + layout: { view: FunctionPlotBox, dataField: 'data' }, + options: { acl: '', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true }, +}); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 8bd5ab03d..d317f46bb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -15,8 +15,9 @@ import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/DocUtils'; import { Networking } from '../../Network'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; @@ -30,9 +31,10 @@ import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProvider'; -import { OpenWhere } from './DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; +import { FieldView, FieldViewProps } from './FieldView'; +import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; +import { OpenWhere } from './OpenWhere'; export class ImageEditorData { // eslint-disable-next-line no-use-before-define @@ -546,3 +548,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl input.click(); }; } + +Docs.Prototypes.TemplateMap.set(DocumentType.IMG, { + layout: { view: ImageBox, dataField: 'data' }, + options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' }, +}); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index b8296ce51..46bb16e50 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -3,12 +3,13 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnAlways, returnTrue } from '../../../ClientUtils'; -import { Doc, Field, FieldType, FieldResult } from '../../../fields/Doc'; +import { Doc, Field, FieldResult, FieldType } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { DocCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; +import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { CompiledScript } from '../../util/Scripting'; @@ -17,11 +18,11 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentIconContainer } from './DocumentIcon'; -import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './KeyValueBox.scss'; import { KeyValuePair } from './KeyValuePair'; +import { OpenWhere } from './OpenWhere'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; export type KVPScript = { @@ -339,3 +340,8 @@ export class KeyValueBox extends ObservableReactComponent { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.KVP, { + layout: { view: KeyValueBox, dataField: 'data' }, + options: { acl: '', _layout_fitWidth: true, _height: 150 }, +}); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index a9aa017a1..878b0e54c 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -14,10 +14,11 @@ import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider } from '../StyleProvider'; -import { OpenWhere, returnEmptyDocViewList } from './DocumentView'; +import { returnEmptyDocViewList } from './DocumentView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; +import { OpenWhere } from './OpenWhere'; // Represents one row in a key value plane diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 89270652c..4dec3506c 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -5,13 +5,14 @@ import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; -import { PinProps, PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; @@ -203,3 +204,12 @@ export class LabelBox extends ViewBoxBaseComponent() { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.LABEL, { + layout: { view: LabelBox, dataField: 'title' }, + options: { acl: '', _singleLine: true, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, +}); +Docs.Prototypes.TemplateMap.set(DocumentType.BUTTON, { + layout: { view: LabelBox, dataField: 'title' }, + options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, +}); diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index f01905ee1..559b1fcae 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -7,9 +7,12 @@ import { DashColor, lightOrDark, returnFalse } from '../../../ClientUtils'; import { FieldResult } from '../../../fields/Doc'; import { DocCss, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxBaseComponent } from '../DocComponent'; @@ -263,3 +266,18 @@ export class LinkBox extends ViewBoxBaseComponent() { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.LINK, { + layout: { view: LinkBox, dataField: 'link' }, + options: { + acl: '', + childDontRegisterViews: true, + layout_hideLinkAnchors: true, + _height: 1, + _width: 1, + link: '', + link_description: '', + color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area + _dropPropertiesToRemove: new List(['onClick']), + }, +}); diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index a9cfe6c0e..0936acc15 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -19,9 +19,10 @@ import { SearchUtil } from '../../util/SearchUtil'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DocumentView, OpenWhere } from './DocumentView'; +import { DocumentView } from './DocumentView'; import { StyleProviderFuncType } from './FieldView'; import './LinkDocPreview.scss'; +import { OpenWhere } from './OpenWhere'; interface LinkDocPreviewProps { linkDoc?: Doc; diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 501831bca..aa89398f3 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -10,6 +10,8 @@ import { DocumentManager } from '../../util/DocumentManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; import './LoadingBox.scss'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; /** * LoadingBox Class represents a placeholder doc for documents that are currently @@ -89,3 +91,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { ); } } +Docs.Prototypes.TemplateMap.set(DocumentType.LOADING, { + layout: { view: LoadingBox, dataField: '' }, + options: { acl: '', _layout_fitWidth: true, _layout_nativeDimEditable: true }, +}); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 60dad314f..50831f8ea 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -18,20 +18,21 @@ import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types'; +import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; -import { DocUtils, Docs } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { LinkManager } from '../../../util/LinkManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; -import { PinProps, PinDocView } from '../../PinFuncs'; +import { PinDocView, PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; -import { FormattedTextBox } from '../formattedText/FormattedTextBox'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility'; import { MapAnchorMenu } from './MapAnchorMenu'; @@ -355,7 +356,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - FormattedTextBox.SetSelectOnLoad(target); + Doc.SetSelectOnLoad(target); return target; }; const docView = this.DocumentView?.(); @@ -1360,3 +1361,8 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.MAP, { + layout: { view: MapBox, dataField: 'data' }, + options: { acl: '', map: '', _height: 600, _width: 800, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' }, +}); diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index 6ccbbbe1c..c69cd8e89 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -32,7 +32,7 @@ // addNoteClick = (e: React.PointerEvent) => { // setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => { // const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true }); -// FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed +// Doc.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed // Doc.AddDocToList(this.props.place, 'data', newDoc); // this._stack?.scrollToBottom(); // e.stopPropagation(); diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx index 8ebc90157..f3dc44755 100644 --- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx +++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer'; +import { Docs } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; /** * Map Pushpin doc class @@ -28,3 +30,8 @@ export class MapPushpinBox extends ViewBoxBaseComponent() { return
; } } + +Docs.Prototypes.TemplateMap.set(DocumentType.PUSHPIN, { + layout: { view: MapPushpinBox, dataField: 'data' }, + options: { acl: '' }, +}); diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index d899fcb9a..3b4ffd4bd 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -10,23 +10,24 @@ import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; import { DocCss, Highlight } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types'; +import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; -import { DocUtils, Docs } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { LinkManager } from '../../../util/LinkManager'; import { Transform } from '../../../util/Transform'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; -import { PinProps, PinDocView } from '../../PinFuncs'; +import { PinDocView, PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; import { MapAnchorMenu } from '../MapBox/MapAnchorMenu'; import '../MapBox/MapBox.scss'; -import { FormattedTextBox } from '../formattedText/FormattedTextBox'; /** * MapBox architecture: @@ -237,7 +238,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - FormattedTextBox.SetSelectOnLoad(target); + Doc.SetSelectOnLoad(target); return target; }; const docView = this.DocumentView?.(); diff --git a/src/client/views/nodes/OpenWhere.ts b/src/client/views/nodes/OpenWhere.ts new file mode 100644 index 000000000..e2a5f1f2a --- /dev/null +++ b/src/client/views/nodes/OpenWhere.ts @@ -0,0 +1,25 @@ +export enum OpenWhereMod { + none = '', + left = 'left', + right = 'right', + top = 'top', + bottom = 'bottom', + keyvalue = 'keyValue', +} +export enum OpenWhere { + lightbox = 'lightbox', + add = 'add', + addLeft = 'add:left', + addRight = 'add:right', + addBottom = 'add:bottom', + close = 'close', + toggle = 'toggle', + toggleRight = 'toggle:right', + replace = 'replace', + replaceRight = 'replace:right', + replaceLeft = 'replace:left', + inParent = 'inParent', + inParentFromScreen = 'inParentFromScreen', + overlay = 'overlay', + addRightKeyvalue = 'add:right:keyValue', +} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index fbf5e018c..5d1874aca 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -16,8 +16,9 @@ import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../ import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/DocUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { KeyCodes } from '../../util/KeyCodes'; import { SelectionManager } from '../../util/SelectionManager'; @@ -31,9 +32,11 @@ import { Colors } from '../global/globalEnums'; import { PDFViewer } from '../pdf/PDFViewer'; import { PinDocView, PinProps } from '../PinFuncs'; import { SidebarAnnos } from '../SidebarAnnos'; -import { DocumentView, OpenWhere } from './DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; +import { DocumentView } from './DocumentView'; +import { FieldView, FieldViewProps } from './FieldView'; +import { FocusViewOptions } from './FocusViewOptions'; import { ImageBox } from './ImageBox'; +import { OpenWhere } from './OpenWhere'; import './PDFBox.scss'; import { CreateImage } from './WebBoxRenderer'; @@ -665,3 +668,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent() implem return pdfView ?? this.renderTitleBox; } } + +Docs.Prototypes.TemplateMap.set(DocumentType.PDF, { + layout: { view: PDFBox, dataField: 'data' }, + options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' }, +}); diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index ae674d604..f88eb3bca 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -1,3 +1,11 @@ +/* eslint-disable camelcase */ +/* eslint-disable jsx-a11y/control-has-associated-label */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable react/no-array-index-key */ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable no-return-assign */ import ArrowLeftIcon from '@mui/icons-material/ArrowLeft'; import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import PauseIcon from '@mui/icons-material/Pause'; @@ -13,13 +21,15 @@ import { NumListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, StrCast } from '../../../../fields/Types'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; -import { FieldView, FieldViewProps } from './../FieldView'; +import { FieldView, FieldViewProps } from '../FieldView'; import './PhysicsSimulationBox.scss'; import InputField from './PhysicsSimulationInputField'; import questions from './PhysicsSimulationQuestions.json'; import tutorials from './PhysicsSimulationTutorial.json'; import Wall from './PhysicsSimulationWall'; import Weight from './PhysicsSimulationWeight'; +import { Docs } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; interface IWallProps { length: number; @@ -204,7 +214,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent) { super.componentDidUpdate(prevProps); - if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax != this._props.PanelHeight()) { + if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax !== this._props.PanelHeight()) { this.xMax = this._props.PanelWidth() * 0.6; this.yMax = this._props.PanelHeight(); this.setupSimulation(); @@ -219,16 +229,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { - const simulationType = this.simulationType; + const { simulationType } = this; const mode = this.simulationMode; this.dataDoc.simulation_paused = true; - if (simulationType != 'Circular Motion') { + if (simulationType !== 'Circular Motion') { this.dataDoc.mass1_velocityXstart = 0; this.dataDoc.mass1_velocityYstart = 0; this.dataDoc.mass1_velocityX = 0; this.dataDoc.mass1_velocityY = 0; } - if (mode == 'Freeform') { + if (mode === 'Freeform') { this.dataDoc.simulation_showForceMagnitudes = true; // prettier-ignore switch (simulationType) { @@ -247,9 +257,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { let theta = this.wedgeAngle; - let index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45'); + const index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45'); if (index >= 0) { theta = NumListCast(this.dataDoc.questionVariables)[index]; } @@ -467,26 +480,26 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { let error: boolean = false; - let epsilon: number = 0.01; + const epsilon: number = 0.01; if (this.selectedQuestion) { for (let i = 0; i < this.selectedQuestion.answerParts.length; i++) { - if (this.selectedQuestion.answerParts[i] == 'force of gravity') { + if (this.selectedQuestion.answerParts[i] === 'force of gravity') { if (Math.abs(NumCast(this.dataDoc.review_GravityMagnitude) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'angle of gravity') { + } else if (this.selectedQuestion.answerParts[i] === 'angle of gravity') { if (Math.abs(NumCast(this.dataDoc.review_GravityAngle) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'normal force') { + } else if (this.selectedQuestion.answerParts[i] === 'normal force') { if (Math.abs(NumCast(this.dataDoc.review_NormalMagnitude) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'angle of normal force') { + } else if (this.selectedQuestion.answerParts[i] === 'angle of normal force') { if (Math.abs(NumCast(this.dataDoc.review_NormalAngle) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'force of static friction') { + } else if (this.selectedQuestion.answerParts[i] === 'force of static friction') { if (Math.abs(NumCast(this.dataDoc.review_StaticMagnitude) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'angle of static friction') { + } else if (this.selectedQuestion.answerParts[i] === 'angle of static friction') { if (Math.abs(NumCast(this.dataDoc.review_StaticAngle) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'coefficient of static friction') { + } else if (this.selectedQuestion.answerParts[i] === 'coefficient of static friction') { if (Math.abs(NumCast(this.dataDoc.coefficientOfStaticFriction) - this.selectedSolutions[i]) > epsilon) { error = true; } - } else if (this.selectedQuestion.answerParts[i] == 'wedge angle') { + } else if (this.selectedQuestion.answerParts[i] === 'wedge angle') { if (Math.abs(this.wedgeAngle - this.selectedSolutions[i]) > epsilon) { error = true; } @@ -539,7 +552,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent (this.dataDoc.simulation_paused = true), 3000); } - if (this.selectedQuestion.goal == 'noMovement') { + if (this.selectedQuestion.goal === 'noMovement') { this.dataDoc.noMovement = !error; } }; @@ -571,12 +584,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent(this.getAnswersToQuestion(question, vars)); - //this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset); + // this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset); }; // Default setup for uniform circular motion simulation @@ -610,8 +623,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { - let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; - let yPos = this.yMin + 200; + const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius; + const yPos = this.yMin + 200; this.dataDoc.mass1_positionYstart = yPos; this.dataDoc.mass1_positionXstart = xPos; this.dataDoc.mass1_positionY = this.getDisplayYPos(yPos); this.dataDoc.mass1_positionX = xPos; - let tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4)); + const tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4)); const tensionForce1: IForce = { description: 'Tension', magnitude: tensionMag, @@ -891,7 +904,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent - {this.simulationType == 'Pulley' && ( + {this.simulationType === 'Pulley' && (
- {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane') && + {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane') && this.wallPositions?.map((element, index) => )}
@@ -927,17 +940,17 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
- {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( + {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && ( (this.dataDoc.simulation_paused = false)}> )} - {!this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( + {!this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && ( (this.dataDoc.simulation_paused = true)}> )} - {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( + {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && ( this._simReset++)}> @@ -974,15 +987,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
- {this.simulationMode == 'Review' && this.simulationType != 'Inclined Plane' && ( + {this.simulationMode === 'Review' && this.simulationType !== 'Inclined Plane' && (
-

- <>{this.simulationType} review problems in progress! -

+

{this.simulationType} review problems in progress!


)} - {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && ( + {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
{!this.dataDoc.hintDialogueOpen && ( )} - (this.dataDoc.hintDialogueOpen = false)}> + (this.dataDoc.hintDialogueOpen = false)}> Hints {this.selectedQuestion.hints?.map((hint: any, index: number) => ( @@ -1030,12 +1041,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} {this.selectedQuestion.answerParts.includes('angle of gravity') && ( @@ -1045,13 +1056,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} {this.selectedQuestion.answerParts.includes('normal force') && ( @@ -1061,12 +1072,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} {this.selectedQuestion.answerParts.includes('angle of normal force') && ( @@ -1076,13 +1087,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} {this.selectedQuestion.answerParts.includes('force of static friction') && ( @@ -1092,12 +1103,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} {this.selectedQuestion.answerParts.includes('angle of static friction') && ( @@ -1107,13 +1118,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} {this.selectedQuestion.answerParts.includes('coefficient of static friction') && ( @@ -1127,7 +1138,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { this.changeWedgeBasedOnNewAngle(val); this.updateReviewForcesBasedOnAngle(val); }} - radianEquivalent={true} + radianEquivalent showIcon={BoolCast(this.dataDoc.simulation_showIcon)} correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]} /> @@ -1158,7 +1169,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {this.simulationMode == 'Tutorial' && ( + {this.simulationMode === 'Tutorial' && (

Problem

@@ -1180,7 +1191,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent + disabled={this.dataDoc.tutorial_stepNumber === 0}>
@@ -1204,8 +1215,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
- {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') &&

Resources

} - {this.simulationType == 'One Weight' && ( + {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') &&

Resources

} + {this.simulationType === 'One Weight' && ( )} - {this.simulationType == 'Inclined Plane' && ( + {this.simulationType === 'Inclined Plane' && ( )} - {this.simulationType == 'Pendulum' && ( + {this.simulationType === 'Pendulum' && (
)} - {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && ( + {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
)} - {this.simulationMode == 'Freeform' && ( + {this.simulationMode === 'Freeform' && (
- {this.simulationType == 'One Weight' && ( + {this.simulationType === 'One Weight' && ( (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />} label="Make collisions elastic" @@ -1334,7 +1345,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent - {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && ( + {(this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && ( (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} label="Show component force vectors" @@ -1351,80 +1362,80 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent - Speed} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit={'x'} upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth={'5em'} /> - {this.dataDoc.simulation_paused && this.simulationType != 'Circular Motion' && ( + Speed} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit="x" upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth="5em" /> + {this.dataDoc.simulation_paused && this.simulationType !== 'Circular Motion' && ( Gravity} lowerBound={-30} dataDoc={this.dataDoc} prop="gravity" step={0.01} - unit={'m/s2'} + unit="m/s2" upperBound={0} value={NumCast(this.dataDoc.simulation_gravity, -9.81)} effect={(val: number) => this.setupSimulation()} - labelWidth={'5em'} + labelWidth="5em" /> )} - {this.dataDoc.simulation_paused && this.simulationType != 'Pulley' && ( + {this.dataDoc.simulation_paused && this.simulationType !== 'Pulley' && ( Mass} lowerBound={1} dataDoc={this.dataDoc} prop="mass1" step={0.1} - unit={'kg'} + unit="kg" upperBound={5} value={this.mass1 ?? 1} effect={(val: number) => this.setupSimulation()} - labelWidth={'5em'} + labelWidth="5em" /> )} - {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && ( + {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && ( Red mass} lowerBound={1} dataDoc={this.dataDoc} prop="mass1" step={0.1} - unit={'kg'} + unit="kg" upperBound={5} value={this.mass1 ?? 1} effect={(val: number) => this.setupSimulation()} - labelWidth={'5em'} + labelWidth="5em" /> )} - {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && ( + {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && ( Blue mass} lowerBound={1} dataDoc={this.dataDoc} prop="mass2" step={0.1} - unit={'kg'} + unit="kg" upperBound={5} value={this.mass2 ?? 1} effect={(val: number) => this.setupSimulation()} - labelWidth={'5em'} + labelWidth="5em" /> )} - {this.dataDoc.simulation_paused && this.simulationType == 'Circular Motion' && ( + {this.dataDoc.simulation_paused && this.simulationType === 'Circular Motion' && ( Rod length} lowerBound={100} dataDoc={this.dataDoc} prop="circularMotionRadius" step={5} - unit={'m'} + unit="m" upperBound={250} value={this.circularMotionRadius} effect={(val: number) => this.setupSimulation()} - labelWidth={'5em'} + labelWidth="5em" /> )} - {this.simulationType == 'Spring' && this.dataDoc.simulation_paused && ( + {this.simulationType === 'Spring' && this.dataDoc.simulation_paused && (
Spring stiffness} @@ -1432,13 +1443,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent this._simReset++)} radianEquivalent={false} - mode={'Freeform'} - labelWidth={'7em'} + mode="Freeform" + labelWidth="7em" /> Rest length} @@ -1452,7 +1463,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent this._simReset++)} radianEquivalent={false} mode="Freeform" - labelWidth={'7em'} + labelWidth="7em" /> Starting displacement} @@ -1470,11 +1481,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {this.simulationType == 'Inclined Plane' && this.dataDoc.simulation_paused && ( + {this.simulationType === 'Inclined Plane' && this.dataDoc.simulation_paused && (
θ} @@ -1482,16 +1493,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { this.changeWedgeBasedOnNewAngle(val); this._simReset++; })} - radianEquivalent={true} - mode={'Freeform'} - labelWidth={'2em'} + radianEquivalent + mode="Freeform" + labelWidth="2em" /> { @@ -1513,8 +1524,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent this._simReset++)} - mode={'Freeform'} - labelWidth={'2em'} + mode="Freeform" + labelWidth="2em" />
)} - {this.simulationType == 'Inclined Plane' && !this.dataDoc.simulation_paused && ( + {this.simulationType === 'Inclined Plane' && !this.dataDoc.simulation_paused && ( <> θ: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad @@ -1546,12 +1557,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} - {this.simulationType == 'Pendulum' && !this.dataDoc.simulation_paused && ( + {this.simulationType === 'Pendulum' && !this.dataDoc.simulation_paused && ( θ: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad )} - {this.simulationType == 'Pendulum' && this.dataDoc.simulation_paused && ( + {this.simulationType === 'Pendulum' && this.dataDoc.simulation_paused && (
Angle} @@ -1559,13 +1570,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { this.dataDoc.pendulum_angleStart = value; this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length; - if (this.simulationType == 'Pendulum') { + if (this.simulationType === 'Pendulum') { const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180); const forceOfTension: IForce = { @@ -1598,7 +1609,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent @@ -1612,7 +1623,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent { - if (this.simulationType == 'Pendulum') { + if (this.simulationType === 'Pendulum') { this.dataDoc.pendulum_angleStart = this.pendulumAngle; this.dataDoc.pendulum_lengthStart = value; this._simReset++; @@ -1627,11 +1638,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )}
- {this.simulationMode == 'Freeform' && ( + {this.simulationMode === 'Freeform' && ( - + @@ -1646,36 +1657,34 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent Position - {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && ( - + {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && ( + )}{' '} - {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && ( + {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && ( )}{' '} - {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && ( + {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && ( )}{' '} - {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && ( + {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && ( @@ -1758,10 +1767,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent Velocity - {(!this.dataDoc.simulation_paused || (this.simulationType != 'One Weight' && this.simulationType != 'Circular Motion')) && ( + {(!this.dataDoc.simulation_paused || (this.simulationType !== 'One Weight' && this.simulationType !== 'Circular Motion')) && ( )}{' '} - {this.dataDoc.simulation_paused && (this.simulationType == 'One Weight' || this.simulationType == 'Circular Motion') && ( + {this.dataDoc.simulation_paused && (this.simulationType === 'One Weight' || this.simulationType === 'Circular Motion') && ( )}{' '} - {(!this.dataDoc.simulation_paused || this.simulationType != 'One Weight') && ( - - )}{' '} - {this.dataDoc.simulation_paused && this.simulationType == 'One Weight' && ( + {(!this.dataDoc.simulation_paused || this.simulationType !== 'One Weight') && }{' '} + {this.dataDoc.simulation_paused && this.simulationType === 'One Weight' && ( @@ -1822,14 +1827,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponentAcceleration @@ -1842,7 +1843,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
{this.simulationType == 'Pulley' ? 'Red Weight' : ''}{this.simulationType === 'Pulley' ? 'Red Weight' : ''} X Y
- <>{this.dataDoc.mass1_positionX} m - {this.dataDoc.mass1_positionX + ''} m { this.dataDoc.mass1_xChange = value; - if (this.simulationType == 'Suspension') { - let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; - let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; - let deltaX1 = value + this.radius - x1rod; - let deltaX2 = x2rod - (value + this.radius); - let deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius; + if (this.simulationType === 'Suspension') { + const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; + const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; + const deltaX1 = value + this.radius - x1rod; + const deltaX2 = x2rod - (value + this.radius); + const deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius; let dir1T = Math.PI - Math.atan(deltaY / deltaX1); let dir2T = Math.atan(deltaY / deltaX2); - let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); - let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); + const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); dir1T = (dir1T * 180) / Math.PI; dir2T = (dir2T * 180) / Math.PI; const tensionForce1: IForce = { @@ -1692,15 +1701,15 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent {`${NumCast(this.dataDoc.mass1_positionY)} m`} { this.dataDoc.mass1_yChange = value; - if (this.simulationType == 'Suspension') { - let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; - let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; - let deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod; - let deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius); - let deltaY = this.getYPosFromDisplay(value) + this.radius; + if (this.simulationType === 'Suspension') { + const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; + const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; + const deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod; + const deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius); + const deltaY = this.getYPosFromDisplay(value) + this.radius; let dir1T = Math.PI - Math.atan(deltaY / deltaX1); let dir2T = Math.atan(deltaY / deltaX2); - let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); - let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); + const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); dir1T = (dir1T * 180) / Math.PI; dir2T = (dir2T * 180) / Math.PI; const tensionForce1: IForce = { @@ -1741,7 +1750,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent {`${NumCast(this.dataDoc.mass1_velocityX)} m/s`} { this.dataDoc.mass1_velocityXstart = value; this._simReset++; })} - small={true} + small mode="Freeform" /> - <>{this.dataDoc.mass1_velocityY} m/s - {this.dataDoc.mass1_velocityY + ''} m/s { this.dataDoc.mass1_velocityYstart = -value; }} - small={true} + small mode="Freeform" /> - <> - {this.dataDoc.mass1_accelerationX} m/s2 - + {this.dataDoc.mass1_accelerationX + ''} m/s2 - <> - {this.dataDoc.mass1_accelerationY} m/s2 - + {this.dataDoc.mass1_accelerationY + ''} m/s2
)} - {this.simulationMode == 'Freeform' && this.simulationType == 'Pulley' && ( + {this.simulationMode === 'Freeform' && this.simulationType === 'Pulley' && ( @@ -1869,14 +1870,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponentAcceleration @@ -1890,7 +1887,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent )} - {this.simulationType != 'Pendulum' && this.simulationType != 'Spring' && ( + {this.simulationType !== 'Pendulum' && this.simulationType !== 'Spring' && (

Kinematic Equations

    @@ -1907,7 +1904,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {this.simulationType == 'Spring' && ( + {this.simulationType === 'Spring' && (

Harmonic Motion Equations: Spring

    @@ -1936,7 +1933,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent
)} - {this.simulationType == 'Pendulum' && ( + {this.simulationType === 'Pendulum' && (

Harmonic Motion Equations: Pendulum

    @@ -1959,11 +1956,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent - + - - + +

    - {this.simulationType == 'Circular Motion' ? 'Z' : 'Y'} + {this.simulationType === 'Circular Motion' ? 'Z' : 'Y'}

    () ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.SCREENSHOT, { + layout: { view: ScreenshotBox, dataField: 'data' }, + options: { acl: '', _layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' }, +}); diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 5ebc50a1b..bc19d7ad1 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -21,6 +21,8 @@ import { OverlayView } from '../OverlayView'; import { FieldView, FieldViewProps } from './FieldView'; import { DocumentIconContainer } from './DocumentIcon'; import './ScriptingBox.scss'; +import { Docs } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; const _global = (window /* browser */ || global) /* node */ as any; const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default; @@ -845,3 +847,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent() ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.SCRIPTING, { + layout: { view: ScriptingBox, dataField: 'data' }, + options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' }, +}); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 13ee3250e..16767d11e 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -10,14 +10,15 @@ import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; +import { FollowLinkScript } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; import { emptyFunction, formatTime } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/DocUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ReplayMovements } from '../../util/ReplayMovements'; import { undoBatch } from '../../util/UndoManager'; @@ -31,7 +32,8 @@ import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProvider'; import { DocumentView } from './DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; +import { FieldView, FieldViewProps } from './FieldView'; +import { FocusViewOptions } from './FocusViewOptions'; import { RecordingBox } from './RecordingBox'; import './VideoBox.scss'; @@ -1162,3 +1164,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.VID, { + layout: { view: VideoBox, dataField: 'data' }, + options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' }, +}); +Docs.Prototypes.TemplateMap.set(DocumentType.REC, { + layout: { view: VideoBox, dataField: 'data' }, + options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' }, +}); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index dfe237a86..54f246b20 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -18,7 +18,9 @@ import { Cast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, stringHash } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/DocUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; @@ -37,9 +39,11 @@ import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { PinDocView, PinProps } from '../PinFuncs'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; -import { DocumentView, OpenWhere } from './DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; +import { DocumentView } from './DocumentView'; +import { FieldView, FieldViewProps } from './FieldView'; +import { FocusViewOptions } from './FocusViewOptions'; import { LinkInfo } from './LinkDocPreview'; +import { OpenWhere } from './OpenWhere'; import './WebBox.scss'; const { CreateImage } = require('./WebBoxRenderer'); @@ -1242,3 +1246,8 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem ScriptingGlobals.add(function urlHash(url: string) { return stringHash(url); }); + +Docs.Prototypes.TemplateMap.set(DocumentType.WEB, { + layout: { view: WebBox, dataField: 'data' }, + options: { acl: '', _height: 300, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' }, +}); diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 8b9e3cc5d..bd66941c3 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -7,6 +7,8 @@ import * as React from 'react'; import { dateRangeStrToDates } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; @@ -119,3 +121,7 @@ export class CalendarBox extends ViewBoxBaseComponent() { ); } } +Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, { + layout: { view: CalendarBox, dataField: 'data' }, + options: { acl: '' }, +}); diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index f311b3cdd..93371685d 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -9,11 +9,12 @@ import { Doc } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { NumCast } from '../../../../fields/Types'; import { DocServer } from '../../../DocServer'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; +import { DocUtils } from '../../../documents/DocUtils'; import { Transform } from '../../../util/Transform'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocumentView } from '../DocumentView'; -import { FocusViewOptions } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; import { FormattedTextBox } from './FormattedTextBox'; const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side. diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index dc388b22a..1c5ea2dd4 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -23,7 +23,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { FilterPanel } from '../../FilterPanel'; import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { OpenWhere } from '../DocumentView'; +import { OpenWhere } from '../OpenWhere'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9bcf5027f..ad6629fc9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -15,7 +15,7 @@ import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; +import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; @@ -28,8 +28,9 @@ import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils'; import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; import { DocServer } from '../../../DocServer'; -import { Docs, DocUtils } from '../../../documents/Documents'; -import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { DocUtils } from '../../../documents/DocUtils'; import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; @@ -54,9 +55,11 @@ import { PinDocView, PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; import { styleFromLayoutString, StyleProp } from '../../StyleProvider'; import { mediaState } from '../AudioBox'; -import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; +import { DocumentView, DocumentViewInternal } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; import { LinkInfo } from '../LinkDocPreview'; +import { OpenWhere } from '../OpenWhere'; import { DashDocCommentView } from './DashDocCommentView'; import { DashDocView } from './DashDocView'; import { DashFieldView } from './DashFieldView'; @@ -182,26 +185,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn); - FormattedTextBox.SetSelectOnLoad(target); + Doc.SetSelectOnLoad(target); return target; }; @@ -992,7 +977,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent }); + helpItems.push({ description: `show markdown options`, event: () => RTFMarkup.Instance.setOpen(true), icon: }); !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); }; @@ -1062,7 +1047,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(schema: S, props: any): KeyMa return true; }); bind('Cmd-?', () => { - RTFMarkup.Instance.open(); + RTFMarkup.Instance.setOpen(true); return true; }); bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 72a2b706d..1ff862859 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -8,7 +8,8 @@ import { List } from '../../../../fields/List'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; +import { DocUtils } from '../../../documents/DocUtils'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { CollectionView } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 87b7a4069..d5fad296f 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -14,12 +14,13 @@ import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { NumCast } from '../../../../fields/Types'; import { Networking } from '../../../Network'; -import { DocUtils, Docs } from '../../../documents/Documents'; +import { DocUtils } from '../../../documents/DocUtils'; +import { Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; -import { OpenWhereMod } from '../DocumentView'; import { ImageEditorData } from '../ImageBox'; +import { OpenWhereMod } from '../OpenWhere'; import './GenerativeFill.scss'; import Buttons from './GenerativeFillButtons'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 4cf9e99fa..485ba7367 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -36,8 +36,10 @@ import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { pinDataTypes as dataTypes } from '../../PinFuncs'; -import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; +import { DocumentView } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; +import { OpenWhere, OpenWhereMod } from '../OpenWhere'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; @@ -423,7 +425,7 @@ export class PresBox extends ViewBoxBaseComponent() { const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc); const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext; if (context) { - const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView; + const ffview = CollectionFreeFormView.from(DocumentManager.Instance.getFirstDocumentView(context)); if (ffview?.childDocs) { PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime); ffview.layoutDoc._currentFrame = NumCast(activeFrame); @@ -745,7 +747,7 @@ export class PresBox extends ViewBoxBaseComponent() { }; _exitTrail: Opt<() => void>; - PlayTrail = (docs: Doc[]) => { + playTrail = (docs: Doc[]) => { const savedStates = docs.map(doc => { switch (doc.type) { case DocumentType.COL: @@ -2688,3 +2690,8 @@ export class PresBox extends ViewBoxBaseComponent() { ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { PresBox.NavigateToTarget(bestTarget, activeItem); }); + +Docs.Prototypes.TemplateMap.set(DocumentType.PRES, { + layout: { view: PresBox, dataField: 'data' }, + options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true }, +}); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index cf78a45b7..af0ab3b53 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -12,7 +12,7 @@ import { List } from '../../../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; -import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -606,3 +606,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem; } } + +Docs.Prototypes.TemplateMap.set(DocumentType.PRESELEMENT, { + layout: { view: PresElementBox, dataField: 'data' }, + options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, +}); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index f1cd1a4f7..8b2b179d3 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -9,8 +9,8 @@ import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { undoable } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { OpenWhere } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; +import { OpenWhere } from '../nodes/OpenWhere'; import { AnchorMenu } from './AnchorMenu'; import './Annotation.scss'; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 56ff2959c..c1bfdf176 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -12,7 +12,8 @@ import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Networking } from '../../../Network'; import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; -import { DocUtils, Docs } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; +import { DocUtils } from '../../../documents/DocUtils'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 22355bc57..15ac73f78 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -6,21 +6,22 @@ import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs'; import * as React from 'react'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils'; +import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData, Height } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils'; -import { DocUtils } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; -import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; +import { FieldViewProps } from '../nodes/FieldView'; +import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { LinkInfo } from '../nodes/LinkDocPreview'; import { PDFBox } from '../nodes/PDFBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; @@ -331,7 +332,7 @@ export class PDFViewer extends ObservableReactComponent { this._ignoreScroll = false; if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio this._scrollTimer = setTimeout(() => { - DocUtils.MakeLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false); + CreateLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false); this._scrollTimer = undefined; }, 200); } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 384e6d654..5df934231 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -4,11 +4,14 @@ import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { ClientUtils } from '../../../ClientUtils'; import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc'; import { DirectLinks, DocData } from '../../../fields/DocSymbols'; +import { Id } from '../../../fields/FieldSymbols'; import { DocCast, StrCast } from '../../../fields/Types'; +import { DocUtils } from '../../documents/DocUtils'; import { DocumentType } from '../../documents/DocumentTypes'; -import { DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { LinkManager } from '../../util/LinkManager'; import { SearchUtil } from '../../util/SearchUtil'; @@ -21,8 +24,6 @@ import { IRecommendation, Recommendation } from '../newlightbox/components'; import { fetchRecommendations } from '../newlightbox/utils'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './SearchBox.scss'; -import { Id } from '../../../fields/FieldSymbols'; -import { ClientUtils } from '../../../ClientUtils'; const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; @@ -520,3 +521,8 @@ export class SearchBox extends ViewBoxBaseComponent() { ); } } + +Docs.Prototypes.TemplateMap.set(DocumentType.SEARCH, { + layout: { view: SearchBox, dataField: 'data' }, + options: { acl: '', _width: 400 }, +}); diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx index 7ad7b2927..6ed3b240d 100644 --- a/src/client/views/selectedDoc/SelectedDocView.tsx +++ b/src/client/views/selectedDoc/SelectedDocView.tsx @@ -8,7 +8,7 @@ import { Doc } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { DocumentManager } from '../../util/DocumentManager'; import { SettingsManager } from '../../util/SettingsManager'; -import { FocusViewOptions } from '../nodes/FieldView'; +import { FocusViewOptions } from '../nodes/FocusViewOptions'; export interface SelectedDocViewProps { selectedDocs: Doc[]; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 9083d6ca3..5fd053eef 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -174,6 +174,49 @@ export function updateCachedAcls(doc: Doc) { return undefined; } +export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore +export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore +export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ''); } // prettier-ignore +export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore +export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore +export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore +export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore +export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore +export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore +export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore +export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore + +export function SetActiveInkWidth(width: string): void { + !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); +} +export function SetActiveBezierApprox(bezier: string): void { + ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier); +} +export function SetActiveInkColor(value: string) { + ActiveInkPen() && (ActiveInkPen().activeInkColor = value); +} +export function SetActiveIsInkMask(value: boolean) { + ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value); +} +export function SetActiveInkHideTextLabels(value: boolean) { + ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value); +} +export function SetActiveFillColor(value: string) { + ActiveInkPen() && (ActiveInkPen().activeFillColor = value); +} +export function SetActiveArrowStart(value: string) { + ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); +} +export function SetActiveArrowEnd(value: string) { + ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); +} +export function SetActiveArrowScale(value: number) { + ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); +} +export function SetActiveDash(dash: string): void { + !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); +} + @scriptingGlobal @Deserializable('Doc', updateCachedAcls, ['id']) export class Doc extends RefField { @@ -420,6 +463,16 @@ export class Doc extends RefField { // eslint-disable-next-line no-redeclare export namespace Doc { + // eslint-disable-next-line import/no-mutable-exports + export let SelectOnLoad: Doc | undefined; + export function SetSelectOnLoad(doc: Doc | undefined) { + SelectOnLoad = doc; + } + // eslint-disable-next-line import/no-mutable-exports + export let DocDragDataName: string = ''; + export function SetDocDragDataName(name: string) { + DocDragDataName = name; + } export function SetContainer(doc: Doc, container: Doc) { if (container !== Doc.MyRecentlyClosed) { doc.embedContainer = container; @@ -1605,6 +1658,29 @@ export namespace Doc { } } +export function RTFIsFragment(html: string) { + return html.indexOf('data-pm-slice') !== -1; +} +export function GetHrefFromHTML(html: string): string { + const parser = new DOMParser(); + const parsedHtml = parser.parseFromString(html, 'text/html'); + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; + } + return ''; +} +export function GetDocFromUrl(url: string) { + return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId +} + +let activeAudioLinker: (f: () => Doc | undefined, broadcast?: boolean) => (Doc | undefined)[]; +export function SetActiveAudioLinker(func: (f: () => Doc | undefined, broadcast?: boolean) => (Doc | undefined)[]) { + activeAudioLinker = func; +} +export function CreateLinkToActiveAudio(func: () => Doc | undefined, broadcast?: boolean) { + return activeAudioLinker(func, broadcast); +} + export function IdToDoc(id: string) { return DocCast(DocServer.GetCachedRefField(id)); } diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index a2cc47a43..5eb60a2f8 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -12,7 +12,8 @@ import { DocServer } from '../client/DocServer'; import { Networking } from '../client/Network'; import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils'; -import { DocUtils, Docs } from '../client/documents/Documents'; +import { Docs } from '../client/documents/Documents'; +import { DocUtils } from '../client/documents/DocUtils'; import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox'; import { schema } from '../client/views/nodes/formattedText/schema_rts'; import { Doc, Opt } from './Doc'; diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 8a3787768..5e5f5527f 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -277,6 +277,10 @@ export class ComputedField extends ScriptField { } } +export function FollowLinkScript() { + return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' }); +} + ScriptingGlobals.add( // eslint-disable-next-line prefer-arrow-callback function setIndexVal(list: any[], index: number, value: any) { diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index b355e70a7..55a85ed1b 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -127,6 +127,7 @@ import './AudioUpload.scss'; import { Uploader } from './ImageUpload'; import './ImageUpload.scss'; import './MobileInterface.scss'; +import { DashboardView } from '../client/views/DashboardView'; library.add( ...[ @@ -583,7 +584,7 @@ export class MobileInterface extends React.Component { }; const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row'); + const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row'); const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`); -- cgit v1.2.3-70-g09d2

- <> - {this.dataDoc.mass2_accelerationX} m/s2 - + {this.dataDoc.mass2_accelerationX + ''} m/s2 - <> - {this.dataDoc.mass2_accelerationY} m/s2 - + {this.dataDoc.mass2_accelerationY + ''} m/s2