diff options
author | Lionel Han <47760119+IGoByJoe@users.noreply.github.com> | 2020-08-14 12:55:05 -0700 |
---|---|---|
committer | Lionel Han <47760119+IGoByJoe@users.noreply.github.com> | 2020-08-14 12:55:05 -0700 |
commit | 4fd8f1533be68bdcd3a6b8a832a38655adde4d71 (patch) | |
tree | cd2110b252c6a73932dfae36ba3d535d5e40fff0 /src | |
parent | a9161a668c96750d8bb50647c043a979058ef451 (diff) | |
parent | 36f7b54914b4d5fce98de3a6d83f1b186ebb17d1 (diff) |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into new_audio
Diffstat (limited to 'src')
80 files changed, 2336 insertions, 1868 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 2fe3e9778..63e01bc5b 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -330,69 +330,76 @@ export namespace DocServer { } } - // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) - // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of - // the fields have been returned from the server - const getSerializedFields: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); - - // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects. - // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all - // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. - const deserializeFields = getSerializedFields.then(async fields => { - const fieldMap: { [id: string]: RefField } = {}; - const proms: Promise<void>[] = []; - runInAction(() => { - for (const field of fields) { - if (field !== undefined && field !== null && !_cache[field.id]) { - // deserialize - const cached = _cache[field.id]; - if (!cached) { - const prom = SerializationHelper.Deserialize(field).then(deserialized => { - fieldMap[field.id] = deserialized; - - //overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (deserialized !== undefined) { - _cache[field.id] = deserialized; - } else { - delete _cache[field.id]; - } - return deserialized; - }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - _cache[field.id] = prom; - - // adds to a list of promises that will be awaited asynchronously - proms.push(prom); - } else if (cached instanceof Promise) { - proms.push(cached as any); + if (requestedIds.length) { + + // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) + // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of + // the fields have been returned from the server + const getSerializedFields: Promise<any> = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); + + // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects. + // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all + // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. + const deserializeFields = getSerializedFields.then(async fields => { + const fieldMap: { [id: string]: RefField } = {}; + const proms: Promise<void>[] = []; + runInAction(() => { + for (const field of fields) { + if (field !== undefined && field !== null && !_cache[field.id]) { + // deserialize + const cached = _cache[field.id]; + if (!cached) { + const prom = SerializationHelper.Deserialize(field).then(deserialized => { + fieldMap[field.id] = deserialized; + + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; + } else { + delete _cache[field.id]; + } + return deserialized; + }); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + _cache[field.id] = prom; + + // adds to a list of promises that will be awaited asynchronously + proms.push(prom); + } else if (cached instanceof Promise) { + proms.push(cached as any); + } + } else if (_cache[field.id] instanceof Promise) { + proms.push(_cache[field.id] as any); + (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f); + } else if (field) { + proms.push(_cache[field.id] as any); + fieldMap[field.id] = field; } - } else if (field) { - proms.push(_cache[field.id] as any); - fieldMap[field.id] = field; } - } + }); + await Promise.all(proms); + return fieldMap; }); - await Promise.all(proms); - return fieldMap; - }); - // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose - // prototype documents, if any, have also been fetched and cached. - const fields = await deserializeFields; + // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose + // prototype documents, if any, have also been fetched and cached. + const fields = await deserializeFields; - // 6) with this confidence, we can now go through and update the cache at the ids of the fields that - // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given - // id to the soon-to-be-returned field mapping. - requestedIds.forEach(id => { - const field = fields[id]; - map[id] = field; - }); + // 6) with this confidence, we can now go through and update the cache at the ids of the fields that + // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given + // id to the soon-to-be-returned field mapping. + requestedIds.forEach(id => { + const field = fields[id]; + map[id] = field; + }); + + } // 7) those promises we encountered in the else if of 1), which represent // other callers having already submitted a request to the server for (a) document(s) diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 71d6c2ccc..1bef6fa08 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -10,7 +10,7 @@ export enum DocumentType { VID = "video", // video AUDIO = "audio", // audio PDF = "pdf", // pdf - INK = "ink", // ink stroke + INK = "inks", // ink stroke SCREENSHOT = "screenshot", // view of a desktop application FONTICON = "fonticonbox", // font icon SEARCH = "search", // search query @@ -31,7 +31,7 @@ export enum DocumentType { COLOR = "color", // color picker (view of a color picker for a color string) YOUTUBE = "youtube", // youtube directory (view of you tube search results) DOCHOLDER = "docholder", // nested document (view of a document) - SEARCHITEM= "searchitem", + SEARCHITEM = "searchitem", COMPARISON = "comparison", // before/after view with slider (view of 2 images) GROUP = "group", // group of users diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a6a697574..42ba4d2c4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -53,6 +53,7 @@ import { Upload } from "../../server/SharedMediaTypes"; const path = require('path'); export interface DocumentOptions { + system?: boolean; _autoHeight?: boolean; _panX?: number; _panY?: number; @@ -111,6 +112,7 @@ export interface DocumentOptions { _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; + watchedDocuments?: Doc; // list of documents to "watch" in an icon doc to display a badge targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List<string>; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) @@ -173,6 +175,7 @@ export interface DocumentOptions { onPointerUp?: ScriptField; dropConverter?: ScriptField; // script to run when documents are dropped on this Document. dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script + clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop clipboard?: Doc; UseCors?: boolean; @@ -182,7 +185,7 @@ export interface DocumentOptions { targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script searchFileTypes?: List<string>; // file types allowed in a search query strokeWidth?: number; - stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere + _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view treeViewHideTitle?: boolean; // whether to hide the title of a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. @@ -530,7 +533,7 @@ export namespace Docs { Scripting.addGlobal(Buxton); - const delegateKeys = ["x", "y", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"]; + const delegateKeys = ["x", "y", "system", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"]; /** * This function receives the relevant document prototype and uses @@ -553,6 +556,8 @@ export namespace Docs { export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys, "^_"); + protoProps.system = delegateProps.system; + if (!("author" in protoProps)) { protoProps.author = Doc.CurrentUserEmail; } @@ -1159,7 +1164,7 @@ export namespace DocUtils { export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) { let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey); if (!(optionsCollection instanceof Doc)) { - optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set` }, enumeratedFieldKey); + optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set`, system: true }, enumeratedFieldKey); Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc); } const options = optionsCollection as Doc; @@ -1208,5 +1213,4 @@ export namespace DocUtils { } Scripting.addGlobal("Docs", Docs); -Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); - +Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; });
\ No newline at end of file diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4e75a51f6..153118083 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -23,6 +23,7 @@ import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { LabelBox } from "../views/nodes/LabelBox"; import { LinkManager } from "./LinkManager"; +import { Id } from "../../fields/FieldSymbols"; export class CurrentUserUtils { private static curr_id: string; @@ -45,10 +46,10 @@ export class CurrentUserUtils { if (doc["template-button-query"] === undefined) { const queryTemplate = Docs.Create.MulticolumnDocument( [ - Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, lockedPosition: true, title: "query", _height: 200 }), - Docs.Create.FreeformDocument([], { title: "data", _height: 100 }) + Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, lockedPosition: true, title: "query", _height: 200, system: true }), + Docs.Create.FreeformDocument([], { title: "data", _height: 100, system: true }) ], - { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } + { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true } ); queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); doc["template-button-query"] = CurrentUserUtils.ficon({ @@ -80,10 +81,10 @@ export class CurrentUserUtils { if (doc["template-button-slides"] === undefined) { const slideTemplate = Docs.Create.MultirowDocument( [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200 }), - Docs.Create.TextDocument("", { title: "text", _height: 100 }) + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true }) ], - { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } + { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true } ); slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); doc["template-button-slides"] = CurrentUserUtils.ficon({ @@ -94,7 +95,7 @@ export class CurrentUserUtils { } if (doc["template-button-description"] === undefined) { - const descriptionTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header")); // text needs to be a space to allow templateText to be created + const descriptionTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created descriptionTemplate[DataSym].layout = "<div>" + " <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" + @@ -110,7 +111,7 @@ export class CurrentUserUtils { } if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to. - const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header")); // text needs to be a space to allow templateText to be created + const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created Doc.GetProto(linkTemplate).layout = "<div>" + " <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" + @@ -158,9 +159,9 @@ export class CurrentUserUtils { if (doc["template-button-switch"] === undefined) { const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; - const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40 }); - const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1 }); - const no = FreeformDocument([], { title: "no", _height: 100, _width: 100 }); + const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true }); + const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true }); + const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true }); const labelTemplate = { doc: { type: "doc", content: [{ @@ -177,13 +178,13 @@ export class CurrentUserUtils { // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); - const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, }); + const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true }); box.isTemplateDoc = makeTemplate(box, true, "switch"); doc["template-button-switch"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: new PrefetchProxy(box) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on" + removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on", system: true }); } @@ -193,12 +194,12 @@ export class CurrentUserUtils { const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)"); const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10, - onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F" + onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F", system: true }); - const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true }); - const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true }); - const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true }); + const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true, system: true }); + const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true, system: true }); + const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true, system: true }); const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"]; const detailedTemplate = { @@ -215,7 +216,7 @@ export class CurrentUserUtils { const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 }; const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" }; - const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" }; + const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true }; const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); descriptionWrapper._columnHeaders = new List<SchemaHeaderField>([ @@ -223,7 +224,7 @@ export class CurrentUserUtils { new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true), new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true), ]); - const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts }); + const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, system: true }); detailView.isTemplateDoc = makeTemplate(detailView); details.title = "Details"; @@ -233,7 +234,7 @@ export class CurrentUserUtils { doc["template-button-detail"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: new PrefetchProxy(detailView) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize" + removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize", system: true }); } @@ -251,7 +252,7 @@ export class CurrentUserUtils { hidden: ComputedField.MakeFunction("self.userDoc.noviceMode") as any, userDoc: doc, _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true })); } else { const curButnTypes = Cast(doc["template-buttons"], Doc, null); @@ -266,42 +267,42 @@ export class CurrentUserUtils { // setup the different note type skins static setupNoteTemplates(doc: Doc) { if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }); + const noteView = Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow", system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); doc["template-note-Note"] = new PrefetchProxy(noteView); } if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Idea", backgroundColor: "pink" }); + const noteView = Docs.Create.TextDocument("", { title: "text", style: "Idea", backgroundColor: "pink", system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); doc["template-note-Idea"] = new PrefetchProxy(noteView); } if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", style: "Topic", backgroundColor: "lightBlue" }); + const noteView = Docs.Create.TextDocument("", { title: "text", style: "Topic", backgroundColor: "lightBlue", system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); doc["template-note-Topic"] = new PrefetchProxy(noteView); } if (doc["template-note-Todo"] === undefined) { const noteView = Docs.Create.TextDocument("", { title: "text", style: "Todo", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", - layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus") + layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); doc["template-note-Todo"] = new PrefetchProxy(noteView); } const taskStatusValues = [ - { title: "todo", _backgroundColor: "blue", color: "white" }, - { title: "in progress", _backgroundColor: "yellow", color: "black" }, - { title: "completed", _backgroundColor: "green", color: "white" } + { title: "todo", _backgroundColor: "blue", color: "white", system: true }, + { title: "in progress", _backgroundColor: "yellow", color: "black", system: true }, + { title: "completed", _backgroundColor: "green", color: "white", system: true } ]; if (doc.fieldTypes === undefined) { - doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" }); + doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true }); DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues); } if (doc["template-notes"] === undefined) { doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc], - { title: "Note Layouts", _height: 75 })); + { title: "Note Layouts", _height: 75, system: true })); } else { const curNoteTypes = Cast(doc["template-notes"], Doc, null); const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, @@ -322,7 +323,7 @@ export class CurrentUserUtils { const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); if (doc.templateDocs === undefined) { doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { - title: "template layouts", _xPadding: 0, + title: "template layouts", _xPadding: 0, system: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) })); } @@ -333,7 +334,7 @@ export class CurrentUserUtils { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.LabelDocument({ title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); // Docs.Create.TextDocument("", { // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") @@ -345,7 +346,7 @@ export class CurrentUserUtils { if (doc["template-icon-view-rtf"] === undefined) { const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); @@ -353,26 +354,26 @@ export class CurrentUserUtils { if (doc["template-icon-view-button"] === undefined) { const iconBtnView = Docs.Create.FontIconDocument({ title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30, - _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON); doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView); } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); } if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") }); + const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } if (doc["template-icons"] === undefined) { doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 })); + doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); } else { const templateIconsDoc = Cast(doc["template-icons"], Doc, null); const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, @@ -387,73 +388,78 @@ export class CurrentUserUtils { static creatorBtnDescriptors(doc: Doc): { title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean + click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc }[] { if (doc.emptyPresentation === undefined) { doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(), - { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); + { title: "Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true }); } if (doc.emptyCollection === undefined) { doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform" }); + { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform", system: true }); + } + if (doc.emptyPane === undefined) { + doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, title: "Untitled Collection", system: true }); } if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300 }); + doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true }); } if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" }); + doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true }); } if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" }); + doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot", system: true }); } if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio" }); + doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio", system: true }); } if (doc.emptyImage === undefined) { - doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat" }); + doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true }); } if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button" }); + doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true }); } if (doc.emptyDocHolder === undefined) { doc.emptyDocHolder = Docs.Create.DocumentDocument( ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any, - { _width: 250, _height: 250, title: "container" }); + { _width: 250, _height: 250, title: "container", system: true }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true }); + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true, system: true }); } if (doc.activeMobileMenu === undefined) { this.setupActiveMobileMenu(doc); } return [ - { toolTip: "Drag a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc, noviceMode: true }, - { toolTip: "Drag a web page", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, - { toolTip: "Drag a cat image", title: "Image", icon: "cat", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyImage as Doc }, - { toolTip: "Drag a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, - { toolTip: "Drag a screengrabber", title: "Grab", icon: "photo-video", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScreenshot as Doc }, + { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.clickFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, + { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyImage as Doc }, + { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, + { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScreenshot as Doc }, // { title: "Drag a webcam", title: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, - { toolTip: "Drag a audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, - { toolTip: "Drag a button", title: "Button", icon: "bolt", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyButton as Doc, noviceMode: true }, + { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, + { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyButton as Doc, noviceMode: true }, - { toolTip: "Drag a presentation view", title: "Present", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, - { toolTip: "Drag a search box", title: "Query", icon: "search", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptySearch as Doc }, - { toolTip: "Drag a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc }, + { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Present", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, + { toolTip: "Tap to create a search box in a new pane, drag for a search box", title: "Query", icon: "search", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptySearch as Doc }, + { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc }, // { title: "Drag an import folder", title: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, - { toolTip: "Drag a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, + { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, // { title: "Drag an instance of the device collection", title: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc }, // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc }, - { toolTip: "Drag a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, + { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, { toolTip: "Connect a Google Account", title: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } + + // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static async setupCreatorButtons(doc: Doc) { let alreadyCreatedButtons: string[] = []; @@ -466,13 +472,13 @@ export class CurrentUserUtils { } } const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); - const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory, noviceMode }) => Docs.Create.FontIconDocument({ + const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ _nativeWidth: 50, _nativeHeight: 50, _width: 50, _height: 50, icon, title, toolTip, ignoreClick, - dropAction: "copy", + dropAction: "alias", onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, onClick: click ? ScriptField.MakeScript(click) : undefined, ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, @@ -480,15 +486,16 @@ export class CurrentUserUtils { backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory, + clickFactory, userDoc: noviceMode ? undefined as any : doc, - hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode") + hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode"), system: true })); if (dragCreatorSet === undefined) { doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _autoHeight: true, _width: 500, _columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true })); } else { creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); @@ -496,19 +503,20 @@ export class CurrentUserUtils { return doc.myItemCreators as Doc; } - static menuBtnDescriptions(): { - title: string, icon: string, click: string, + static menuBtnDescriptions(doc: Doc): { + title: string, icon: string, click: string, watchedDocuments?: Doc }[] { + this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing return [ - { title: "Sharing", icon: "users", click: 'scriptContext.selectMenu(self, "Sharing")' }, - { title: "Workspace", icon: "desktop", click: 'scriptContext.selectMenu(self, "Workspace")' }, - { title: "Catalog", icon: "file", click: 'scriptContext.selectMenu(self, "Catalog")' }, - { title: "Archive", icon: "archive", click: 'scriptContext.selectMenu(self, "Archive")' }, - { title: "Import", icon: "upload", click: 'scriptContext.selectMenu(self, "Import")' }, - { title: "Tools", icon: "wrench", click: 'scriptContext.selectMenu(self, "Tools")' }, - { title: "Help", icon: "question-circle", click: 'scriptContext.selectMenu(self, "Help")' }, - { title: "Settings", icon: "cog", click: 'scriptContext.selectMenu(self, "Settings")' }, - { title: "User Doc", icon: "address-card", click: 'scriptContext.selectMenu(self, "UserDoc")' }, + { title: "Sharing", icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc["sidebar-sharing"] as Doc }, + { title: "Workspace", icon: "desktop", click: 'selectMainMenu(self)' }, + { title: "Catalog", icon: "file", click: 'selectMainMenu(self)' }, + { title: "Archive", icon: "archive", click: 'selectMainMenu(self)' }, + { title: "Import", icon: "upload", click: 'selectMainMenu(self)' }, + { title: "Tools", icon: "wrench", click: 'selectMainMenu(self)' }, + { title: "Help", icon: "question-circle", click: 'selectMainMenu(self)' }, + { title: "Settings", icon: "cog", click: 'selectMainMenu(self)' }, + { title: "User Doc", icon: "address-card", click: 'selectMainMenu(self)' }, ]; } @@ -516,23 +524,24 @@ export class CurrentUserUtils { if (doc["search-panel"] === undefined) { doc["search-panel"] = new PrefetchProxy(Docs.Create.SearchDocument({ _width: 500, _height: 400, backgroundColor: "dimGray", ignoreClick: true, - childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", + childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", system: true })) as any as Doc; } } static setupMenuPanel(doc: Doc) { if (doc.menuStack === undefined) { - const menuBtns = CurrentUserUtils.menuBtnDescriptions().map(({ title, icon, click }) => + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, icon, click, watchedDocuments }) => Docs.Create.FontIconDocument({ icon, iconShape: "square", title, _backgroundColor: "black", - stayInCollection: true, + _stayInCollection: true, childDropAction: "same", _width: 60, _height: 60, - onClick: ScriptField.MakeScript(click, { scriptContext: "any" }), + watchedDocuments, + onClick: ScriptField.MakeScript(click, { scriptContext: "any" }), system: true })); const userDoc = menuBtns[menuBtns.length - 1]; userDoc.userDoc = doc; @@ -544,7 +553,7 @@ export class CurrentUserUtils { _backgroundColor: "black", _gridGap: 0, _yMargin: 0, - _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, lockedPosition: true, _chromeStatus: "disabled", + _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, lockedPosition: true, _chromeStatus: "disabled", system: true })); } // this resets all sidebar buttons to being deactivated @@ -571,7 +580,7 @@ export class CurrentUserUtils { // Sets up mobileMenu stacking document static setupMobileMenu() { const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { - _width: 980, ignoreClick: true, lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100 + _width: 980, ignoreClick: true, lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100, system: true })); return menu; } @@ -592,9 +601,9 @@ export class CurrentUserUtils { title: data.title, lockedPosition: true, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - _backgroundColor: data.backgroundColor + _backgroundColor: data.backgroundColor, system: true }, - [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) + [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) ); } @@ -602,26 +611,26 @@ export class CurrentUserUtils { static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { ...opts, dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, - borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled" + borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled", system: true }) as any as Doc // sets up the text container for the information contained within the mobile button static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { ...opts, dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, - backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", _chromeStatus: "disabled", ignoreClick: true + backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", _chromeStatus: "disabled", ignoreClick: true, system: true }) as any as Doc // Sets up the title of the button static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { ...opts, - dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)" + dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", system: true }) as any as Doc // Sets up the description of the button static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { ...opts, - dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, + dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true }) as any as Doc @@ -629,7 +638,7 @@ export class CurrentUserUtils { const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300 }), backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, + { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, ]; @@ -640,7 +649,7 @@ export class CurrentUserUtils { clipboard: data.clipboard, onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activeInkPen: data.activeInkPen, pointerHack: true, - backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, + backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, system: true })); } @@ -648,10 +657,10 @@ export class CurrentUserUtils { if (!userDoc.thumbDoc) { const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", - _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white" + _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true }); thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { - _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", linearViewIsExpanded: true, flexDirection: "column" + _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", linearViewIsExpanded: true, flexDirection: "column", system: true }); userDoc.thumbDoc = thumbDoc; } @@ -687,20 +696,20 @@ export class CurrentUserUtils { if (doc.myCreators === undefined) { doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, - _width: 500, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", + _width: 500, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", system: true })); } // setup a color picker if (doc.myColorPicker === undefined) { const color = Docs.Create.ColorDocument({ - title: "color picker", _width: 300, dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"]) + title: "color picker", _width: 300, dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"]), system: true }); doc.myColorPicker = new PrefetchProxy(color); } if (doc["sidebar-tools"] === undefined) { const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "sidebar-tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", hideFilterView: true, forceActive: true + title: "sidebar-tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", hideFilterView: true, forceActive: true, system: true })) as any as Doc; doc["sidebar-tools"] = toolsStack; @@ -712,7 +721,7 @@ export class CurrentUserUtils { doc.myWorkspaces === undefined; if (doc.myWorkspaces === undefined) { doc.myWorkspaces = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, + title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, system: true })); } if (doc["sidebar-workspaces"] === undefined) { @@ -725,7 +734,7 @@ export class CurrentUserUtils { doc["sidebar-workspaces"] = new PrefetchProxy(Docs.Create.TreeDocument([workspaces], { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same" + lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } } @@ -735,7 +744,7 @@ export class CurrentUserUtils { if (doc.myCatalog === undefined) { doc.myCatalog = new PrefetchProxy(Docs.Create.SchemaDocument([], [], { title: "CATALOG", _height: 1000, _fitWidth: true, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, - childDropAction: "alias", targetDropAction: "same", stayInCollection: true, treeViewOpen: true, + childDropAction: "alias", targetDropAction: "same", _stayInCollection: true, treeViewOpen: true, system: true })); } @@ -746,7 +755,7 @@ export class CurrentUserUtils { title: "sidebar-catalog", treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same" + lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } } @@ -755,7 +764,7 @@ export class CurrentUserUtils { doc.myRecentlyClosed === undefined; if (doc.myRecentlyClosed === undefined) { doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, treeViewOpen: true, stayInCollection: true, + title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, treeViewOpen: true, _stayInCollection: true, system: true })); } // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready @@ -771,7 +780,7 @@ export class CurrentUserUtils { title: "sidebar-recentlyClosed", treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same" + lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } } @@ -784,7 +793,7 @@ export class CurrentUserUtils { doc["sidebar-userDoc"] = new PrefetchProxy(Docs.Create.TreeDocument([doc], { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "sidebar-userDoc", treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, - lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same" + lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } } @@ -795,6 +804,7 @@ export class CurrentUserUtils { sidebarContainer._chromeStatus = "disabled"; sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()"); doc.sidebar = new PrefetchProxy(sidebarContainer); + doc.system = true; } return doc.sidebar as Doc; } @@ -812,20 +822,20 @@ export class CurrentUserUtils { static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - backgroundColor: "black", treeViewPreventOpen: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true + backgroundColor: "black", treeViewPreventOpen: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true, system: true })) as any as Doc static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40 + ...opts, dropAction: "alias", removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true })) as any as Doc /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc) { if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), toolTip: "click to undo", title: "undo", icon: "undo-alt" }); + doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), toolTip: "click to undo", title: "undo", icon: "undo-alt", system: true }); } if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), toolTip: "click to redo", title: "redo", icon: "redo-alt" }); + doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), toolTip: "click to redo", title: "redo", icon: "redo-alt", system: true }); } if (doc.dockedBtns === undefined) { doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); @@ -834,7 +844,7 @@ export class CurrentUserUtils { // sets up the default set of documents to be shown in the Overlay layer static setupOverlays(doc: Doc) { if (doc.myOverlayDocuments === undefined) { - doc.myOverlayDocuments = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6" })); + doc.myOverlayDocuments = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); } } @@ -842,21 +852,30 @@ export class CurrentUserUtils { static setupDefaultPresentation(doc: Doc) { if (doc["template-presentation"] === undefined) { doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" + title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true })); } if (doc.activePresentation === undefined) { - doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), { - title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", - _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" - }); + doc.activePresentation = Doc.MakeCopy(doc.emptyPresentation as Doc, true); } } - // Right sidebar is where mobile uploads are contained + // Sharing sidebar is where shared documents are contained static setupSharingSidebar(doc: Doc) { if (doc["sidebar-sharing"] === undefined) { - doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Shared Documents", childDropAction: "alias" })); + doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Shared Documents", childDropAction: "alias", system: true })); + } + } + + // Import sidebar is where shared documents are contained + static setupImportSidebar(doc: Doc) { + if (doc["sidebar-import-documents"] === undefined) { + doc["sidebar-import-documents"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Imported Documents", forceActive: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 30, lockedPosition: true, _chromeStatus: "disabled" })); + } + if (doc["sidebar-import"] === undefined) { + const uploads = Cast(doc["sidebar-import-documents"], Doc, null); + const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _backgroundColor: "black", title: "Import", icon: "upload", system: true }); + doc["sidebar-import"] = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "Imported Documents", _yMargin: 20, ignoreClick: true, lockedPosition: true })); } } @@ -868,14 +887,14 @@ export class CurrentUserUtils { "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ", { thisContainer: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, - targetScriptKey: "onChildClick", + targetScriptKey: "onChildClick", system: true }); const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)", - {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" }); + {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); - doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); + doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); } // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); @@ -883,24 +902,26 @@ export class CurrentUserUtils { if (doc.clickFuncs === undefined) { const onClick = Docs.Create.ScriptingDocument(undefined, { title: "onClick", "onClick-rawScript": "console.log('click')", - isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200 + isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true }, "onClick"); const onChildClick = Docs.Create.ScriptingDocument(undefined, { title: "onChildClick", "onChildClick-rawScript": "console.log('child click')", - isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200 + isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true }, "onChildClick"); const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", - isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200 + isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true }, "onDoubleClick"); const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, { title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')", - isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200 + isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true }, "onChildDoubleClick"); const onCheckedClick = Docs.Create.ScriptingDocument(undefined, { - title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200 + title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", + "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, + isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" }); + doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true }); } PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); @@ -922,25 +943,28 @@ export class CurrentUserUtils { doc.fontFamily = StrCast(doc.fontFamily, "Arial"); doc.fontColor = StrCast(doc.fontColor, "black"); doc.fontHighlight = StrCast(doc.fontHighlight, ""); - doc.defaultColor = StrCast(doc.defaultColor, "white"); + doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, true); + doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white"); + doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); doc.noviceMode = BoolCast(doc.noviceMode, true); doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates - this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing + this.setupImportSidebar(doc); this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile this.setupMenuPanel(doc); this.setupSearchPanel(doc); this.setupOverlays(doc); // documents in overlay layer this.setupDockedButtons(doc); // the bottom bar of font icons - this.setupDefaultPresentation(doc); // presentation that's initially triggered await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument(); doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); + setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered + // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index bd57e7f48..962294933 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -231,6 +231,7 @@ export class DocumentManager { containerDoc.currentTimecode = targetTimecode; const targetContext = await target?.context as Doc; const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; + console.log(targetNavContext); DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished); } else { finished?.(); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4b1860b5c..0cca61841 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -210,7 +210,7 @@ export namespace DragManager { docDragData.droppedDocuments = dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : - docDragData.dropAction === "copy" ? Doc.MakeDelegate(d) : d); + docDragData.dropAction === "copy" ? Doc.MakeClone(d) : d); docDragData.dropAction !== "same" && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []); const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index d0acf14c3..f1848f7e5 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -58,7 +58,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { let dbox = doc; // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) { - dbox = Doc.MakeAlias(doc); + //dbox = Doc.MakeAlias(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon } else if (!doc.onDragStart && !doc.isButtonBar) { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; if (layoutDoc.type !== DocumentType.FONTICON) { diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts index cacb72a57..de2457a5a 100644 --- a/src/client/util/KeyCodes.ts +++ b/src/client/util/KeyCodes.ts @@ -131,6 +131,6 @@ export class KeyCodes { public static NUM_7: number = 55; public static NUM_8: number = 56; public static NUM_9: number = 57; - public static SUBSTRACT: number = 189; + public static SUBTRACT: number = 189; public static ADD: number = 187; }
\ No newline at end of file diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 7b2c601fe..b63fc8991 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -37,7 +37,8 @@ export namespace SearchUtil { export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) { query = query || "*"; //If we just have a filter query, search for * as the query const rpquery = Utils.prepend("/dashsearch"); - const gotten = await rp.get(rpquery, { qs: { ...options, q: query } }); + const replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}type_t:${arg}`); + const gotten = await rp.get(rpquery, { qs: { ...options, sort: "lastModified_d desc", q: replacedQuery } }); const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); if (!returnDocs) { return result; @@ -82,7 +83,7 @@ export namespace SearchUtil { } } - return { docs: theDocs, numFound: theDocs.length, highlighting, lines: theLines }; + return { docs: theDocs, numFound: result.numFound, highlighting, lines: theLines }; } export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]>; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 05ba00331..113278593 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -84,11 +84,4 @@ export namespace SelectionManager { export function SelectedDocuments(): Array<DocumentView> { return Array.from(manager.SelectedDocuments.keys()); } -} - - -Scripting.addGlobal(function selectDoc(doc: any) { - const view = DocumentManager.Instance.getDocumentView(doc); - view && SelectionManager.SelectDoc(view, false); - //Doc.UserDoc().activeSelection = new List([doc]); -});
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index 01dda0aca..ec513e5d5 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -33,10 +33,10 @@ font-size: 12px; padding-right: 15px; color: black; - margin-top: 4px; + margin-top: 10px; /* right: 135; */ position: absolute; - left: 235; + left: 243; } .settings-section { @@ -97,6 +97,8 @@ .modes-content { display: flex; + margin-left: 10px; + font-size: 12; .modes-select { // width: 170px; @@ -112,6 +114,8 @@ .modes-playground, .default-acl { display: flex; + margin-left: 10px; + font-size: 12; .playground-check, .acl-check { @@ -125,10 +129,12 @@ .playground-text { color: black; margin-right: 10px; + margin-top: 2; } .acl-text { color: black; + margin-top: 2; } } @@ -147,6 +153,7 @@ height: 20px; border: 0.5px solid black; border-radius: 5px; + padding-top: 3px; } } @@ -229,7 +236,7 @@ } .logout-button { - right: 35; + right: 355; position: absolute; } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index e3b91925a..b4778d3eb 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -56,7 +56,7 @@ export default class SettingsManager extends React.Component<{}> { @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice"); @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value); @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); - @undoBatch switchColor = action((color: ColorState) => Doc.UserDoc().defaultColor = String(color.hex)); + @undoBatch switchColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex)); @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; @@ -91,10 +91,10 @@ export default class SettingsManager extends React.Component<{}> { </div> <div className="preferences-font"> <div className="preferences-font-text">Default Font</div> - <select className="font-select" onChange={this.changeFontFamily}> + <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} > {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)} </select> - <select className="size-select" onChange={this.changeFontSize}> + <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}> {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)} </select> </div> diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index d50a132f8..48a3c023f 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -179,6 +179,9 @@ export default class SharingManager extends React.Component<{}> { if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } + /** + * Called from the properties sidebar to change permissions of a user. + */ shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, target: Doc) => { const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); if (user) this.setInternalSharing(user, permission, target); @@ -228,7 +231,6 @@ export default class SharingManager extends React.Component<{}> { const key = user.email.replace('.', '_'); const ACL = `ACL-${key}`; - target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target); if (permission !== SharingPermissions.None) { diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store Binary files differindex c379549d0..c6f3afa14 100644 --- a/src/client/views/.DS_Store +++ b/src/client/views/.DS_Store diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index eea133ed9..8c8bb6fde 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -122,15 +122,23 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T @action.bound removeDocument(doc: Doc | Doc[]): boolean { - const docs = doc instanceof Doc ? [doc] : doc; - docs.map(doc => doc.isPushpin = doc.annotationOn = undefined); - const targetDataDoc = this.dataDoc; - const value = DocListCast(targetDataDoc[this.annotationKey]); - const toRemove = value.filter(v => docs.includes(v)); - // can't assign new List<Doc>(result) to this because you can't assign new values in addonly - if (toRemove.length !== 0) { - toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.annotationKey, doc)); - return true; + const effectiveAcl = GetEffectiveAcl(this.dataDoc); + if (effectiveAcl === AclAdmin || effectiveAcl === AclEdit) { + const docs = doc instanceof Doc ? [doc] : doc; + docs.map(doc => doc.isPushpin = doc.annotationOn = undefined); + const targetDataDoc = this.dataDoc; + const value = DocListCast(targetDataDoc[this.annotationKey]); + const toRemove = value.filter(v => docs.includes(v)); + + if (toRemove.length !== 0) { + const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; + toRemove.forEach(doc => { + Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey + "-annotations", doc); + recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); + doc.deleted = true; + }); + return true; + } } return false; @@ -171,6 +179,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T added.map(doc => doc.context = this.props.Document); (targetDataDoc[this.annotationKey] as List<Doc>).push(...added); targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now())); + const lastModified = "lastModified"; + targetDataDoc[lastModified] = new DateField(new Date(Date.now())); } } } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 5401623e8..1e8cfdff4 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -21,14 +21,6 @@ $linkGap : 3px; background: none; } - - .documentDecorations-rotation { - pointer-events: auto; - cursor: alias; - width: 10px; - height: 10px; - } - .documentDecorations-resizer { pointer-events: auto; background: $alt-accent; @@ -76,11 +68,17 @@ $linkGap : 3px; grid-column-end: 7; } + #documentDecorations-rotation, #documentDecorations-borderRadius { - grid-column-start: 5; - grid-column-end: 7; + grid-column: 5; + grid-row: 4; border-radius: 100%; background: dimgray; + height: 8; + right: -12; + top: 12; + position: relative; + pointer-events: all; .borderRadiusTooltip { width: 10px; @@ -88,6 +86,11 @@ $linkGap : 3px; position: absolute; } } + #documentDecorations-rotation { + background: transparent; + right: -15; + } + #documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer { @@ -193,11 +196,11 @@ $linkGap : 3px; .documentDecorations-iconifyButton { opacity: 1; grid-column-start: 4; - grid-column-end: 5; + grid-column-end: 4; pointer-events: all; text-align: center; - left: -25px; - top: -2px; + right: 0; + top: 0; cursor: pointer; position: absolute; background: transparent; @@ -206,14 +209,11 @@ $linkGap : 3px; .documentDecorations-openInTab { opacity: 1; - grid-column-start: 4; + grid-column-start: 5; grid-column-end: 5; pointer-events: all; text-align: center; cursor: pointer; - width: 15px; - margin-left: -8px; - margin-top: auto; } .documentDecorations-closeButton { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index f1169763e..03746a1d2 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -197,17 +197,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @action onCloseClick = async (e: React.MouseEvent | undefined) => { if (!e?.button) { - const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = SelectionManager.SelectedDocuments().slice(); SelectionManager.DeselectAll(); - - selected.map(dv => { - const effectiveAcl = GetEffectiveAcl(dv.props.Document); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete - recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); - dv.props.removeDocument?.(dv.props.Document); - } - }); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); } } @action @@ -418,6 +410,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> this._snapX = thisPt.thisX; this._snapY = thisPt.thisY; let dragBottom = false; + let dragRight = false; let dX = 0, dY = 0, dW = 0, dH = 0; const unfreeze = () => SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => @@ -465,6 +458,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> case "documentDecorations-rightResizer": unfreeze(); dW = move[0]; + dragRight = true; break; } @@ -482,7 +476,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (nwidth / nheight !== width / height) { height = nheight / nwidth * width; } - if (!e.ctrlKey && (!dragBottom || !element.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (e.ctrlKey || (!dragBottom || !element.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } @@ -492,33 +486,34 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); const fixedAspect = (nwidth && nheight); + const fieldKey = Doc.LayoutFieldKey(doc); if (fixedAspect && (!nwidth || !nheight)) { - doc._nativeWidth = nwidth = doc._width || 0; - doc._nativeHeight = nheight = doc._height || 0; + doc[DataSym][fieldKey + "-nativeWidth"] = doc._nativeWidth = nwidth = doc._width || 0; + doc[DataSym][fieldKey + "-nativeHeight"] = doc._nativeHeight = nheight = doc._height || 0; } const anno = Cast(doc.annotationOn, Doc, null); if (e.ctrlKey && anno) { dW !== 0 && runInAction(() => { const dataDoc = anno[DataSym]; - const fieldKey = Doc.LayoutFieldKey(anno); - const nw = NumCast(dataDoc[fieldKey + "-nativeWidth"]); - const nh = NumCast(dataDoc[fieldKey + "-nativeHeight"]); - dataDoc[fieldKey + "-nativeWidth"] = nw + (dW > 0 ? 10 : -10); - dataDoc[fieldKey + "-nativeHeight"] = nh + (dW > 0 ? 10 : -10) * nh / nw; + const annoFieldKey = Doc.LayoutFieldKey(anno); + const nw = NumCast(dataDoc[annoFieldKey + "-nativeWidth"]); + const nh = NumCast(dataDoc[annoFieldKey + "-nativeHeight"]); + dataDoc[annoFieldKey + "-nativeWidth"] = nw + (dW > 0 ? 10 : -10); + dataDoc[annoFieldKey + "-nativeHeight"] = nh + (dW > 0 ? 10 : -10) * nh / nw; }); } else if (nwidth > 0 && nheight > 0) { - if (Math.abs(dW) > Math.abs(dH)) { - if (!fixedAspect || e.ctrlKey) { - doc._nativeWidth = actualdW / (doc._width || 1) * (doc._nativeWidth || 0); + if (Math.abs(dW) > Math.abs(dH) || dragRight) { + if (!fixedAspect || (dragRight && e.ctrlKey)) { + doc[DataSym][fieldKey + "-nativeWidth"] = doc._nativeWidth = actualdW / (doc._width || 1) * (doc._nativeWidth || 0); } doc._width = actualdW; if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width; else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH; } else { - if (!fixedAspect || e.ctrlKey || (dragBottom && element.layoutDoc._fitWidth)) { - doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0); + if (!fixedAspect || (dragBottom && (e.ctrlKey || element.layoutDoc._fitWidth))) { + doc[DataSym][fieldKey + "-nativeHeight"] = doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0); } doc._height = actualdH; if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height; @@ -612,9 +607,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return (null); } const canDelete = SelectionManager.SelectedDocuments().some(docView => { - const docAcl = GetEffectiveAcl(docView.props.Document); - const collectionAcl = GetEffectiveAcl(docView.props.ContainingCollectionDoc); - return [docAcl, collectionAcl].some(acl => [AclAdmin, AclEdit].includes(acl)); + const collectionAcl = GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]); + return collectionAcl === AclAdmin || collectionAcl === AclEdit; }); const minimal = bounds.r - bounds.x < 100 ? true : false; const maximizeIcon = minimal ? ( @@ -622,7 +616,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}> <FontAwesomeIcon size="lg" icon="cog" /> </div></Tooltip>) : canDelete ? ( - <Tooltip title={<><div className="dash-tooltip">Delete</div></>} placement="top"> + <Tooltip title={<><div className="dash-tooltip">Close</div></>} placement="top"> <div className="documentDecorations-closeButton" onClick={this.onCloseClick}> {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" /> @@ -666,13 +660,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight); } var offset = 0; - var rotButton = <></>; - //make offset larger for ink to edit points - if (seldoc.rootDoc.type === DocumentType.INK) { - offset = 20; - rotButton = <div id="documentDecorations-rotation" title="rotate" className="documentDecorations-rotation" - onPointerDown={this.onRotateDown}> ⟲ </div>; - } + let useRotation = seldoc.rootDoc.type === DocumentType.INK; return (<div className="documentDecorations" style={{ background: darkScheme }} > <div className="documentDecorations-background" style={{ @@ -696,12 +684,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></>} placement="top"> <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}> - {"_"} + <FontAwesomeIcon icon={seldoc.finalLayoutKey.includes("icon") ? "window-restore" : "window-minimize"} className="documentView-minimizedIcon" /> </div></Tooltip>} - <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}> + <Tooltip title={<><div className="dash-tooltip">Open In a New Pane</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}> {SelectionManager.SelectedDocuments().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."} </div></Tooltip> - {rotButton} <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-topResizer" className="documentDecorations-resizer" @@ -725,8 +712,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}> <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" /> </div></Tooltip>} - <div id="documentDecorations-borderRadius" className="documentDecorations-radius" - onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div> + <div id={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`} + onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div> </div > <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}> diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 81bb542dd..76e786257 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -678,8 +678,8 @@ export default class GestureOverlay extends Touchable { var top = Math.min(...ys); const firstx = this._points[0].X; const firsty = this._points[0].Y; - const lastx = this._points[this._points.length - 2].X; - const lasty = this._points[this._points.length - 2].Y; + var lastx = this._points[this._points.length - 2].X; + var lasty = this._points[this._points.length - 2].Y; var fourth = (lastx - firstx) / 4; if (isNaN(fourth) || fourth === 0) { fourth = 0.01; } var m = (lasty - firsty) / (lastx - firstx); @@ -771,11 +771,17 @@ export default class GestureOverlay extends Touchable { break; case "line": + if (Math.abs(firstx - lastx) < 20) { + lastx = firstx; + } + if (Math.abs(firsty - lasty) < 20) { + lasty = firsty; + } this._points.push({ X: firstx, Y: firsty }); this._points.push({ X: firstx, Y: firsty }); - this._points.push({ X: firstx + 4 * fourth, Y: m * (firstx + 4 * fourth) + b }); - this._points.push({ X: firstx + 4 * fourth, Y: m * (firstx + 4 * fourth) + b }); + this._points.push({ X: lastx, Y: lasty }); + this._points.push({ X: lastx, Y: lasty }); break; case "arrow": const x1 = left; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 182f6397c..be6aa6be2 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -1,6 +1,6 @@ import { action } from "mobx"; import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast, AclEdit, AclAdmin } from "../../fields/Doc"; +import { Doc, DocListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -23,7 +23,6 @@ import PDFMenu from "./pdf/PDFMenu"; import { ContextMenu } from "./ContextMenu"; import GroupManager from "../util/GroupManager"; import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; -import { GetEffectiveAcl } from "../../fields/util"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>; @@ -120,16 +119,9 @@ export default class KeyManager { } } - const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = SelectionManager.SelectedDocuments().slice(); UndoManager.RunInBatch(() => { - selected.map(dv => { - const effectiveAcl = GetEffectiveAcl(dv.props.Document); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete - recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); - dv.props.removeDocument?.(dv.props.Document); - } - }); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); }, "delete"); SelectionManager.DeselectAll(); break; @@ -331,6 +323,8 @@ export default class KeyManager { undoBatch(() => { targetDataDoc[fieldKey] = new List<Doc>([...docList, ...added]); targetDataDoc[fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + const lastModified = "lastModified"; + targetDataDoc[lastModified] = new DateField(new Date(Date.now())); })(); } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index e3390426b..41311ed86 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -43,7 +43,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume this.props.Document._backgroundColor = "rgba(0,0,0,0.7)"; this.props.Document.mixBlendMode = "hard-light"; this.props.Document.color = "#9b9b9bff"; - this.props.Document.stayInCollection = true; + this.props.Document._stayInCollection = true; this.props.Document.isInkMask = true; } diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index f3fba82bc..a05a2b858 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -4,6 +4,8 @@ .dash-tooltip { font-size: 11px; padding: 2px; + max-width: 150; + line-height: 150%; } .mainView-tabButtons { @@ -157,7 +159,7 @@ width: 60px; background-color: #121721; - height: calc(100% - 32px); + height: calc(100% - $searchpanel-height); //overflow-y: scroll; //overflow-x: hidden; @@ -215,7 +217,7 @@ .mainView-searchPanel { width: 100%; - height: 32px; + height: $searchpanel-height; background-color: black; color: white; text-align: center; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4d5dfc99e..059e1f566 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -41,16 +41,14 @@ import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import GestureOverlay from './GestureOverlay'; -import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss'; +import { ANTIMODEMENU_HEIGHT, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss'; import KeyManager from './GlobalKeyHandler'; import { LinkMenu } from './linking/LinkMenu'; import "./MainView.scss"; -import { MainViewNotifs } from './MainViewNotifs'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import RichTextMenu from './nodes/formattedText/RichTextMenu'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview } from './nodes/LinkDocPreview'; import { RadialMenu } from './nodes/RadialMenu'; @@ -59,10 +57,14 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { Hypothesis } from '../util/HypothesisUtils'; -import { undoBatch } from '../util/UndoManager'; import { WebBox } from './nodes/WebBox'; import * as ReactDOM from 'react-dom'; import { SearchBox } from './search/SearchBox'; +import { SearchUtil } from '../util/SearchUtil'; +import { Networking } from '../Network'; +import * as rp from 'request-promise'; +import { LinkManager } from '../util/LinkManager'; +import RichTextMenu from './nodes/formattedText/RichTextMenu'; @observer export class MainView extends React.Component { @@ -164,7 +166,7 @@ export class MainView extends React.Component { } } - library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, + library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight, @@ -180,7 +182,8 @@ export class MainView extends React.Component { fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper, fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp, - fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer); + fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, + fa.faWindowMinimize, fa.faWindowRestore); this.initEventListeners(); this.initAuthenticationRouters(); } @@ -199,7 +202,7 @@ export class MainView extends React.Component { let check = false; const icon = "icon"; targets.forEach((thing) => { - if (thing.className.toString() === "collectionSchemaView-table" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "beta" || thing.className.toString() === "collectionSchemaView-menuOptions-wrapper") { + if (thing.className.toString() === "collectionSchemaView-searchContainer" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "collectionSchema-header-menuOptions" || thing.className.toString() === "altcollectionTimeView-treeView") { check = true; } }); @@ -258,7 +261,7 @@ export class MainView extends React.Component { y: 400, _width: this._panelWidth * .7 - this.propertiesWidth() * 0.7, _height: this._panelHeight, - title: "Collection " + workspaceCount, + title: "Untitled Collection", }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row"); @@ -307,11 +310,7 @@ export class MainView extends React.Component { DocServer.Control.makeEditable(); } } - // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized) - setTimeout(async () => { - const col = this.userDoc && await Cast(this.userDoc["sidebar-sharing"], Doc); - col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col); - }, 100); + return true; } @@ -332,7 +331,7 @@ export class MainView extends React.Component { getPHeight = () => this._panelHeight; getContentsHeight = () => this._panelHeight - this._buttonBarHeight; - defaultBackgroundColors = (doc: Opt<Doc>) => { + defaultBackgroundColors = (doc: Opt<Doc>, renderDepth: number) => { if (this.panelContent === doc?.title) return "lightgrey"; if (doc?.type === DocumentType.COL) { @@ -342,7 +341,7 @@ export class MainView extends React.Component { || doc.title === "Advanced Item Prototypes" || doc.title === "all Creators") { return "lightgrey"; } - return StrCast(Doc.UserDoc().defaultColor); + return StrCast(renderDepth > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground); } if (this.darkScheme) { switch (doc?.type) { @@ -402,7 +401,7 @@ export class MainView extends React.Component { TraceMobx(); const mainContainer = this.mainContainer; const width = this.flyoutWidth + this.propertiesWidth(); - return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - 32px)` }}> + return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - ${SEARCH_PANEL_HEIGHT})` }}> {!mainContainer ? (null) : this.mainDocView} </div>; } @@ -439,15 +438,13 @@ export class MainView extends React.Component { doc.dockingConfig ? this.openWorkspace(doc) : CollectionDockingView.AddRightSplit(doc, libraryPath); } - sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0), 1); - //sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1); - mainContainerXf = () => this.sidebarScreenToLocal().translate(-55, -this._buttonBarHeight); + sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0) - Number(SEARCH_PANEL_HEIGHT.replace("px", "")), 1); + mainContainerXf = () => this.sidebarScreenToLocal().translate(-58, 0); - @computed get closePosition() { return 55 + this.flyoutWidth; } @computed get flyout() { if (!this.sidebarContent) return null; return <div className="mainView-libraryFlyout"> - <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - 32px)`, width: "100%", overflow: "visible" }}> + <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${SEARCH_PANEL_HEIGHT})`, width: "100%", overflow: "visible" }}> {/* {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close" onPointerDown={this.closeFlyout}> <FontAwesomeIcon icon="times" color="black" size="lg" /> @@ -531,22 +528,24 @@ export class MainView extends React.Component { _lastButton: Doc | undefined; @action - selectMenu = (button: Doc, str: string) => { + selectMenu = (button: Doc) => { + const title = StrCast(Doc.GetProto(button).title); this._lastButton && (this._lastButton.color = "white"); this._lastButton && (this._lastButton._backgroundColor = ""); - if (this.panelContent === str && this.flyoutWidth !== 0) { + if (this.panelContent === title && this.flyoutWidth !== 0) { this.panelContent = "none"; this.flyoutWidth = 0; } else { let panelDoc: Doc | undefined; - switch (this.panelContent = str) { + switch (this.panelContent = title) { case "Tools": panelDoc = Doc.UserDoc()["sidebar-tools"] as Doc ?? undefined; break; case "Workspace": panelDoc = Doc.UserDoc()["sidebar-workspaces"] as Doc ?? undefined; break; case "Catalog": panelDoc = Doc.UserDoc()["sidebar-catalog"] as Doc ?? undefined; break; case "Archive": panelDoc = Doc.UserDoc()["sidebar-recentlyClosed"] as Doc ?? undefined; break; case "Settings": SettingsManager.Instance.open(); break; + case "Import": panelDoc = Doc.UserDoc()["sidebar-import"] as Doc ?? undefined; break; case "Sharing": panelDoc = Doc.UserDoc()["sidebar-sharing"] as Doc ?? undefined; break; - case "UserDoc": panelDoc = Doc.UserDoc()["sidebar-userDoc"] as Doc ?? undefined; break; + case "User Doc": panelDoc = Doc.UserDoc()["sidebar-userDoc"] as Doc ?? undefined; break; } this.sidebarContent.proto = panelDoc; if (panelDoc) { @@ -610,7 +609,6 @@ export class MainView extends React.Component { </div> </div> {this.dockingContent} - <MainViewNotifs /> {this.showProperties ? (null) : <div className="mainView-propertiesDragger" title="Properties View Dragger" onPointerDown={this.onPropertiesPointerDown} style={{ right: rightFlyout, top: "50%" }}> @@ -819,8 +817,8 @@ export class MainView extends React.Component { <DocumentDecorations /> {this.search} <CollectionMenu /> - <FormatShapePane /> <div style={{ display: "none" }}><RichTextMenu key="rich" /></div> + <FormatShapePane /> {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null} {DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)} {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors} @@ -895,7 +893,83 @@ export class MainView extends React.Component { document.addEventListener("editSuccess", onSuccess); }); } + + importDocument = () => { + const sidebar = Cast(Doc.UserDoc()["sidebar-import-documents"], Doc, null); + const sidebarDocView = DocumentManager.Instance.getDocumentView(sidebar); + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.accept = ".zip, application/pdf, video/*, image/*, audio/*"; + input.onchange = async _e => { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + const file = input.files && input.files[0]; + if (file && file.type === 'application/zip') { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc && sidebarDocView) { + sidebarDocView.props.addDocument?.(doc); + setTimeout(() => { + SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { + docs.docs.forEach(d => LinkManager.Instance.addLink(d)); + }); + }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. + + } + } + } else if (input.files && input.files.length !== 0) { + const files = input.files || []; + Array.from(files).forEach(async file => { + const res = await Networking.UploadFilesToServer(file); + res.map(async ({ result }) => { + const name = file.name; + if (result instanceof Error) { + return; + } + const path = Utils.prepend(result.accessPaths.agnostic.client); + let doc: Doc; + // Case 1: File is a video + if (file.type.includes("video")) { + doc = Docs.Create.VideoDocument(path, { _height: 100, title: name }); + // Case 2: File is a PDF document + } else if (file.type === "application/pdf") { + doc = Docs.Create.PdfDocument(path, { _height: 100, _fitWidth: true, title: name }); + // Case 3: File is an image + } else if (file.type.includes("image")) { + doc = Docs.Create.ImageDocument(path, { _height: 100, title: name }); + // Case 4: File is an audio document + } else { + doc = Docs.Create.AudioDocument(path, { title: name }); + } + const res = await rp.get(Utils.prepend("/getUserDocumentId")); + if (!res) { + throw new Error("No user id returned"); + } + const field = await DocServer.GetRefField(res); + let pending: Opt<Doc>; + if (field instanceof Doc) { + pending = sidebar; + } + if (pending) { + const data = await Cast(pending.data, listSpec(Doc)); + if (data) data.push(doc); + else pending.data = new List([doc]); + } + }); + }); + } else { + console.log("No file selected"); + } + }; + input.click(); + } } +Scripting.addGlobal(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); }); Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); }); Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }); Scripting.addGlobal(function copyWorkspace() { @@ -905,3 +979,5 @@ Scripting.addGlobal(function copyWorkspace() { // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0); }); +Scripting.addGlobal(function importDocument() { return MainView.Instance.importDocument(); }, + "imports files from device directly into the import sidebar"); diff --git a/src/client/views/MainViewNotifs.scss b/src/client/views/MainViewNotifs.scss deleted file mode 100644 index 92d7d6ee3..000000000 --- a/src/client/views/MainViewNotifs.scss +++ /dev/null @@ -1,20 +0,0 @@ -.mainNotifs-container { - position:absolute; - z-index: 1000; - top: 12px; - - .mainNotifs-badge { - position: absolute; - top: -10px; - right: -10px; - color: white; - background: #f44b42; - font-weight: 300; - border-radius: 100%; - width: 25px; - height: 25px; - text-align: center; - padding-top: 4px; - font-size: 12px; - } -}
\ No newline at end of file diff --git a/src/client/views/MainViewNotifs.tsx b/src/client/views/MainViewNotifs.tsx deleted file mode 100644 index 89006ebc8..000000000 --- a/src/client/views/MainViewNotifs.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { observable } from 'mobx'; -import { observer } from 'mobx-react'; -import "normalize.css"; -import * as React from 'react'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { returnFalse, setupMoveUpEvents } from '../../Utils'; -import { DragManager } from '../util/DragManager'; -import "./MainViewNotifs.scss"; -import { MainView } from './MainView'; -import { NumCast } from '../../fields/Types'; - - -@observer -export class MainViewNotifs extends React.Component { - @observable static NotifsCol: Opt<Doc>; - _notifsRef = React.createRef<HTMLDivElement>(); - - onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const dragData = new DragManager.DocumentDragData([MainViewNotifs.NotifsCol!]); - DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - return true; - }, - returnFalse, - () => MainViewNotifs.NotifsCol && MainView.Instance.selectMenu(MainViewNotifs.NotifsCol, "Sharing")); - } - - render() { - const length = MainViewNotifs.NotifsCol ? DocListCast(MainViewNotifs.NotifsCol.data).length : 0; - return <div className="mainNotifs-container" style={{ width: 15, height: 15, top: 12 + NumCast(MainViewNotifs.NotifsCol?.position) * 60 }} ref={this._notifsRef}> - <button className="mainNotifs-badge" style={length > 0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} - </button> - </div>; - } -} diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 09a349012..555f4298d 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -44,6 +44,6 @@ } .overlayView-doc { - z-index: 1; + z-index: 9002; //so that it appears above chroma position: absolute; }
\ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 5c3a8185c..49580cde4 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -182,7 +182,7 @@ export class OverlayView extends React.Component { offsetx = NumCast(d.x) - e.clientX; offsety = NumCast(d.y) - e.clientY; }; - return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}> + return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ top: d.type === 'presentation' ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}> <DocumentView Document={d} LibraryPath={emptyPath} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 5e25ead87..35d4f7f6e 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -3,7 +3,7 @@ import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faChec import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, AclEdit, AclAdmin } from "../../fields/Doc"; +import { Doc } from "../../fields/Doc"; import { RichTextField } from '../../fields/RichTextField'; import { Cast, NumCast, BoolCast } from "../../fields/Types"; import { emptyFunction, setupMoveUpEvents, Utils } from "../../Utils"; @@ -30,7 +30,6 @@ import { undoBatch, UndoManager } from '../util/UndoManager'; import { DocumentType } from '../documents/DocumentTypes'; import { InkField } from '../../fields/InkField'; import { PresBox } from './nodes/PresBox'; -import { GetEffectiveAcl } from "../../fields/util"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -417,14 +416,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { get deleteButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Delete Document"}</div></>} placement="top"> + title={<><div className="dash-tooltip">Close Document</div></>} placement="top"> <div> <div className={"propertiesButtons-linkButton-empty"} onPointerDown={this.deleteDocument}> {<FontAwesomeIcon className="propertiesButtons-icon" - icon="trash-alt" size="lg" />} + icon="times" size="lg" />} </div> - <div className="propertiesButtons-title"> delete </div> + <div className="propertiesButtons-title"> close </div> </div> </Tooltip>; } @@ -432,16 +431,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action deleteDocument = () => { - const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = SelectionManager.SelectedDocuments().slice(); - - selected.map(dv => { - const effectiveAcl = GetEffectiveAcl(dv.props.Document); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete - recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); - dv.props.removeDocument?.(dv.props.Document); - } - }); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); this.selectedDoc && (this.selectedDoc.deleted = true); this.selectedDocumentView?.props.ContainingCollectionView?.removeDocument(this.selectedDocumentView?.props.Document); SelectionManager.DeselectAll(); @@ -648,7 +639,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { this.selectedDoc._backgroundColor = "rgba(0,0,0,0.7)"; this.selectedDoc.mixBlendMode = "hard-light"; this.selectedDoc.color = "#9b9b9bff"; - this.selectedDoc.stayInCollection = true; + this.selectedDoc._stayInCollection = true; this.selectedDoc.isInkMask = true; } } @@ -718,6 +709,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const isInk = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof InkField; const isCollection = this.selectedDoc.type === DocumentType.COL ? true : false; const isFreeForm = this.selectedDoc._viewType === "freeform" ? true : false; + const hasContext = this.selectedDoc.context ? true : false; return <div><div className="propertiesButtons" style={{ paddingBottom: "5.5px" }}> <div className="propertiesButtons-button"> @@ -732,7 +724,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button"> {this.pinWithViewButton} </div> - <div className="propertiesButtons-button"> + <div className="propertiesButtons-button" style={{ display: hasContext ? "" : "none" }}> {this.copyButton} </div> <div className="propertiesButtons-button"> @@ -750,6 +742,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button"> {this.sharingButton} </div> + <div className="propertiesButtons-button"> + {this.contextButton} + </div> <div className="propertiesButtons-button" style={{ display: !considerPush ? "none" : "" }}> {this.considerGoogleDocsPush} </div> @@ -774,9 +769,6 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}> {this.maskButton} </div> - <div className="propertiesButtons-button"> - {this.contextButton} - </div> </div> </div>; } diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index 1eb380e0b..db087fb23 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -104,7 +104,7 @@ export class ScriptingRepl extends React.Component { if (ts.isParameter(node.parent)) { // delete knownVars[node.text]; } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) { - const match = node.text.match(/\d([0-9]+)/); + const match = node.text.match(/d([0-9]+)/); if (match) { const m = parseInt(match[1]); usedDocuments.push(m); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 27aea4b99..8a27f8102 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -64,7 +64,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) </div> <div className="collectionCarouselView-caption" key="caption" style={{ - background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.backgroundColor?.(this.props.Document)), + background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)), color: StrCast(this.layoutDoc._captionColor, StrCast(this.layoutDoc.color)), borderRadius: StrCast(this.layoutDoc._captionBorderRounding), }}> diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 4204ef5bb..6ebd5103b 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -20,82 +20,6 @@ } } -.miniPres:hover { - opacity: 1; -} - -.miniPres { - position: absolute; - overflow: hidden; - right: 10; - top: 10; - opacity: 0.1; - transition: all 0.4s; - /* border: solid 1px; */ - color: white; - /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ - - .miniPresOverlay { - display: grid; - grid-template-columns: auto auto auto auto auto auto auto auto; - grid-template-rows: 100%; - height: 100%; - justify-items: center; - align-items: center; - - .miniPres-button-text { - display: flex; - height: 20; - font-weight: 400; - min-width: 100%; - border-radius: 5px; - align-items: center; - justify-content: center; - transition: all 0.3s; - } - - .miniPres-button-frame { - justify-self: center; - align-self: center; - align-items: center; - display: grid; - grid-template-columns: auto auto auto; - justify-content: space-around; - font-size: 11; - margin-left: 7; - width: 30; - height: 85%; - background-color: rgba(91, 157, 221, 0.4); - border-radius: 5px; - } - - .miniPres-divider { - width: 0.5px; - height: 80%; - border-right: solid 2px #5a5a5a; - } - - .miniPres-button { - display: flex; - height: 20; - min-width: 20; - border-radius: 100%; - align-items: center; - justify-content: center; - transition: all 0.3s; - } - - .miniPres-button:hover { - background-color: #5a5a5a; - } - - .miniPres-button-text:hover { - background-color: #5a5a5a; - } - } -} - - .lm_title { margin-top: 3px; border-radius: 5px; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 3691e844f..43da0d3cf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -191,31 +191,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp return retVal; } - @undoBatch - @action - public static ReplaceTab(document: Doc, stack: any): Opt<Doc> { - if (!CollectionDockingView.Instance) return undefined; - const instance = CollectionDockingView.Instance; - const replaceTab = (doc: Doc, child: any): Opt<Doc> => { - for (const contentItem of child.contentItems) { - const { config, isStack, isRow, isColumn } = contentItem; - if (isRow || isColumn || isStack) { - const val = replaceTab(doc, contentItem); - if (val) return val; - } else if (config.component === "DocumentFrameRenderer" && - config.props.documentId === doc[Id]) { - const alias = Doc.MakeAlias(doc); - config.props.documentId = alias[Id]; - config.title = alias.title; - instance.stateChanged(); - return alias; - } - } - return undefined; - }; - return replaceTab(document, instance._goldenLayout.root); - } - // // Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split // @@ -358,6 +333,32 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp return true; } + @undoBatch + @action + public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => { + Doc.GetProto(document).lastOpened = new DateField; + const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath); + if (stack === undefined) { + let stack: any = this._goldenLayout.root; + while (!stack.isStack) { + if (stack.contentItems.length) { + stack = stack.contentItems[0]; + } else { + stack.addChild({ type: 'stack', content: [docContentConfig] }); + stack = undefined; + break; + } + } + if (stack) { + stack.addChild(docContentConfig); + } + } else { + stack.addChild(docContentConfig, undefined); + } + this.layoutChanged(); + return true; + } + setupGoldenLayout() { const config = StrCast(this.props.Document.dockingConfig); if (config) { @@ -727,9 +728,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { pinDoc.presZoomButton = true; pinDoc.context = curPres; Doc.AddDocToList(curPres, "data", pinDoc); + if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; if (!DocumentManager.Instance.getDocumentView(curPres)) { CollectionDockingView.AddRightSplit(curPres); } + DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null)); } } } @@ -758,7 +761,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); - this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, "white") }), + this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white") }), (data) => { const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document)); this._tab && (this._tab.style.backgroundColor = selected ? data.color : ""); @@ -821,7 +824,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { if (this._mainCont && this._mainCont.children) { const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement); const scale = Utils.GetScreenTransform(this._mainCont).scale; - return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); + return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); } return Transform.Identity(); } @@ -837,12 +840,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { } else if (location === "close") { return CollectionDockingView.CloseRightSplit(doc); } else if (location === "replace") { - const alias = CollectionDockingView.ReplaceTab(doc, this._stack); - if (alias) { - runInAction(() => this._document = alias); - return true; - } - return false; + CollectionDockingView.UseRightSplit(doc); + return true; } else {// if (location === "inPlace") { return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath); } @@ -873,30 +872,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { const currentFrame = Cast(presTargetDoc.currentFrame, "number", null); return currentFrame; } - renderMiniPres() { - return ( - <div className="miniPres" - style={{ width: 250, height: 30, background: '#323232' }} - > - {<div className="miniPresOverlay"> - <div className="miniPres-button" onClick={PresBox.Instance.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="miniPres-button" onClick={() => PresBox.Instance.startAutoPres(PresBox.Instance.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> - <div className="miniPres-button" onClick={PresBox.Instance.next}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text"> - Slide {PresBox.Instance.itemIndex + 1} / {PresBox.Instance.childDocs.length} - {PresBox.Instance.playButtonFrames} - </div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text" onClick={PresBox.Instance.updateMinimize}>EXIT</div> - </div>} - </div> - ); - } + renderMiniMap() { return <div className="miniMap" style={{ width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor, - StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!))), + StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!, 0))), }}> <CollectionFreeFormView Document={this._document!} @@ -975,7 +955,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)} - {document._viewType === CollectionViewType.Freeform && this._document?.miniPres ? this.renderMiniPres() : (null)} </>; } diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 3cf46dbed..e1b07077e 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -119,7 +119,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)" }} onPointerDown={e => e.stopPropagation()} > - <p>+</p> + <p>{BoolCast(this.props.Document.linearViewIsExpanded) ? "–" : "+"}</p> </label>; return <div className="collectionLinearView-outer"> diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index b41cbe92d..8658212b7 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -315,6 +315,7 @@ display: inline-flex; position: relative; align-items: center; + margin-left: 10px; .antimodeMenu-button { text-align: center; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 53d2a136e..5119ff6c9 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -85,7 +85,8 @@ export default class CollectionMenu extends AntimodeMenu { const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom"> - <button className="antimodeMenu-button" key="properties" onPointerDown={this.toggleProperties}> + <button className="antimodeMenu-button" key="properties" style={{ backgroundColor: "#424242" }} + onPointerDown={this.toggleProperties}> <FontAwesomeIcon icon={propIcon} size="lg" /> </button> </Tooltip>; @@ -580,9 +581,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })} style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}> {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */} - <div className="color-previewII" style={{ backgroundColor: color }} /> - {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""} - + <div className="color-previewII" style={{ backgroundColor: color }}> + {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""} + </div> </button>)} </div>; } @@ -622,7 +623,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div> </Tooltip> : null} {!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> - <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }} + <div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }} onClick={action(() => this.document.editing = !this.document.editing)} > {NumCast(this.document.currentFrame)} </div> @@ -669,7 +670,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu @computed get pivotField() { return StrCast(this.document._pivotField); } getKeySuggestions = async (value: string): Promise<string[]> => { - value = value.toLowerCase(); + const val = value.toLowerCase(); const docs = DocListCast(this.document[this.props.fieldKey]); if (Doc.UserDoc().noviceMode) { @@ -677,7 +678,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); - return keys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + return keys.filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set<string>(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); @@ -685,16 +686,16 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); - return noviceKeys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } } if (docs instanceof Doc) { - return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value)); + return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set<string>(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys).filter(key => key.toLowerCase().startsWith(value)); + return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); } } @@ -735,8 +736,10 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu @action resetValue = () => { this._currentKey = this.pivotField; }; render() { + const doctype = this.props.docView.Document.type; + const isPres: boolean = (doctype === DocumentType.PRES); return ( - <div className="collectionStackingViewChrome-cont"> + isPres ? (null) : <div className="collectionStackingViewChrome-cont"> <div className="collectionStackingViewChrome-pivotField-cont"> <div className="collectionStackingViewChrome-pivotField-label"> GROUP BY: diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 8a1dd8472..20ce6b76d 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, observable, trace, computed } from "mobx"; +import { action, observable, trace, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import { CellInfo } from "react-table"; import "react-table/react-table.css"; @@ -33,6 +33,8 @@ import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { DateField } from "../../../fields/DateField"; import { RichTextField } from "../../../fields/RichTextField"; +import { DocumentManager } from "../../util/DocumentManager"; +import { SearchUtil } from "../../util/SearchUtil"; const path = require('path'); library.add(faExpand); @@ -67,10 +69,29 @@ export class CollectionSchemaCell extends React.Component<CellProps> { protected _document = this.props.rowProps.original; protected _dropDisposer?: DragManager.DragDropDisposer; - componentDidMount() { + async componentWillMount() { + + } + + async componentDidMount() { document.addEventListener("keydown", this.onKeyDown); + console.log("mounted"); + console.log(this.type); + if (this.type === "context") { + console.log("mounted2"); + const doc = Doc.GetProto(this.props.rowProps.original); + const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc); + if (aliasdoc.length > 0) { + const targetContext = Cast(aliasdoc[0].context, Doc) as Doc; + console.log(StrCast(targetContext.title)); + runInAction(() => this.contents = StrCast(targetContext.title)); + } + } + } + @observable contents: string = ""; + componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown); } @@ -159,6 +180,31 @@ export class CollectionSchemaCell extends React.Component<CellProps> { // e.stopPropagation(); // } + returnHighlights(bing: (() => string), positions?: number[]) { + const results = []; + const contents = bing(); + + if (positions !== undefined) { + StrCast(this.props.Document._searchString); + const length = StrCast(this.props.Document._searchString).length; + const color = contents ? "black" : "grey"; + + results.push(<span key="-1" style={{ color }}>{contents?.slice(0, positions[0])}</span>); + positions.forEach((num, cur) => { + results.push(<span key={"start" + cur} style={{ backgroundColor: "#FFFF00", color }}>{contents?.slice(num, num + length)}</span>); + let end = 0; + cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; + results.push(<span key={"end" + cur} style={{ color }}>{contents?.slice(num + length, end)}</span>); + } + ); + return results; + } + else { + return <span style={{ color: contents ? "black" : "grey" }}>{contents ? contents?.valueOf() : "undefined"}</span>; + } + } + type: string = ""; + renderCellWithType(type: string | undefined) { const dragRef: React.RefObject<HTMLDivElement> = React.createRef(); @@ -200,12 +246,23 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const doc = FieldValue(Cast(field, Doc)); const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); - const onItemDown = (e: React.PointerEvent) => { - //fieldIsDoc && - SetupDrag(this._focusRef, - () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, - this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + const onItemDown = async (e: React.PointerEvent) => { + if (this.props.Document._searchDoc !== undefined) { + const doc = Doc.GetProto(this.props.rowProps.original); + const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc); + let targetContext = undefined; + if (aliasdoc.length > 0) { + targetContext = Cast(aliasdoc[0].context, Doc) as Doc; + } + DocumentManager.Instance.jumpToDocument(this.props.rowProps.original, false, undefined, targetContext); + } + else { + fieldIsDoc && + SetupDrag(this._focusRef, + () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, + this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, + this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + } }; const onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) { @@ -250,20 +307,10 @@ export class CollectionSchemaCell extends React.Component<CellProps> { // </div> // ); const positions = []; + let cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); + this.type = props.fieldKey; if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { - const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - let term = ""; - if (cfield !== undefined) { - if (cfield.Text !== undefined) { - term = cfield.Text; - } - else if (StrCast(cfield)) { - term = StrCast(cfield); - } - else { - term = String(NumCast(cfield)); - } - } + let term = Field.toString(cfield as Field); term = term.toLowerCase(); const search = StrCast(this.props.Document._searchString).toLowerCase(); let start = term.indexOf(search); @@ -281,89 +328,123 @@ export class CollectionSchemaCell extends React.Component<CellProps> { positions.pop(); } } + let search = false; + if (this.props.Document._searchDoc !== undefined) { + search = true; + } + return ( <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}> - <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}> - <EditableView - positions={positions.length > 0 ? positions : undefined} - search={StrCast(this.props.Document._searchString) ? StrCast(this.props.Document._searchString) : undefined} - editing={this._isEditing} - isEditingCallback={this.isEditingCallback} - display={"inline"} - contents={contents ? contents : type === "number" ? "0" : "undefined"} - highlight={positions.length > 0 ? true : undefined} - //contents={StrCast(contents)} - height={"auto"} - maxHeight={Number(MAX_ROW_HEIGHT)} - placeholder={"enter value"} - bing={() => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (cfield !== undefined) { - console.log(typeof (cfield)); - // if (typeof(cfield)===RichTextField) - const a = cfield as RichTextField; - if (a.Text !== undefined) { - return (a.Text); - } - else if (StrCast(cfield)) { - return StrCast(cfield); - } - else { - return String(NumCast(cfield)); - } - } - }} - GetValue={() => { - if (type === "number" && (contents === 0 || contents === "0")) { - return "0"; - } else { + <div className="collectionSchemaView-cellContents" + ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}> + {!search ? + <EditableView + positions={positions.length > 0 ? positions : undefined} + search={StrCast(this.props.Document._searchString) ? StrCast(this.props.Document._searchString) : undefined} + editing={this._isEditing} + isEditingCallback={this.isEditingCallback} + display={"inline"} + contents={contents ? contents : type === "number" ? "0" : "undefined"} + highlight={positions.length > 0 ? true : undefined} + //contents={StrCast(contents)} + height={"auto"} + maxHeight={Number(MAX_ROW_HEIGHT)} + placeholder={"undefined"} + bing={() => { const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (type === "number") { - return StrCast(cfield); + if (cfield !== undefined) { + // if (typeof(cfield)===RichTextField) + const a = cfield as RichTextField; + const b = cfield as DateField; + console.log(b); + if (a.Text !== undefined) { + return (a.Text); + } + else if (b.toString() !== undefined) { + return b.toString(); + } + else if (StrCast(cfield)) { + return StrCast(cfield); + } + else { + return String(NumCast(cfield)); + } } - const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; - const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; - const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : - Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; - return val; + }} + GetValue={() => { + if (type === "number" && (contents === 0 || contents === "0")) { + return "0"; + } else { + const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); + if (type === "number") { + return StrCast(cfield); + } + const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; + const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; + const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : + Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; + return val; - } + } - }} - SetValue={action((value: string) => { - let retVal = false; + }} + SetValue={action((value: string) => { + let retVal = false; + + if (value.startsWith(":=")) { + retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col); + } else { + const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + if (script.compiled) { + retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run); + } - if (value.startsWith(":=")) { - retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col); - } else { + } + if (retVal) { + this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' + this.props.setIsEditing(false); + } + return retVal; + + //return true; + })} + OnFillDown={async (value: string) => { const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); if (script.compiled) { - retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run); + DocListCast(this.props.Document[this.props.fieldKey]). + forEach((doc, i) => value.startsWith(":=") ? + this.props.setComputed(value.substring(2), doc, this.props.rowProps.column.id!, i, this.props.col) : + this.applyToDoc(doc, i, this.props.col, script.run)); } - + }} + /> + : + this.returnHighlights(() => { + console.log(props.fieldKey); + const dateCheck: Date | undefined = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date : undefined; + if (dateCheck !== undefined) { + cfield = dateCheck.toLocaleString(); } - if (retVal) { - this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' - this.props.setIsEditing(false); + if (props.fieldKey === "context") { + cfield = this.contents; + console.log("this should work"); } - return retVal; + if (props.fieldKey === "*lastModified") { + if (FieldValue(props.Document["data-lastModified"]) !== undefined) { + const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["data-lastModified"])) as DateField; + cfield = d.date.toLocaleString(); + } - //return true; - })} - OnFillDown={async (value: string) => { - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - if (script.compiled) { - DocListCast(this.props.Document[this.props.fieldKey]). - forEach((doc, i) => value.startsWith(":=") ? - this.props.setComputed(value.substring(2), doc, this.props.rowProps.column.id!, i, this.props.col) : - this.applyToDoc(doc, i, this.props.col, script.run)); + else if (FieldValue(props.Document["text-lastModified"]) !== undefined) { + const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["text-lastModified"])) as DateField; + cfield = d.date.toLocaleString(); + } } - }} - /> - - + return Field.toString(cfield as Field); + }, positions) + } </div > {/* {fieldIsDoc ? docExpander : null} */} </div> @@ -903,7 +984,6 @@ export class CollectionSchemaButtons extends CollectionSchemaCell { }}><button onClick={() => { doc.searchMatch = false; setTimeout(() => doc.searchMatch = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); }} style={{ padding: 2, left: 77 }}> <FontAwesomeIcon icon="arrow-up" size="sm" /> </button> @@ -911,7 +991,6 @@ export class CollectionSchemaButtons extends CollectionSchemaCell { { doc.searchMatchAlt = false; setTimeout(() => doc.searchMatchAlt = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); } }} style={{ padding: 2 }}> <FontAwesomeIcon icon="arrow-down" size="sm" /> @@ -929,7 +1008,6 @@ export class CollectionSchemaButtons extends CollectionSchemaCell { onClick={() => { doc.searchMatch = false; setTimeout(() => doc.searchMatch = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); }}> <FontAwesomeIcon icon="arrow-down" size="sm" /> </button></div >; diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index e65adcf76..5c0e6581b 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -1,22 +1,24 @@ import React = require("react"); -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import "./CollectionSchemaView.scss"; -import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar } from '@fortawesome/free-solid-svg-icons'; -import { library, IconProp } from "@fortawesome/fontawesome-svg-core"; +import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { ColumnType } from "./CollectionSchemaView"; -import { faFile } from "@fortawesome/free-regular-svg-icons"; -import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { listSpec } from "../../../fields/Schema"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../fields/Types"; import { undoBatch } from "../../util/UndoManager"; -import { Doc } from "../../../fields/Doc"; -import { StrCast } from "../../../fields/Types"; +import { SearchBox } from "../search/SearchBox"; +import { ColumnType } from "./CollectionSchemaView"; +import "./CollectionSchemaView.scss"; +import { CollectionView } from "./CollectionView"; +import * as fa from '@fortawesome/free-solid-svg-icons'; + const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar); - export interface HeaderProps { keyValue: SchemaHeaderField; possibleKeys: string[]; @@ -35,7 +37,7 @@ export interface HeaderProps { export class CollectionSchemaHeader extends React.Component<HeaderProps> { render() { const icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" : - this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" : + this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "sort-amount-down" : this.props.keyType === ColumnType.Image ? "image" : this.props.keyType === ColumnType.List ? "list-ul" : this.props.keyType === ColumnType.Date ? "calendar" : "align-justify"; return ( @@ -238,18 +240,6 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> renderContent = () => { return ( <div className="collectionSchema-header-menuOptions"> - <div className="collectionSchema-headerMenu-group"> - <label>Key:</label> - <KeysDropdown - keyValue={this.props.columnField.heading} - possibleKeys={this.props.possibleKeys} - existingKeys={this.props.existingKeys} - canAddNew={true} - addNew={this.props.addNew} - onSelect={this.props.onSelect} - setIsEditing={this.props.setIsEditing} - /> - </div> {this.props.onlyShowOptions ? <></> : <> {this.renderTypes()} @@ -286,6 +276,15 @@ export interface KeysDropdownProps { setIsEditing: (isEditing: boolean) => void; width?: string; docs?: Doc[]; + Document: Doc; + dataDoc: Doc | undefined; + fieldKey: string; + ContainingCollectionDoc: Doc | undefined; + ContainingCollectionView: Opt<CollectionView>; + active?: (outsideReaction?: boolean) => boolean; + openHeader: (column: any, screenx: number, screeny: number) => void; + col: SchemaHeaderField; + icon: IconProp; } @observer export class KeysDropdown extends React.Component<KeysDropdownProps> { @@ -301,34 +300,38 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @action onSelect = (key: string): void => { - if (key.slice(0, this._key.length) === this._key && this._key !== key) { - const filter = key.slice(this._key.length - key.length); - this.props.onSelect(this._key, this._key, this.props.addNew, filter); - } - else { - this.props.onSelect(this._key, key, this.props.addNew); - this.setKey(key); - this._isOpen = false; - this.props.setIsEditing(false); - } - } - - @action - onSelect2 = (key: string): void => { - this._searchTerm = this._searchTerm.slice(0, this._key.length) + key; + this.props.onSelect(this._key, key, this.props.addNew); + this.setKey(key); this._isOpen = false; - + this.props.setIsEditing(false); } @undoBatch onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { - const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); - if (keyOptions.length) { - this.onSelect(keyOptions[0]); - } else if (this._searchTerm !== "" && this.props.canAddNew) { - this.setSearchTerm(this._searchTerm || this._key); - this.onSelect(this._searchTerm); + if (this._searchTerm.includes(":")) { + const colpos = this._searchTerm.indexOf(":"); + const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); + if (temp === "") { + Doc.setDocFilter(this.props.Document, this._key, temp, undefined); + } + else { + Doc.setDocFilter(this.props.Document, this._key, temp, "match"); + this.props.col.setColor("green"); + } + } + else { + let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + const blockedkeys = ["_scrollTop", "customTitle", "limitHeight", "proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; + keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); + if (keyOptions.length) { + this.onSelect(keyOptions[0]); + console.log("case1"); + } else if (this._searchTerm !== "" && this.props.canAddNew) { + this.setSearchTerm(this._searchTerm || this._key); + console.log("case2"); + this.onSelect(this._searchTerm); + } } } } @@ -360,87 +363,172 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { onPointerOut = (e: React.PointerEvent): void => { this._canClose = true; } - + @action renderOptions = (): JSX.Element[] | JSX.Element => { - if (!this._isOpen) return <></>; - + if (!this._isOpen) { + this.defaultMenuHeight = 0; + return <></>; + } const searchTerm = this._searchTerm.trim() === "New field" ? "" : this._searchTerm; - const keyOptions = searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let keyOptions = searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 || this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1; + const blockedkeys = ["proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; + keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); + const options = keyOptions.map(key => { return <div key={key} className="key-option" style={{ border: "1px solid lightgray", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", }} onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect(key); this.setSearchTerm(""); }}>{key}</div>; }); // if search term does not already exist as a group type, give option to create new group type + if (this._key !== this._searchTerm.slice(0, this._key.length)) { + console.log("little further"); if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { options.push(<div key={""} className="key-option" style={{ - border: "1px solid lightgray", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", }} onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> Create "{this._searchTerm}" key</div>); } } + if (options.length === 0) { + this.defaultMenuHeight = 0; + } + else { + if (this.props.docs) { + const panesize = this.props.docs.length * 30; + options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; + } + else { + options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8; + } + } return options; } + docSafe: Doc[] = []; + + @action renderFilterOptions = (): JSX.Element[] | JSX.Element => { - if (!this._isOpen) return <></>; + if (!this._isOpen) { + this.defaultMenuHeight = 0; + return <></>; + } + const keyOptions: string[] = []; - const temp = this._searchTerm.slice(this._key.length); - this.props.docs?.forEach((doc) => { + const colpos = this._searchTerm.indexOf(":"); + const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); + if (this.docSafe.length === 0) { + this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]); + } + const docs = this.docSafe; + docs.forEach((doc) => { const key = StrCast(doc[this._key]); if (keyOptions.includes(key) === false && key.includes(temp)) { keyOptions.push(key); } }); + const filters = Cast(this.props.Document._docFilters, listSpec("string")); + for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) { + if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) { + keyOptions.push(filters![i + 1]); + } + } + + if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) { + this.props.col.setColor("rgb(241, 239, 235)"); + } const options = keyOptions.map(key => { + //Doc.setDocFilter(this.props.Document!, this._key, key, undefined); + let bool = false; + console.log(filters); + if (filters !== undefined) { + bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check"; + console.log(filters.includes(key)); + } return <div key={key} className="key-option" style={{ - border: "1px solid lightgray", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + border: "1px solid lightgray", paddingLeft: 5, textAlign: "left", + width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white", }} - onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect2(key); }}>{key}</div>; + > + <input type="checkbox" onChange={(e) => { + e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, undefined); + e.target.checked === true ? this.props.col.setColor("green") : ""; + e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, undefined); + }} + checked={bool} ></input> + <span style={{ paddingLeft: 4 }}> + {key} + </span> + + </div>; }); + if (options.length === 0) { + this.defaultMenuHeight = 0; + } + else { + if (this.props.docs) { + const panesize = this.props.docs.length * 30; + options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; + } + else { + options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8; + } + } return options; } + @observable defaultMenuHeight = 0; + + + get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } + + @computed get scriptField() { + console.log("we kinda made it"); + const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)"; + const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name }); + return script ? () => script : undefined; + } + filterBackground = () => "rgba(105, 105, 105, 0.432)"; + + @observable filterOpen: boolean | undefined = undefined; render() { return ( - <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> - {this._key === this._searchTerm.slice(0, this._key.length) ? - <div style={{ position: "absolute", marginLeft: "4px", marginTop: "3", color: "grey", pointerEvents: "none", lineHeight: 1.15 }}> - {this._key} + <div style={{ display: "flex" }}> + <FontAwesomeIcon onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> + + {/* <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => { + runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen }) + }} /> */} + + <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> + <input className="keys-search" style={{ width: "100%" }} + ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} + onChange={e => this.onChange(e.target.value)} + onClick={(e) => { + //this._inputRef.current!.select(); + e.stopPropagation(); + }} onFocus={this.onFocus} onBlur={this.onBlur}></input> + <div className="keys-options-wrapper" style={{ + width: this.props.width, maxWidth: this.props.width, height: "auto", + }} + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> + {this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()} </div> - : undefined} - <input className="keys-search" style={{ width: "100%" }} - ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} - onChange={e => this.onChange(e.target.value)} - onClick={(e) => { - //this._inputRef.current!.select(); - e.stopPropagation(); - }} onFocus={this.onFocus} onBlur={this.onBlur}></input> - <div className="keys-options-wrapper" style={{ - backgroundColor: "white", - width: this.props.width, maxWidth: this.props.width, - }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> - {this._key === this._searchTerm.slice(0, this._key.length) ? - this.renderFilterOptions() : this.renderOptions()} - </div> - </div > + </div > + </div> ); } } diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index ba0a259c5..5c2931a8b 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -43,6 +43,49 @@ // } } +.collectionSchemaView-searchContainer { + border-width: $COLLECTION_BORDER_WIDTH; + border-color: $intermediate-color; + border-style: solid; + border-radius: $border-radius; + box-sizing: border-box; + position: relative; + top: 0; + width: 100%; + height: 100%; + margin-top: 0; + transition: top 0.5s; + display: flex; + justify-content: space-between; + flex-wrap: nowrap; + touch-action: none; + + div { + touch-action: none; + } + + + .collectionSchemaView-tableContainer { + width: 100%; + height: 100%; + } + + .collectionSchemaView-dividerDragger { + position: relative; + height: 100%; + width: 20px; + z-index: 20; + right: 0; + top: 0; + background: gray; + cursor: col-resize; + } + + // .documentView-node:first-child { + // background: $light-color; + // } +} + .ReactTable { width: 100%; background: white; @@ -165,7 +208,7 @@ .collectionSchema-header-menu { - height: 100%; + height: auto; z-index: 100; position: absolute; background:white; @@ -280,11 +323,9 @@ button.add-column { background-color: white; .key-option { - //background-color: $light-color; background-color: white; border: 1px solid lightgray; padding: 2px 3px; - &:not(:first-child) { border-top: 0; } @@ -315,12 +356,53 @@ button.add-column { } } +.altcollectionTimeView-treeView { + display: flex; + flex-direction: column; + width: 175px; + height: auto; + position: fixed; + border-left: solid 1px; + z-index: 1; + + .collectionTimeView-addfacet { + display: inline-block; + width: 200px; + height: 30px; + background: darkGray; + text-align: left; + + .collectionTimeView-button { + align-items: center; + display: flex; + width: 100%; + height: 100%; + + .collectionTimeView-span { + margin: auto; + } + } + + >div, + >div>div { + width: 100%; + height: 100%; + } + } + + .altcollectionTimeView-tree { + display: inline-block; + width: 100%; + height: calc(100% - 30px); + } +} + .collectionSchema-row { height: 100%; background-color: white; &.row-focused .rt-td { - background-color: rgb(255, 246, 246); //$light-color-secondary; + background-color: #bfffc0; //$light-color-secondary; } &.row-wrapped { @@ -383,6 +465,9 @@ button.add-column { .collectionSchemaView-cellWrapper { height: 100%; padding: 4px; + text-align:left; + padding-left:19px; + position: relative; &:focus { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index a003de0d3..f1de3cee7 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -11,7 +11,7 @@ import { Doc } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { Cast, NumCast } from "../../../fields/Types"; +import { Cast, NumCast, BoolCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils"; import { SnappingManager } from "../../util/SnappingManager"; @@ -71,8 +71,18 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @observable _pointerY = 0; @observable _openTypes: boolean = false; @computed get menuCoordinates() { - const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)); - const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)); + let searchx = 0; + let searchy = 0; + if (this.props.Document._searchDoc !== undefined) { + const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0]; + if (el !== undefined) { + const rect = el.getBoundingClientRect(); + searchx = rect.x; + searchy = rect.y; + } + } + const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; + const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy; return this.props.ScreenToLocalTransform().transformPoint(x, y); } @@ -110,7 +120,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { document.removeEventListener("pointerdown", this.detectClick); } - @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing; + @action setHeaderIsEditing = (isEditing: boolean) => { + this._headerIsEditing = isEditing; + } detectClick = (e: PointerEvent): void => { if (this._node && this._node.contains(e.target as Node)) { @@ -356,17 +368,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action closeHeader = () => { this._headerOpen = false; } - renderKeysDropDown = (col: any) => { - return <KeysDropdown - keyValue={col.heading} - possibleKeys={this.possibleKeys} - existingKeys={this.columns.map(c => c.heading)} - canAddNew={true} - addNew={false} - onSelect={this.changeColumns} - setIsEditing={this.setHeaderIsEditing} - />; - } + @undoBatch @action @@ -405,10 +407,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get renderMenuContent() { TraceMobx(); return <div className="collectionSchema-header-menuOptions"> - <div className="collectionSchema-headerMenu-group"> - <label>Key:</label> - {this.renderKeysDropDown(this._col)} - </div> {this.renderTypes(this._col)} {this.renderSorting(this._col)} {this.renderColors(this._col)} @@ -424,7 +422,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { super.CreateDropTarget(ele); } - isFocused = (doc: Doc): boolean => this.props.isSelected() && doc === this._focusedTable; + isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; @action setFocused = (doc: Doc) => this._focusedTable = doc; @@ -611,14 +609,18 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { } render() { + let name = "collectionSchemaView-container"; + if (this.props.Document._searchDoc !== undefined) { + name = "collectionSchemaView-searchContainer"; + } TraceMobx(); const menuContent = this.renderMenuContent; const menu = <div className="collectionSchema-header-menu" ref={this.setNode} onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ - position: "fixed", background: "white", - transform: `translate(${this.menuCoordinates[0] / this.scale}px, ${this.menuCoordinates[1] / this.scale}px)` + position: "fixed", background: "white", border: "black 1px solid", + transform: `translate(${(this.menuCoordinates[0] / this.scale)}px, ${(this.menuCoordinates[1] / this.scale)}px)` }}> <Measure offset onResize={action((r: any) => { const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); @@ -627,11 +629,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>} </Measure> </div>; - return <div className="collectionSchemaView-container" + return <div className={name} style={{ - overflow: this.props.overflow === true ? "auto" : undefined, + overflow: this.props.overflow === true ? "scroll" : undefined, pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, - width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", + width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index b27af66ba..fe3d57bdb 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,7 +4,7 @@ import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import Switch from 'rc-switch'; -import { DataSym, Doc, HeightSym, WidthSym, DocListCastAsync } from "../../../fields/Doc"; +import { DataSym, Doc, HeightSym, WidthSym } from "../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; @@ -28,7 +28,6 @@ import { CollectionViewType } from "./CollectionView"; import { SnappingManager } from "../../util/SnappingManager"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocUtils } from "../../documents/Documents"; -import { MainViewNotifs } from "../MainViewNotifs"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -299,10 +298,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) const srcInd = docs.indexOf(doc); docs.splice(srcInd, 1); docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); - DocListCastAsync(docs).then(resolvedDocs => { - const pos = resolvedDocs?.findIndex(shareDoc => shareDoc.icon === "users") || 0; // hopefully find out if the sharing doc has been moved - if (MainViewNotifs.NotifsCol && pos !== -1) MainViewNotifs.NotifsCol.position = pos; - }); } else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated if (targInd === -1) targInd = docs.length; else targInd = docs.indexOf(newDocs[0]) + 1; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d1aeb1b65..3f2ad47a5 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -112,10 +112,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])]; } @computed get childDocs() { - let rawdocs: (Doc | Promise<Doc>)[] = DocListCast(this.props.Document._searchDocs); - - if (rawdocs.length !== 0) { - } else if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; + let rawdocs: (Doc | Promise<Doc>)[] = []; + if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; rawdocs = [this.dataField]; } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. rawdocs = Cast(this.dataField, listSpec(Doc), null); @@ -128,30 +126,25 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - let childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + + let searchDocs = DocListCast(this.props.Document._searchDocs); - const searchDocs = DocListCast(this.props.Document._searchDocs); - // if (searchDocs !== undefined && searchDocs.length > 0) { - // let newdocs: Doc[] = []; - // childDocs.forEach((el) => { - // searchDocs.includes(el) ? newdocs.push(el) : undefined; - // }); - // childDocs = newdocs; - // } let docsforFilter: Doc[] = childDocs; + if (searchDocs !== undefined && searchDocs.length > 0) { docsforFilter = []; - // let newdocs: Doc[] = []; - // let newarray: Doc[] = []; - //while (childDocs.length > 0) { - //newarray = []; + const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); + console.log(searchDocs); + searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript); + console.log(this.docFilters()); + console.log(searchDocs); childDocs.forEach((d) => { if (d.data !== undefined) { - console.log(d); let newdocs = DocListCast(d.data); if (newdocs.length > 0) { - let vibecheck: boolean | undefined = undefined; + let displaycheck: boolean | undefined = undefined; let newarray: Doc[] = []; while (newdocs.length > 0) { newarray = []; @@ -163,12 +156,12 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: }); } if (searchDocs.includes(t)) { - vibecheck = true; + displaycheck = true; } }); newdocs = newarray; } - if (vibecheck === true) { + if (displaycheck === true) { docsforFilter.push(d); } } @@ -177,14 +170,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: docsforFilter.push(d); } }); - //childDocs = newarray; - //} + return docsforFilter; } - childDocs = docsforFilter; - const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - - return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, this.docFilters(), docRangeFilters, viewSpecScript); + return this.props.Document.dontRegisterView ? childDocs : DocUtils.FilterDocs(childDocs, this.docFilters(), docRangeFilters, viewSpecScript); } @action @@ -251,6 +240,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else added = res; + !added && alert("You don't have permission to perform this move"); + e.stopPropagation(); } else { ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); added = this.addDocument(docDragData.droppedDocuments); @@ -343,7 +334,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: let srcWeb: Doc | undefined; if (SelectionManager.SelectedDocuments().length) { srcWeb = SelectionManager.SelectedDocuments()[0].props.Document; - srcUrl = (srcWeb.data as WebField).url.href?.match(/http[s]?:\/\/[^/]*/)?.[0]; + srcUrl = (srcWeb.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0]; } const reg = new RegExp(Utils.prepend(""), "g"); const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; @@ -351,7 +342,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text; this.props.addDocument(htmlDoc); if (srcWeb) { - const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any); + const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")?.[0].contentDocument?.getSelection()?.focusNode as any); if (focusNode) { const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect(); const x = (rect?.x || 0); @@ -481,7 +472,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: completed?.(); } else { if (text && !text.includes("https://")) { - this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 })); + UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop"); } } disposer(); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 3c7471d7c..d096e7d66 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -53,7 +53,7 @@ export interface TreeViewProps { indentDocument?: () => void; outdentDocument?: () => void; ScreenToLocalTransform: () => Transform; - backgroundColor?: (doc: Doc) => string | undefined; + backgroundColor?: (doc: Doc, renderDepth: number) => string | undefined; outerXf: () => { translateX: number, translateY: number }; treeViewDoc: Doc; parentKey: string; @@ -88,7 +88,7 @@ class TreeView extends React.Component<TreeViewProps> { get doc() { return this.props.document; } get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); } get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive - get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); } + get defaultExpandedView() { return this.childDocs.length ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); } @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; @@ -108,6 +108,7 @@ class TreeView extends React.Component<TreeViewProps> { } @computed get childDocs() { return this.childDocList(this.fieldKey); } @computed get childLinks() { return this.childDocList("links"); } + @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } @computed get boundsOfCollectionDocument() { return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 || !DocListCast(this.props.document[this.fieldKey]).length ? undefined : Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey])); @@ -127,7 +128,7 @@ class TreeView extends React.Component<TreeViewProps> { constructor(props: any) { super(props); - const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `); + const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); this._editTitleScript = script && (() => script); if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false); } @@ -313,11 +314,11 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; - if (["links", this.fieldKey].includes(expandKey)) { + if (["links", "annotations", this.fieldKey].includes(expandKey)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey); const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true); - const docs = expandKey === "links" ? this.childLinks : this.childDocs; + const docs = expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; const sortKey = `${this.fieldKey}-sortAscending`; return <ul key={expandKey + "more"} onClick={(e) => { this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true)); @@ -397,7 +398,7 @@ class TreeView extends React.Component<TreeViewProps> { title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} onClick={this.bulletClick} style={{ color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}> - {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />} + {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs?.length ? "caret-square-right" : "caret-right") : (this.childDocs?.length ? "caret-square-down" : "caret-down"))} />} </div>; } @@ -424,7 +425,8 @@ class TreeView extends React.Component<TreeViewProps> { this.doc.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") : this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" : this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" : - this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields"); + (this.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : + this.childDocs.length ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields"); } this.treeViewOpen = true; })}> @@ -453,6 +455,7 @@ class TreeView extends React.Component<TreeViewProps> { NativeHeight={returnZero} NativeWidth={returnZero} contextMenuItems={this.contextMenuItems} + opacity={returnOne} renderDepth={1} focus={returnTrue} parentActive={returnTrue} @@ -475,7 +478,7 @@ class TreeView extends React.Component<TreeViewProps> { </div > {headerElements} <div className="treeViewItem-openRight" onClick={this.openRight}> - <FontAwesomeIcon title="open in pane on right" icon="external-link-alt" size="sm" /> + <FontAwesomeIcon title="open in a new pane" icon="external-link-alt" size="sm" /> </div> </>; } @@ -494,7 +497,7 @@ class TreeView extends React.Component<TreeViewProps> { } } } else this._editMaxWidth = ""; - return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active() && SelectionManager.DeselectAll()}> + return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}> <li className="collection-child"> <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => { if (this.props.active(true)) { @@ -533,7 +536,7 @@ class TreeView extends React.Component<TreeViewProps> { dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => boolean, pinToPres: (document: Doc) => void, - backgroundColor: undefined | ((document: Doc) => string | undefined), + backgroundColor: undefined | ((document: Doc, renderDepth: number) => string | undefined), screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, active: (outsideReaction?: boolean) => boolean, @@ -813,7 +816,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}> <div className="collectionTreeView-dropTarget" id="body" style={{ - background: this.props.backgroundColor?.(this.doc), + background: this.props.backgroundColor?.(this.doc, this.props.renderDepth), paddingLeft: `${NumCast(this.doc._xPadding, 10)}px`, paddingRight: `${NumCast(this.doc._xPadding, 10)}px`, paddingTop: `${NumCast(this.doc._yPadding, 20)}px`, diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 0feec3fbd..6dd21ef7f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -46,6 +46,7 @@ import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; import { ContextMenuProps } from '../ContextMenuItem'; +import { table } from 'console'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -141,7 +142,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus const targetDataDoc = this.props.Document[DataSym]; const docList = DocListCast(targetDataDoc[this.props.fieldKey]); const added = docs.filter(d => !docList.includes(d)); - const effectiveAcl = GetEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); if (added.length) { if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { @@ -182,6 +183,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus // targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]); (targetDataDoc[this.props.fieldKey] as List<Doc>).push(...added); targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + const lastModified = "lastModified"; + targetDataDoc[lastModified] = new DateField(new Date(Date.now())); } } } @@ -190,16 +193,19 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus @action.bound removeDocument = (doc: any): boolean => { - const collectionEffectiveAcl = GetEffectiveAcl(this.props.Document); - const docEffectiveAcl = GetEffectiveAcl(doc); - // you can remove the document if you either have Admin/Edit access to the collection or to the specific document - if (collectionEffectiveAcl === AclEdit || collectionEffectiveAcl === AclAdmin || docEffectiveAcl === AclAdmin || docEffectiveAcl === AclEdit) { + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); + if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { const docs = doc instanceof Doc ? [doc] : doc as Doc[]; const targetDataDoc = this.props.Document[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { - toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc)); + const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; + toRemove.forEach(doc => { + Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc); + recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); + doc.deleted = true; + }); return true; } } @@ -216,7 +222,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return true; } const first = doc instanceof Doc ? doc : doc[0]; - if (!first?.stayInCollection && addDocument !== returnFalse) { + if (!first?._stayInCollection && addDocument !== returnFalse) { if (UndoManager.RunInTempBatch(() => this.removeDocument(doc))) { const added = addDocument(doc); if (!added) UndoManager.UndoTempBatch(); @@ -510,56 +516,56 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus </label>)} </div> ); - return !this._facetWidth || this.props.dontRegisterView ? (null) : - <div className="collectionTimeView-treeView" style={{ width: `${this.facetWidth()}px`, overflow: this.facetWidth() < 15 ? "hidden" : undefined }}> - <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> - <div className="collectionTimeView-button"> - <FontAwesomeIcon icon={faEdit} size={"lg"} /> - <span className="collectionTimeView-span">Facet Filters</span> - </div> - </Flyout> - </div> - <div className="collectionTimeView-tree" key="tree"> - <CollectionTreeView - PanelPosition={""} - Document={facetCollection} - DataDoc={facetCollection} - fieldKey={`${this.props.fieldKey}-filter`} - CollectionView={this} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - ContainingCollectionView={this.props.ContainingCollectionView} - PanelWidth={this.facetWidth} - PanelHeight={this.props.PanelHeight} - NativeHeight={returnZero} - NativeWidth={returnZero} - LibraryPath={emptyPath} - rootSelected={this.props.rootSelected} - renderDepth={1} - dropAction={this.props.dropAction} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} - addDocTab={returnFalse} - pinToPres={returnFalse} - isSelected={returnFalse} - select={returnFalse} - bringToFront={emptyFunction} - active={this.props.active} - whenActiveChanged={returnFalse} - treeViewHideTitle={true} - ContentScaling={returnOne} - focus={returnFalse} - treeViewHideHeaderFields={true} - onCheckedClick={this.scriptField} - ignoreFields={this.ignoreFields} - annotationsKey={""} - dontRegisterView={true} - backgroundColor={this.filterBackground} - moveDocument={returnFalse} - removeDocument={returnFalse} - addDocument={returnFalse} /> - </div> - </div>; + + return !this._facetWidth || this.props.dontRegisterView ? (null) : <div className="collectionTimeView-treeView" style={{ width: `${this.facetWidth()}px`, overflow: this.facetWidth() < 15 ? "hidden" : undefined }}> + <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> + <div className="collectionTimeView-button"> + <FontAwesomeIcon icon={faEdit} size={"lg"} /> + <span className="collectionTimeView-span">Facet Filters</span> + </div> + </Flyout> + </div> + <div className="collectionTimeView-tree" key="tree"> + <CollectionTreeView + PanelPosition={""} + Document={facetCollection} + DataDoc={facetCollection} + fieldKey={`${this.props.fieldKey}-filter`} + CollectionView={this} + docFilters={returnEmptyFilter} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + PanelWidth={this.facetWidth} + PanelHeight={this.props.PanelHeight} + NativeHeight={returnZero} + NativeWidth={returnZero} + LibraryPath={emptyPath} + rootSelected={this.props.rootSelected} + renderDepth={1} + dropAction={this.props.dropAction} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + addDocTab={returnFalse} + pinToPres={returnFalse} + isSelected={returnFalse} + select={returnFalse} + bringToFront={emptyFunction} + active={this.props.active} + whenActiveChanged={returnFalse} + treeViewHideTitle={true} + ContentScaling={returnOne} + focus={returnFalse} + treeViewHideHeaderFields={true} + onCheckedClick={this.scriptField} + ignoreFields={this.ignoreFields} + annotationsKey={""} + dontRegisterView={true} + backgroundColor={this.filterBackground} + moveDocument={returnFalse} + removeDocument={returnFalse} + addDocument={returnFalse} /> + </div> + </div>; } childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 4c8cac3ed..149d4927b 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -15,6 +15,7 @@ import { faCog, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons"; import { library } from "@fortawesome/fontawesome-svg-core"; import { DocumentView } from "../nodes/DocumentView"; import { SelectionManager } from "../../util/SelectionManager"; +import { Tooltip } from "@material-ui/core"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -121,16 +122,17 @@ export class DockingViewButtonSelector extends React.Component<{ views: () => Do } render() { - return <span title="Tap for menu, drag tab as document" - onPointerDown={e => { + return <Tooltip title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom"> + <span onPointerDown={e => { if (getComputedStyle(this._ref.current!).width !== "100%") { e.stopPropagation(); e.preventDefault(); } this.props.views()[0]?.select(false); }} className="buttonSelector"> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}> - <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} /> - </Flyout> - </span>; + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}> + <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} /> + </Flyout> + </span> + </Tooltip>; } } diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index a974c5496..a2c529a21 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -63,12 +63,12 @@ export interface SchemaTableProps { addDocument: (document: Doc | Doc[]) => boolean; moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - active: (outsideReaction: boolean) => boolean; + active: (outsideReaction: boolean | undefined) => boolean; onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; isSelected: (outsideReaction?: boolean) => boolean; - isFocused: (document: Doc) => boolean; + isFocused: (document: Doc, outsideReaction: boolean) => boolean; setFocused: (document: Doc) => void; setPreviewDoc: (document: Doc) => void; columns: SchemaHeaderField[]; @@ -155,7 +155,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); const columns: Column<Doc>[] = []; - const tableIsFocused = this.props.isFocused(this.props.Document); + const tableIsFocused = this.props.isFocused(this.props.Document, false); const focusedRow = this._focusedCell.row; const focusedCol = this._focusedCell.col; const isEditable = !this.props.headerIsEditing; @@ -177,8 +177,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } ); } + this.props.active; const cols = this.props.columns.map(col => { + const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : + this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : + this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : + this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; const keysDropdown = <KeysDropdown keyValue={col.heading} @@ -189,24 +194,20 @@ export class SchemaTable extends React.Component<SchemaTableProps> { onSelect={this.props.changeColumns} setIsEditing={this.props.setHeaderIsEditing} docs={this.props.childDocs} + Document={this.props.Document} + dataDoc={this.props.dataDoc} + fieldKey={this.props.fieldKey} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + active={this.props.active} + openHeader={this.props.openHeader} + icon={icon} + col={col} // try commenting this out width={"100%"} />; - const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : - this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : - this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : - this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; - const headerText = this._showTitleDropdown ? keysDropdown : <div - onClick={this.changeTitleMode} - style={{ - background: col.color, padding: "2px", - letterSpacing: "2px", - textTransform: "uppercase", - display: "flex" - }}> - {col.heading}</div>; const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up"; @@ -218,19 +219,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> { background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}> - <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px" }} /> - {/* <div className="keys-dropdown" - style={{ display: "inline", zIndex: 1000 }}> */} + {/* <FontAwesomeIcon onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> */} {keysDropdown} - {/* </div> */} <div onClick={e => this.changeSorting(col)} - style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit" }}> + style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}> <FontAwesomeIcon icon={sortIcon} size="lg" /> </div> - {/* <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} - style={{ float: "right", paddingRight: "6px", zIndex: 1, background: "inherit" }}> - <FontAwesomeIcon icon={"compass"} size="sm" /> - </div> */} </div>; return { @@ -318,27 +312,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } - - @action - nextHighlight = (e: React.MouseEvent, doc: Doc) => { - e.preventDefault(); - e.stopPropagation(); - doc.searchMatch = false; - console.log(doc.searchMatch); - setTimeout(() => doc.searchMatch = true, 0); - console.log(doc.searchMatch); - - doc.searchIndex = NumCast(doc.searchIndex); - } - - @action - nextHighlight2 = (doc: Doc) => { - - doc.searchMatchAlt = false; - setTimeout(() => doc.searchMatchAlt = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); - } - constructor(props: SchemaTableProps) { super(props); // convert old schema columns (list of strings) into new schema columns (list of schema header fields) @@ -347,7 +320,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i); this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders); } else if (this.props.Document._schemaHeaders === undefined) { - this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]); + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb"), + new SchemaHeaderField("text", "#f1efeb"), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb")]); } } @@ -369,7 +343,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { addDoc: this.tableAddDoc, removeDoc: this.props.deleteDocument, rowInfo, - rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document), + rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), textWrapRow: this.toggleTextWrapRow, rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, dropAction: StrCast(this.props.Document.childDropAction), @@ -383,7 +357,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const row = rowInfo.index; //@ts-ignore const col = this.columns.map(c => c.heading).indexOf(column!.id); - const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document); + const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); // TODO: editing border doesn't work :( return { style: { @@ -403,7 +377,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { @action onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) { + if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && this.props.isSelected(true)) { const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : ""; this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 163554db9..46e30f616 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -15,7 +15,7 @@ import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils, setupMoveUpEvents } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -378,7 +378,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } getClusterColor = (doc: Doc) => { - let clusterColor = this.props.backgroundColor?.(doc); + let clusterColor = this.props.backgroundColor?.(doc, this.props.renderDepth + 1); const cluster = NumCast(doc.cluster); if (this.Document.useClusters) { if (this._clusterSets.length <= cluster) { @@ -1200,27 +1200,29 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.addDocTab(childDocs as any as Doc, "inParent"); this.props.ContainingCollectionView?.removeDocument(this.props.Document); })); - layoutDocsInGrid = () => { - UndoManager.RunInBatch(() => { - const docs = this.childLayoutPairs; - const startX = this.Document._panX || 0; - let x = startX; - let y = this.Document._panY || 0; - let i = 0; - const width = Math.max(...docs.map(doc => NumCast(doc.layout._width))); - const height = Math.max(...docs.map(doc => NumCast(doc.layout._height))); - docs.forEach(pair => { - pair.layout.x = x; - pair.layout.y = y; - x += width + 20; - if (++i === 6) { - i = 0; - x = startX; - y += height + 20; - } - }); - }, "arrange contents"); - } + + + @undoBatch + layoutDocsInGrid = action(() => { + const docs = this.childLayoutPairs; + const startX = this.Document._panX || 0; + let x = startX; + let y = this.Document._panY || 0; + let i = 0; + const width = Math.max(...docs.map(doc => NumCast(doc.layout._width))); + const height = Math.max(...docs.map(doc => NumCast(doc.layout._height))); + docs.forEach(pair => { + pair.layout.x = x; + pair.layout.y = y; + x += width + 20; + if (++i === 6) { + i = 0; + x = startX; + y += height + 20; + } + }); + }); + @undoBatch @action toggleNativeDimensions = () => { @@ -1495,8 +1497,123 @@ interface CollectionFreeFormViewPannableContentsProps { @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{ + @observable private _drag: string = ''; + + //Adds event listener so knows pointer is down and moving + onPointerDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + const corner = e.target as any; + console.log(corner.id); + if (corner) this._drag = corner.id; + const rect = document.getElementById(this._drag); + if (rect) { + console.log(this._drag); + setupMoveUpEvents(e.target, e, this.onPointerMove, (e) => { }, (e) => { }); + } + } + + //Removes all event listeners + onPointerUp = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._drag = ""; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } + + //Adjusts the value in NodeStore + @action + onPointerMove = (e: PointerEvent) => { + const doc = document.getElementById('resizable'); + const rect = doc!.getBoundingClientRect(); + const toNumber = (original: number, delta: number): number => { + return original + (delta * this.props.zoomScaling()); + }; + if (doc) { + const height = doc.offsetHeight; + const width = doc.offsetWidth; + const top = doc.offsetTop; + const left = doc.offsetLeft; + switch (this._drag) { + case "": break; + case "resizer-br": + doc.style.width = toNumber(width, e.movementX) + 'px'; + doc.style.height = toNumber(height, e.movementY) + 'px'; + break; + case "resizer-bl": + doc.style.width = toNumber(width, -e.movementX) + 'px'; + doc.style.height = toNumber(height, e.movementY) + 'px'; + doc.style.left = toNumber(left, e.movementX) + 'px'; + break; + case "resizer-tr": + doc.style.width = toNumber(width, -e.movementX) + 'px'; + doc.style.height = toNumber(height, -e.movementY) + 'px'; + doc.style.top = toNumber(top, e.movementY) + 'px'; + case "resizer-tl": + doc.style.width = toNumber(width, -e.movementX) + 'px'; + doc.style.height = toNumber(height, -e.movementY) + 'px'; + doc.style.top = toNumber(top, e.movementY) + 'px'; + doc.style.left = toNumber(left, e.movementX) + 'px'; + case "resizable": + doc.style.top = toNumber(top, e.movementY) + 'px'; + doc.style.left = toNumber(left, e.movementX) + 'px'; + } + this.updateAll(height, width, top, left); + return false; + } + return true; + } + + @action + updateAll = (width: number, height: number, top: number, left: number) => { + const activeItem = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + this.updateList(targetDoc, activeItem["viewfinder-width-indexed"], width); + this.updateList(targetDoc, activeItem["viewfinder-height-indexed"], height); + this.updateList(targetDoc, activeItem["viewfinder-top-indexed"], top); + this.updateList(targetDoc, activeItem["viewfinder-left-indexed"], left); + } + + @action + updateList = (doc: Doc, list: any, val: number) => { + const x: List<number> = list; + if (x && x.length >= NumCast(doc.currentFrame) + 1) { + x[NumCast(doc.currentFrame)] = val; + list = x; + } else if (doc && x) { + x.length = NumCast(doc.currentFrame) + 1; + x[NumCast(doc.currentFrame)] = val; + list = x; + } + } + + // scale: NumCast(targetDoc._viewScale), + @computed get zoomProgressivizeContainer() { + const activeItem = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + if (activeItem && activeItem.zoomProgressivize) { + const vfLeft: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-left-indexed"]); + const vfWidth: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-width-indexed"]); + const vfTop: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-top-indexed"]); + const vfHeight: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-height-indexed"]); + return ( + <> + {!activeItem.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}> + <div className='resizers'> + <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerDown}></div> + <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerDown}></div> + <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerDown}></div> + <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerDown}></div> + </div> + </div>} + </> + ); + } + } + @computed get zoomProgressivize() { - return PresBox.Instance && this.props.zoomProgressivize ? PresBox.Instance.zoomProgressivizeContainer : (null); + return PresBox.Instance && this.props.zoomProgressivize ? this.zoomProgressivizeContainer : (null); } @computed get progressivize() { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1a708d67d..c0b19fcd2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -21,6 +21,7 @@ import { CollectionView } from "../CollectionView"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); +import { ContextMenuItem } from "../../ContextMenuItem"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -70,23 +71,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque onKeyPress = (e: KeyboardEvent) => { //make textbox and add it to this collection // tslint:disable-next-line:prefer-const - let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); + const cm = ContextMenu.Instance; + const [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); if (e.key === "?") { - ContextMenu.Instance.setDefaultItem("?", (str: string) => { - const textDoc = Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { - _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, - title: "bing", UseCors: true - }); - this.props.addDocTab(textDoc, "onRight"); - }); + cm.setDefaultItem("?", (str: string) => this.props.addDocTab( + Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "onRight")); - ContextMenu.Instance.displayMenu(this._downX, this._downY); + cm.displayMenu(this._downX, this._downY); e.stopPropagation(); } else if (e.key === ":") { DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y); - ContextMenu.Instance.displayMenu(this._downX, this._downY); + cm.displayMenu(this._downX, this._downY); e.stopPropagation(); } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); @@ -108,11 +105,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (br) break; } } + let ypos = y; ns.map(line => { const indent = line.search(/\S|$/); - const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, title: line }); + const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: ypos, title: line }); this.props.addDocument(newBox); - y += 40 * this.props.getTransform().Scale; + ypos += 40 * this.props.getTransform().Scale; }); })(); e.stopPropagation(); @@ -280,7 +278,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else { this._downX = x; this._downY = y; - const effectiveAcl = GetEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge); this.clearSelection(); } @@ -342,41 +340,33 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action delete = () => { - const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - - selected.map(doc => { - const effectiveAcl = GetEffectiveAcl(doc); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete - recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); - this.props.removeDocument(doc); - } - }); + selected.forEach(doc => this.props.removeDocument(doc)); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } - getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => { - const bounds = this.Bounds; - // const inkData = this.ink ? this.ink.inkData : undefined; - const newCollection = (creator || Docs.Create.FreeformDocument)(selected, { - x: bounds.left, - y: bounds.top, - _panX: 0, - _panY: 0, - isBackground, - backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined, - _width: bounds.width, - _height: bounds.height, - title: "a nested collection", - }); + getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => { + const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { + Doc.GetProto(doc).data = new List<Doc>(selected); + Doc.GetProto(doc).title = "nested freeform"; + doc._panX = doc._panY = 0; + return doc; + })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); + newCollection.system = undefined; + newCollection.isBackground = isBackground; + newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined; + newCollection._width = this.Bounds.width; + newCollection._height = this.Bounds.height; + newCollection.x = this.Bounds.left; + newCollection.y = this.Bounds.top; selected.forEach(d => d.context = newCollection); this.hideMarquee(); return newCollection; - } + }); @action pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => { diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index e0f3eca44..57e968aa7 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -316,7 +316,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ getPermissionsSelect(user: string, permission: string) { return <select className="permissions-select" - defaultValue={permission} + value={permission} onChange={e => this.changePermissions(e, user)}> {Object.values(SharingPermissions).map(permission => { return ( @@ -963,22 +963,22 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } if (this.isPres) { const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; - return <div className="propertiesView"> - <div className="propertiesView-title"> + return <div className="propertiesView" style={{ width: this.props.width }}> + <div className="propertiesView-title" style={{ width: this.props.width }}> Presentation </div> <div className="propertiesView-name"> {this.editableTitle} <div className="propertiesView-presSelected"> - {PresBox.Instance._selectedArray.length} selected + {PresBox.Instance?._selectedArray.length} selected <div className="propertiesView-selectedList"> - {PresBox.Instance.listOfSelected} + {PresBox.Instance?.listOfSelected} </div> </div> </div> {!selectedItem ? (null) : <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" - onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} + onPointerDown={action(() => { this.openPresTransitions = !this.openPresTransitions; })} style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> <FontAwesomeIcon icon={"rocket"} /> Transitions <div className="propertiesView-presTrails-title-icon"> @@ -1028,7 +1028,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {PresBox.Instance.newDocumentDropdown} </div> : null} </div> - <div className="propertiesView-sharing"> + {/* <div className="propertiesView-sharing"> <div className="propertiesView-sharing-title" onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} style={{ backgroundColor: this.openSharing ? "black" : "" }}> @@ -1040,7 +1040,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {this.openSharing ? <div className="propertiesView-sharing-content"> {this.sharingTable} </div> : null} - </div> + </div> */} </div>; } } diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss index 4c79a7c2f..7eb07ff55 100644 --- a/src/client/views/globalCssVariables.scss +++ b/src/client/views/globalCssVariables.scss @@ -29,6 +29,7 @@ $search-thumnail-size: 130; $contextMenu-zindex: 100000; // context menu shows up over everything $radialMenu-zindex: 100000; // context menu shows up over everything +$searchpanel-height: 32px; $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc $docDecorations-zindex: 998; // then doc decorations appear over everything else $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right? @@ -43,4 +44,5 @@ $MAX_ROW_HEIGHT: 44px; MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT; SEARCH_THUMBNAIL_SIZE: $search-thumnail-size; ANTIMODEMENU_HEIGHT: $antimodemenu-height; + SEARCH_PANEL_HEIGHT: $searchpanel-height; }
\ No newline at end of file diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts index a7ca4b300..fd1f60f13 100644 --- a/src/client/views/globalCssVariables.scss.d.ts +++ b/src/client/views/globalCssVariables.scss.d.ts @@ -6,6 +6,7 @@ interface IGlobalScss { MAX_ROW_HEIGHT: string; SEARCH_THUMBNAIL_SIZE: string; ANTIMODEMENU_HEIGHT: string; + SEARCH_PANEL_HEIGHT: string; } declare const globalCssVariables: IGlobalScss; diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index c9e39cf7b..ed64bde32 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -382,11 +382,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> { </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("onRight")}> - Always open in new pane on right + Always open in a new pane </div> <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior("inTab")}> - Always open in new tab + Always open in a new tab </div> {this.props.linkDoc.linksToAnnotation ? <div className="linkEditor-followingDropdown-option" diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 289388320..900963eb0 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -85,6 +85,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD constructor(props: Readonly<FieldViewProps>) { super(props); + AudioBox.Instance = this; // onClick play scripts AudioBox.RangeScript = AudioBox.RangeScript || ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart), (this.audioEnd))`, { scriptContext: "any" })!; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 42a42ddf1..52f6a66c8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -132,7 +132,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } - public static updateKeyframe(docs: Doc[], time: number) { + public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) { const timecode = Math.round(time); docs.forEach(doc => { const xindexed = Cast(doc['x-indexed'], listSpec("number"), null); @@ -145,11 +145,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number); yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number); opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number); - if (doc.appearFrame) { + if (doc.appearFrame && targetDoc) { if (doc.appearFrame === timecode + 1) { - doc["text-color"] = "red"; + doc["text-color"] = StrCast(targetDoc["pres-text-color"]); } else if (doc.appearFrame < timecode + 1) { - doc["text-color"] = "grey"; + doc["text-color"] = StrCast(targetDoc["pres-text-viewed-color"]); } else { doc["text-color"] = "black"; } } else if (doc.appearFrame === 0) { doc["text-color"] = "black"; @@ -164,15 +164,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010); } - public static setupZoom(doc: Doc, zoomProgressivize: boolean = false) { + + public static setupZoom(doc: Doc, targDoc: Doc, zoomProgressivize: boolean = false) { const width = new List<number>(); const height = new List<number>(); const top = new List<number>(); const left = new List<number>(); - width.push(NumCast(doc.width)); - height.push(NumCast(doc.height)); - top.push(NumCast(doc.height) / -2); - left.push(NumCast(doc.width) / -2); + width.push(NumCast(targDoc._width)); + height.push(NumCast(targDoc._height)); + top.push(NumCast(targDoc._height) / -2); + left.push(NumCast(targDoc._width) / -2); doc["viewfinder-width-indexed"] = width; doc["viewfinder-height-indexed"] = height; doc["viewfinder-top-indexed"] = top; @@ -267,13 +268,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF NativeHeight = () => this.nativeHeight; render() { TraceMobx(); - const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document); + const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document, this.props.renderDepth); return <div className="collectionFreeFormDocumentView-container" style={{ boxShadow: this.Opacity === 0 ? undefined : // if it's not visible, then no shadow this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow - this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent + this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big StrCast(this.layoutDoc.boxShadow, ""), borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding), diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index 0c4242172..88eb48f51 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -184,7 +184,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do onContextMenu={this.specificContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ - background: this.props.backgroundColor?.(containedDoc), + background: this.props.backgroundColor?.(containedDoc, this.props.renderDepth), border: `#00000021 solid ${this.xPad}px`, borderTop: `#0000005e solid ${this.yPad}px`, borderBottom: `#0000005e solid ${this.yPad}px`, diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 31dd33fc1..cf8645e4c 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -241,6 +241,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", backgroundColor: DocumentLinksButton.StartLink ? "" : "grey", + opacity: DocumentLinksButton.StartLink ? "" : "50%", border: DocumentLinksButton.StartLink ? "" : "none" }} onPointerDown={DocumentLinksButton.StartLink ? this.completeLink : emptyFunction} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 47e1b2715..d92dc0ec2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -6,15 +6,13 @@ import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; -import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; +import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { emptyFunction, emptyPath, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils"; +import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; -import { ClientRecommender } from '../../ClientRecommender'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from "../../util/DocumentManager"; @@ -22,7 +20,6 @@ import { DragManager, dropActionType } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; import { LinkManager } from '../../util/LinkManager'; import { Scripting } from '../../util/Scripting'; -import { SearchUtil } from '../../util/SearchUtil'; import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -33,7 +30,6 @@ import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; -import { KeyphraseQueryView } from '../KeyphraseQueryView'; import { DocumentContentsView } from "./DocumentContentsView"; import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; @@ -86,7 +82,7 @@ export interface DocumentViewProps { addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; pinToPres: (document: Doc) => void; backgroundHalo?: () => boolean; - backgroundColor?: (doc: Doc) => string | undefined; + backgroundColor?: (doc: Doc, renderDepth: number) => string | undefined; forcedBackgroundColor?: (doc: Doc) => string | undefined; opacity?: () => number | undefined; ChromeHeight?: () => number; @@ -179,7 +175,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); - const effectiveAcl = GetEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 }); // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); @@ -324,6 +320,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu self: this.rootDoc, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, + documentView: this, shiftKey: e.shiftKey }, console.log); if (!Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-undo"] as Doc) && !Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-redo"] as Doc)) { @@ -570,19 +567,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (Doc.UserDoc().activeWorkspace === this.props.Document) { alert("Can't delete the active workspace"); } else { - const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = SelectionManager.SelectedDocuments().slice(); SelectionManager.DeselectAll(); - - selected.map(dv => { - const effectiveAcl = GetEffectiveAcl(dv.props.Document); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete - recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); - dv.props.removeDocument?.(dv.props.Document); - } - }); - - this.props.Document.deleted = true; + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); this.props.removeDocument?.(this.props.Document); } } @@ -776,8 +763,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()) && moreItems.push({ description: "Toggle Always Show Link End", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" }); } - const effectiveAcl = GetEffectiveAcl(this.props.Document); - (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); + moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" }); !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!); @@ -829,7 +815,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @computed get contents() { const pos = this.props.relative ? "relative " : "absolute"; TraceMobx(); - return (<div style={{ width: "100%", height: "100%" }}> + return (<div className="documentView-contentsView" style={{ borderRadius: "inherit", width: "100%", height: "100%" }}> <DocumentContentsView key={1} docFilters={this.props.docFilters} ContainingCollectionView={this.props.ContainingCollectionView} @@ -935,7 +921,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const showTitle = StrCast(this.layoutDoc._showTitle); const showTitleHover = StrCast(this.layoutDoc._showTitleHover); const showCaption = StrCast(this.layoutDoc._showCaption); - const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined; + const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined; const captionView = (!showCaption ? (null) : <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}> <DocumentContentsView {...OmitKeys(this.props, ['children']).omit} @@ -1002,9 +988,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu render() { if (!(this.props.Document instanceof Doc)) return (null); - if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null); + if (GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate) return (null); if (this.props.Document.hidden) return (null); - const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document); + const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document, this.props.renderDepth); const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null))); const finalOpacity = this.props.opacity ? this.props.opacity() : opacity; const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index dd70fb1db..9d61ec6d1 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -5,7 +5,7 @@ import { DateField } from "../../../fields/DateField"; import { Doc, FieldResult, Opt, Field } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { ScriptField } from "../../../fields/ScriptField"; -import { AudioField, VideoField } from "../../../fields/URLField"; +import { AudioField, VideoField, WebField } from "../../../fields/URLField"; import { Transform } from "../../util/Transform"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; @@ -38,7 +38,7 @@ export interface FieldViewProps { pinToPres: (document: Doc) => void; removeDocument?: (document: Doc | Doc[]) => boolean; moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - backgroundColor?: (document: Doc) => string | undefined; + backgroundColor?: (document: Doc, renderDepth: number) => string | undefined; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; active: (outsideReaction?: boolean) => boolean; @@ -136,9 +136,9 @@ export class FieldView extends React.Component<FieldViewProps> { return <div> {field.map(f => Field.toString(f)).join(", ")} </div>; } // bcz: this belongs here, but it doesn't render well so taking it out for now - // else if (field instanceof HtmlField) { - // return <WebBox {...this.props} /> - // } + else if (field instanceof WebField) { + return <p>{Field.toString(field.url.href)}</p>; + } else if (!(field instanceof Promise)) { return <p>{Field.toString(field)}</p>; } diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 6a540269e..75bc90d7a 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -13,6 +13,27 @@ width: 100%; } +.fontIconBadge-container { + position:absolute; + z-index: 1000; + top: 12px; + + .fontIconBadge { + position: absolute; + top: -10px; + right: -10px; + color: white; + background: #f44b42; + font-weight: 300; + border-radius: 100%; + width: 25px; + height: 25px; + text-align: center; + padding-top: 4px; + font-size: 12px; + } +} + .menuButton-round { border-radius: 100%; background-color: black; diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index a6b1678b5..fd71876b0 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -5,13 +5,14 @@ import { createSchema, makeInterface } from '../../../fields/Schema'; import { DocComponent } from '../DocComponent'; import './FontIconBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; -import { StrCast, Cast, NumCast } from '../../../fields/Types'; -import { Utils, emptyFunction } from "../../../Utils"; +import { StrCast, Cast, ScriptCast } from '../../../fields/Types'; +import { Utils, setupMoveUpEvents, returnFalse, emptyFunction } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; -import { Doc } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { ContextMenu } from '../ContextMenu'; import { ScriptField } from '../../../fields/ScriptField'; import { Tooltip } from '@material-ui/core'; +import { DragManager } from '../../util/DragManager'; const FontIconSchema = createSchema({ icon: "string", }); @@ -61,8 +62,9 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( render() { const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); const color = StrCast(this.layoutDoc.color, this._foregroundColor); - const backgroundColor = StrCast(this.layoutDoc._backgroundColor, StrCast(this.rootDoc.backgroundColor, this.props.backgroundColor?.(this.rootDoc))); + const backgroundColor = StrCast(this.layoutDoc._backgroundColor, StrCast(this.rootDoc.backgroundColor, this.props.backgroundColor?.(this.rootDoc, this.props.renderDepth))); const shape = StrCast(this.layoutDoc.iconShape, "round"); + const button = <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, @@ -72,6 +74,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color} size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />} {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>} + {this.props.Document.watchedDocuments ? <FontIconBadge collection={Cast(this.props.Document.watchedDocuments, Doc, null)} /> : (null)} </div> </button>; return !this.layoutDoc.toolTip ? button : @@ -79,4 +82,34 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( {button} </Tooltip>; } +} + +interface FontIconBadgeProps { + collection: Doc; +} + +@observer +export class FontIconBadge extends React.Component<FontIconBadgeProps> { + _notifsRef = React.createRef<HTMLDivElement>(); + + onPointerDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, + (e: PointerEvent) => { + const dragData = new DragManager.DocumentDragData([this.props.collection]); + DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + return true; + }, + returnFalse, emptyFunction, false); + } + + render() { + if (!(this.props.collection instanceof Doc)) return (null); + const length = DocListCast(this.props.collection.data).length; + return <div className="fontIconBadge-container" style={{ width: 15, height: 15, top: 12 }} ref={this._notifsRef}> + <div className="fontIconBadge" style={length > 0 ? { "display": "initial" } : { "display": "none" }} + onPointerDown={this.onPointerDown} > + {length} + </div> + </div>; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d668d332b..216c5f39e 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -158,7 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD if (field) { const funcs: ContextMenuProps[] = []; funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" }); - funcs.push({ description: "Make Background", event: () => this.layoutDoc.isBackground = true, icon: "expand-arrows-alt" }); + funcs.push({ description: "Make Background", event: () => { this.layoutDoc.isBackground = true; this.props.bringToFront?.(this.rootDoc); }, icon: "expand-arrows-alt" }); if (!Doc.UserDoc().noviceMode) { funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); @@ -466,7 +466,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`, height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`, pointerEvents: this.layoutDoc.isBackground ? "none" : undefined, - borderRadius: `${Number(StrCast(this.layoutDoc.borderRoundisng).replace("px", "")) / this.props.ContentScaling()}px` + borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px` }} > <CollectionFreeFormView {...this.props} forceScaling={true} diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 3f942e87b..532e7dc15 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -17,7 +17,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>( public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } render() { return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`} - style={{ background: this.props.backgroundColor?.(this.props.Document) }} > + style={{ background: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }} > <CollectionTreeView {...this.props} ChromeHeight={returnZero} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index ebb916307..c4481b213 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -7,21 +7,16 @@ import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, re import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { Transform } from "../../util/Transform"; +import { ContextMenu } from '../ContextMenu'; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; -import React = require("react"); -import { DocumentView } from './DocumentView'; -import { sortAndDeduplicateDiagnostics } from 'typescript'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { LinkManager } from '../../util/LinkManager'; import { DocumentLinksButton } from './DocumentLinksButton'; -import { ContextMenu } from '../ContextMenu'; -import { undoBatch } from '../../util/UndoManager'; +import React = require("react"); interface Props { linkDoc?: Doc; linkSrc?: Doc; href?: string; - backgroundColor: (doc: Doc) => string; + backgroundColor: (doc: Doc, renderDepth: number) => string; addDocTab: (document: Doc, where: string) => boolean; location: number[]; } diff --git a/src/client/views/nodes/MenuIconBox.tsx b/src/client/views/nodes/MenuIconBox.tsx index 0aa7b327e..5ed8a9b78 100644 --- a/src/client/views/nodes/MenuIconBox.tsx +++ b/src/client/views/nodes/MenuIconBox.tsx @@ -19,10 +19,10 @@ export class MenuIconBox extends DocComponent<FieldViewProps, MenuIconDocument>( render() { - const color = this.props.backgroundColor?.(this.props.Document) === "lightgrey" ? "black" : "white"; - const menuBTN = <div className="menuButton" style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document) }}> + const color = this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) === "lightgrey" ? "black" : "white"; + const menuBTN = <div className="menuButton" style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }}> <div className="menuButton-wrap" - style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document) }} > + style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }} > <FontAwesomeIcon className="menuButton-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={color} size="lg" /> <div className="menuButton-label" style={{ color: color }}> {this.dataDoc.title} </div> </div> diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 323da1233..255a1b2d0 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -60,19 +60,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum if (href) { const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g; const matches = pathCorrectionTest.exec(href); - console.log("\nHere's the { url } being fed into the outer regex:"); - console.log(href); - console.log("And here's the 'properPath' build from the captured filename:\n"); + // console.log("\nHere's the { url } being fed into the outer regex:"); + // console.log(href); + // console.log("And here's the 'properPath' build from the captured filename:\n"); if (matches !== null && href.startsWith(window.location.origin)) { const properPath = Utils.prepend(`/files/pdfs/${matches[0]}`); - console.log(properPath); + //console.log(properPath); if (!properPath.includes(href)) { console.log(`The two (url and proper path) were not equal`); const proto = Doc.GetProto(Document); proto[this.props.fieldKey] = new PdfField(properPath); proto[backup] = href; } else { - console.log(`The two (url and proper path) were equal`); + //console.log(`The two (url and proper path) were equal`); } } else { console.log("Outer matches was null!"); @@ -91,7 +91,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum loaded = (nw: number, nh: number, np: number) => { this.dataDoc[this.props.fieldKey + "-numPages"] = np; - this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = nw * 96 / 72; + this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = Math.max(NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]), nw * 96 / 72); this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = nh * 96 / 72; !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); } diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index a87b0e466..c4d8f1a4f 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -3,6 +3,7 @@ $dark-blue: #5B9FDD; $light-background: #ececec; .presBox-cont { + cursor: auto; position: absolute; display: block; pointer-events: inherit; @@ -12,14 +13,14 @@ $light-background: #ececec; width: 100%; min-width: 20px; height: 100%; - min-height: 41px; + min-height: 35px; letter-spacing: 2px; overflow: hidden; transition: 0.7s opacity ease; .presBox-listCont { position: relative; - height: calc(100% - 25px); + height: calc(100% - 67px); width: 100%; margin-top: 3px; } @@ -50,6 +51,7 @@ $light-background: #ececec; background-color: #323232; .toolbar-button { + cursor: pointer; margin-left: 10px; margin-right: 10px; letter-spacing: 0; @@ -153,7 +155,6 @@ $light-background: #ececec; margin-left: 5px; margin-top: 5px; margin-bottom: 5px; - margin-right: 5px; width: max-content; justify-content: center; align-items: center; @@ -161,6 +162,30 @@ $light-background: #ececec; padding-left: 10px; } + .ribbon-propertyUpDown { + height: 20; + width: 20; + margin-top: 5px; + display: grid; + grid-template-rows: 10px 10px; + + .ribbon-propertyUpDownItem { + cursor: pointer; + color: white; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + background: black; + } + + .ribbon-propertyUpDownItem:hover { + background: darkgrey; + transform: scale(1.05); + } + } + .presBox-subheading { font-size: 11; font-weight: 400; @@ -254,6 +279,18 @@ $light-background: #ececec; width: 100%; } + .presBox-input { + width: 30; + height: 100%; + background: none; + border: none; + text-align: right; + } + + .presBox-input:focus { + outline: none; + } + .ribbon-frameSelector { border: black solid 1px; width: 60px; @@ -281,6 +318,7 @@ $light-background: #ececec; } .numKeyframe { + cursor: pointer; font-size: 10; font-weight: 600; position: relative; @@ -311,6 +349,7 @@ $light-background: #ececec; .ribbon-final-button { + cursor: pointer; position: relative; font-size: 11; font-weight: normal; @@ -329,7 +368,13 @@ $light-background: #ececec; background-color: #979797; } + .ribbon-final-button:hover { + transform: scale(1.05); + transition: all 0.4s; + } + .ribbon-final-button-hidden { + cursor: pointer; position: relative; font-size: 11; font-weight: normal; @@ -347,6 +392,11 @@ $light-background: #ececec; border-radius: 10px; background-color: black; } + + .ribbon-final-button-hidden:hover { + transform: scale(1.05); + transition: all 0.4s; + } } .selectedList { @@ -363,6 +413,7 @@ $light-background: #ececec; } .ribbon-button { + cursor: pointer; font-size: 10.5; font-weight: 200; height: 20; @@ -399,11 +450,9 @@ $light-background: #ececec; grid-template-rows: max-content auto; justify-self: center; margin-top: 10px; - /* padding-left: 10px; */ padding-right: 10px; letter-spacing: normal; width: 100%; - /* max-width: 100%; */ height: max-content; font-weight: 500; position: relative; @@ -412,10 +461,36 @@ $light-background: #ececec; border-bottom: solid 1px darkgrey; .presBox-dropdown:hover { - border: solid 1px #378AD8; - border-bottom-left-radius: 0px; + border: solid 1px $dark-blue; + + .presBox-dropdownIcon { + color: $dark-blue; + } + } + + .presBox-dropdown { + cursor: pointer; + display: grid; + grid-template-columns: auto 20%; + position: relative; + border: solid 1px black; + background-color: $light-background; + border-radius: 5px; + font-size: 10; + height: 25; + padding-left: 5px; + align-items: center; + margin-top: 5px; + margin-bottom: 5px; + font-weight: 200; + width: 100%; + min-width: max-content; + max-width: 200px; + overflow: visible; + .presBox-dropdownOption { + cursor: pointer; font-size: 11; display: block; padding-left: 10px; @@ -431,13 +506,13 @@ $light-background: #ececec; .presBox-dropdownOption.active { position: relative; - background-color: #aedef8; + background-color: $light-blue; } .presBox-dropdownOptions { position: absolute; - top: 24px; - left: -1px; + top: 23px; + left: -2px; z-index: 200; width: 85%; min-width: max-content; @@ -449,34 +524,6 @@ $light-background: #ececec; } .presBox-dropdownIcon { - color: #378AD8; - } - } - - .presBox-dropdown { - display: grid; - grid-template-columns: auto 20%; - position: relative; - border: solid 1px black; - background-color: $light-background; - border-radius: 5px; - font-size: 10; - height: 25; - padding-left: 5px; - align-items: center; - margin-top: 5px; - margin-bottom: 5px; - font-weight: 200; - width: 100%; - min-width: max-content; - max-width: 200px; - overflow: visible; - - .presBox-dropdownOptions { - display: none; - } - - .presBox-dropdownIcon { position: relative; color: black; align-self: center; @@ -509,6 +556,7 @@ $light-background: #ececec; } .dropdown-play-button { + cursor: pointer; font-size: 12; padding-left: 5px; padding-right: 5px; @@ -523,6 +571,7 @@ $light-background: #ececec; } .presBox-button-left { + cursor: pointer; position: relative; align-self: flex-start; justify-self: flex-start; @@ -539,6 +588,7 @@ $light-background: #ececec; } .presBox-button-right { + cursor: pointer; position: relative; text-align: center; border-left: solid 1px darkgrey; @@ -561,6 +611,7 @@ $light-background: #ececec; } .dropdown-play { + cursor: pointer; right: 0px; top: calc(100% + 2px); display: none; @@ -581,6 +632,7 @@ $light-background: #ececec; } .open-layout { + cursor: pointer; position: relative; display: flex; align-items: center; @@ -612,6 +664,7 @@ $light-background: #ececec; } .layout { + cursor: pointer; align-self: center; justify-self: center; margin-top: 5; @@ -682,6 +735,7 @@ $light-background: #ececec; grid-template-columns: auto auto; .presBox-viewPicker { + cursor: pointer; height: 25; position: relative; display: inline-block; @@ -708,6 +762,7 @@ $light-background: #ececec; } .presBox-button { + cursor: pointer; height: 25px; border-radius: 5px; display: none; @@ -746,7 +801,7 @@ $light-background: #ececec; } - .miniPresOverlay { + .presPanelOverlay { background-color: #323232; color: white; border-radius: 5px; @@ -761,7 +816,7 @@ $light-background: #ececec; right: 10px; transition: all 0.2s; - .miniPres-button-text { + .presPanel-button-text { display: flex; height: 20; width: max-content; @@ -778,13 +833,13 @@ $light-background: #ececec; transition: all 0.3s; } - .miniPres-divider { + .presPanel-divider { width: 0.5px; height: 80%; border-right: solid 1px #5a5a5a; } - .miniPres-button-frame { + .presPanel-button-frame { justify-self: center; align-self: center; align-items: center; @@ -799,7 +854,8 @@ $light-background: #ececec; border-radius: 5px; } - .miniPres-button { + .presPanel-button { + cursor: pointer; display: flex; height: 20; min-width: 20; @@ -811,11 +867,11 @@ $light-background: #ececec; transition: all 0.3s; } - .miniPres-button:hover { + .presPanel-button:hover { background-color: #5a5a5a; } - .miniPres-button-text:hover { + .presPanel-button-text:hover { background-color: #5a5a5a; } } @@ -894,4 +950,82 @@ $light-background: #ececec; .select { font-size: 100%; } -}
\ No newline at end of file +} + +.miniPres:hover { + opacity: 1; +} + +.miniPres { + cursor: grab; + position: absolute; + overflow: hidden; + right: 10; + top: 10; + opacity: 0.1; + transition: all 0.4s; + /* border: solid 1px; */ + color: white; + /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ + + .miniPresOverlay { + display: grid; + grid-template-columns: auto auto auto auto auto auto auto auto; + grid-template-rows: 100%; + height: 100%; + justify-items: center; + align-items: center; + + .miniPres-button-text { + cursor: pointer; + display: flex; + height: 20; + font-weight: 400; + min-width: 100%; + border-radius: 5px; + align-items: center; + justify-content: center; + transition: all 0.3s; + } + + .miniPres-button-frame { + justify-self: center; + align-self: center; + align-items: center; + display: grid; + grid-template-columns: auto auto auto; + justify-content: space-around; + font-size: 11; + margin-left: 7; + width: 30; + height: 85%; + background-color: rgba(91, 157, 221, 0.4); + border-radius: 5px; + } + + .miniPres-divider { + width: 0.5px; + height: 80%; + border-right: solid 2px #5a5a5a; + } + + .miniPres-button { + cursor: pointer; + display: flex; + height: 20; + min-width: 20; + border-radius: 100%; + align-items: center; + justify-content: center; + transition: all 0.3s; + } + + .miniPres-button:hover { + background-color: #5a5a5a; + } + + .miniPres-button-text:hover { + background-color: #5a5a5a; + } + } +} diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 849fc4076..5fc76223d 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -23,10 +23,12 @@ import { Scripting } from "../../util/Scripting"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { List } from "../../../fields/List"; import { Tooltip } from "@material-ui/core"; -import { CollectionFreeFormViewChrome } from "../collections/CollectionMenu"; import { actionAsync } from "mobx-utils"; import { SelectionManager } from "../../util/SelectionManager"; import { AudioBox } from "./AudioBox"; +import { DocumentView } from "./DocumentView"; +import { SketchPicker, ColorState } from "react-color"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -54,13 +56,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @observable private presentTools: boolean = false; @observable private pathBoolean: boolean = false; @observable private expandBoolean: boolean = false; + @observable private openMovementDropdown: boolean = false; + @observable private openEffectDropdown: boolean = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); - PresBox.Instance = this; + if (Doc.UserDoc().activePresentation = this.rootDoc) PresBox.Instance = this; if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ title: "pres element template", backgroundColor: "transparent", _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" @@ -92,8 +96,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + componentWillUnmount() { document.removeEventListener("keydown", this.keyEvents, true); + this.turnOffEdit(); } componentDidMount() { @@ -106,6 +112,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> updateCurrentPresentation = () => { Doc.UserDoc().activePresentation = this.rootDoc; + PresBox.Instance = this; } /** @@ -123,25 +130,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const lastFrame = Cast(presTargetDoc.lastFrame, "number", null); const curFrame = NumCast(presTargetDoc.currentFrame); let internalFrames: boolean = false; - if (presTargetDoc.presProgressivize || presTargetDoc.zoomProgressivize || presTargetDoc.scrollProgressivize) internalFrames = true; + if (presTargetDoc.presProgressivize || activeItem.zoomProgressivize || presTargetDoc.scrollProgressivize) internalFrames = true; // Case 1: There are still other frames and should go through all frames before going to next slide if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { presTargetDoc._viewTransition = "all 1s"; setTimeout(() => presTargetDoc._viewTransition = undefined, 1010); presTargetDoc.currentFrame = curFrame + 1; if (presTargetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(presTargetDoc, currentFrame); - if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); - if (presTargetDoc.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc); + if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, presTargetDoc); + else presTargetDoc.editing = true; + if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc); // Case 2: Audio or video therefore wait to play the audio or video before moving on - } else if ((presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio) { + } else if ((presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio && this.layoutDoc.presStatus !== 'auto') { AudioBox.Instance.playFrom(0); this._moveOnFromAudio = true; // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide } else if (this.childDocs[this.itemIndex + 1] !== undefined) { const nextSelected = this.itemIndex + 1; + if (presTargetDoc.type === DocumentType.AUDIO) AudioBox.Instance.pause(); this.gotoDocument(nextSelected, this.itemIndex); const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); if (activeNext && targetNext.type === DocumentType.AUDIO && activeNext.playAuto) { + AudioBox.Instance.playFrom(0); + this._moveOnFromAudio = true; } else this._moveOnFromAudio = false; } } @@ -156,10 +167,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> back = () => { this.updateCurrentPresentation(); const docAtCurrent = this.childDocs[this.itemIndex]; - if (docAtCurrent) { + const targetDoc = Cast(docAtCurrent.presentationTargetDoc, Doc, null); + const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); + const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); + const lastFrame = Cast(targetDoc.lastFrame, "number", null); + const curFrame = NumCast(targetDoc.currentFrame); + if (lastFrame !== undefined && curFrame >= 1) { + this.prevKeyframe(targetDoc, docAtCurrent); + } else if (docAtCurrent) { let prevSelected = this.itemIndex; prevSelected = Math.max(0, prevSelected - 1); this.gotoDocument(prevSelected, this.itemIndex); + if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc.currentFrame = NumCast(prevTargetDoc.lastFrame); } } @@ -174,8 +193,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (presTargetDoc?.lastFrame !== undefined) { presTargetDoc.currentFrame = 0; } - this.navigateToElement(this.childDocs[index]); //Handles movement to element this._selectedArray = [this.childDocs[index]]; //Update selected array + //Handles movement to element + if (this.layoutDoc._viewType === "stacking") this.navigateToElement(this.childDocs[index]); this.onHideDocument(); //Handles hide after/before } }); @@ -197,8 +217,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.turnOffEdit(); if (this.itemIndex >= 0) { - if (targetDoc) { - if (srcContext) this.layoutDoc.presCollection = srcContext; + if (srcContext && targetDoc) { + this.layoutDoc.presCollection = srcContext; } else if (targetDoc) this.layoutDoc.presCollection = targetDoc; } if (collectionDocView) { @@ -211,21 +231,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const docToJump = curDoc; const willZoom = false; - //docToJump stayed same meaning, it was not in the group or was the last element in the group - if (targetDoc.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { - this.zoomProgressivizeNext(targetDoc); - } else if (docToJump === curDoc) { - //checking if curDoc has navigation open - if (curDoc.presNavButton && targetDoc) { - await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); - } else if (curDoc.presZoomButton && targetDoc) { + // If openDocument is selected then it should open the document for the user + if (activeItem.openDocument) { + this.props.addDocTab(activeItem, "replace"); + } else + //docToJump stayed same meaning, it was not in the group or was the last element in the group + if (activeItem.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { + this.zoomProgressivizeNext(targetDoc); + } else if (docToJump === curDoc) { + //checking if curDoc has navigation open + if (curDoc.presNavButton && targetDoc) { + await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); + } else if (curDoc.presZoomButton && targetDoc) { + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); + } + } else { //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); + targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext); } - } else { - //awaiting jump so that new scale can be found, since jumping is async - targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext); - } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. // TODO: Add option to remove presPinView @@ -234,10 +258,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> targetDoc._panY = activeItem.presPinViewY; targetDoc._viewScale = activeItem.presPinViewScale; } - // If openDocument is selected then it should open the document for the user - if (collectionDocView && activeItem.openDocument) { - collectionDocView.props.addDocTab(activeItem, "inPlace"); - } // If website and has presWebsite data associated then on click it should // go back to that specific website // TODO: Add progressivize for navigating web (storing websites for given frames) @@ -250,22 +270,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * Uses the viewfinder to progressivize through the different views of a single collection. * @param presTargetDoc: document for which internal zoom is used */ - zoomProgressivizeNext = (presTargetDoc: Doc) => { - const srcContext = Cast(presTargetDoc.context, Doc, null); - const docView = DocumentManager.Instance.getDocumentView(presTargetDoc); - const vfLeft: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-left-indexed"]); - const vfWidth: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-width-indexed"]); - const vfTop: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-top-indexed"]); - const vfHeight: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-height-indexed"]); + zoomProgressivizeNext = (activeItem: Doc) => { + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const srcContext = Cast(targetDoc?.context, Doc, null); + const docView = DocumentManager.Instance.getDocumentView(targetDoc); + const vfLeft = this.checkList(targetDoc, activeItem["viewfinder-left-indexed"]); + const vfWidth = this.checkList(targetDoc, activeItem["viewfinder-width-indexed"]); + const vfTop = this.checkList(targetDoc, activeItem["viewfinder-top-indexed"]); + const vfHeight = this.checkList(targetDoc, activeItem["viewfinder-height-indexed"]); // Case 1: document that is not a Golden Layout tab if (srcContext) { const srcDocView = DocumentManager.Instance.getDocumentView(srcContext); if (srcDocView) { - const layoutdoc = Doc.Layout(presTargetDoc); + const layoutdoc = Doc.Layout(targetDoc); const panelWidth: number = srcDocView.props.PanelWidth(); const panelHeight: number = srcDocView.props.PanelHeight(); - const newPanX = NumCast(presTargetDoc.x) + NumCast(layoutdoc._width) / 2; - const newPanY = NumCast(presTargetDoc.y) + NumCast(layoutdoc._height) / 2; + const newPanX = NumCast(targetDoc.x) + NumCast(layoutdoc._width) / 2; + const newPanY = NumCast(targetDoc.y) + NumCast(layoutdoc._height) / 2; const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); srcContext._panX = newPanX + (vfLeft + (vfWidth / 2)); srcContext._panY = newPanY + (vfTop + (vfHeight / 2)); @@ -277,9 +298,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const panelWidth: number = docView.props.PanelWidth(); const panelHeight: number = docView.props.PanelHeight(); const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - presTargetDoc._panX = vfLeft + (vfWidth / 2); - presTargetDoc._panY = vfTop + (vfWidth / 2); - presTargetDoc._viewScale = newScale; + targetDoc._panX = vfLeft + (vfWidth / 2); + targetDoc._panY = vfTop + (vfWidth / 2); + targetDoc._viewScale = newScale; } const resize = document.getElementById('resizable'); if (resize) { @@ -299,7 +320,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onHideDocument = () => { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); - const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); + const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null); if (tagDoc) tagDoc.opacity = 1; if (curDoc.presHideTillShownButton) { if (index > this.itemIndex) { @@ -319,40 +340,58 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } + //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @undoBatch @action startAutoPres = (startSlide: number) => { this.updateCurrentPresentation(); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + let activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + let targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + let duration = NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition); + const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); + const load = async () => { // Wrap the loop into an async function for this to work + for (var i = startSlide; i < this.childDocs.length; i++) { + activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + duration = NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition); + if (duration <= 100) { duration = 2500; } + if (NumCast(targetDoc.lastFrame) > 0) { + for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { + await timer(duration / NumCast(targetDoc.lastFrame)); + this.next(); + } + } + await timer(duration); this.next(); // then the created Promise can be awaited + if (i === this.childDocs.length - 1) setTimeout(() => { clearTimeout(this._presTimer); if (this.layoutDoc.presStatus === 'auto') this.layoutDoc.presStatus = "manual"; }, duration); + } + }; if (this.layoutDoc.presStatus === "auto") { - if (this._presTimer) clearInterval(this._presTimer); + if (this._presTimer) clearTimeout(this._presTimer); this.layoutDoc.presStatus = "manual"; } else { this.layoutDoc.presStatus = "auto"; this.startPresentation(startSlide); this.gotoDocument(startSlide, this.itemIndex); - this._presTimer = setInterval(() => { - if (this.itemIndex + 1 < this.childDocs.length) this.next(); - else { - clearInterval(this._presTimer); - this.layoutDoc.presStatus = "manual"; - } - }, targetDoc.presDuration ? NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition) : 2000); + load(); } } //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. - // TODO: Ensure resetPresentation is called when the presentation is closed resetPresentation = () => { this.updateCurrentPresentation(); this.rootDoc._itemIndex = 0; } @action togglePath = () => this.pathBoolean = !this.pathBoolean; - @action toggleExpand = () => this.expandBoolean = !this.expandBoolean; + @action toggleExpandMode = () => { + this.rootDoc.expandBoolean = !this.rootDoc.expandBoolean; + this.childDocs.forEach((doc) => { + if (this.rootDoc.expandBoolean) doc.presExpandInlineButton = true; + else if (!this.rootDoc.expandBoolean) doc.presExpandInlineButton = false; + }); + } /** * The function that starts the presentation at the given index, also checking if actions should be applied @@ -376,17 +415,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * The method called to open the presentation as a minimized view * TODO: Look at old updateMinimize and compare... */ + @undoBatch + @action updateMinimize = () => { - const srcContext = Cast(this.rootDoc.presCollection, Doc, null); - this.turnOffEdit(); - if (srcContext) { - if (srcContext.miniPres) { - srcContext.miniPres = false; - CollectionDockingView.AddRightSplit(this.rootDoc); - } else { - srcContext.miniPres = true; - this.props.addDocTab?.(this.rootDoc, "close"); - } + if (this.layoutDoc.inOverlay) { + this.layoutDoc.presStatus = 'edit'; + Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); + CollectionDockingView.AddRightSplit(this.rootDoc); + this.layoutDoc.inOverlay = false; + } else { + this.layoutDoc.presStatus = 'manual'; + const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); + this.rootDoc.y = pt[1] + 10; + this.rootDoc._height = 35; + this.rootDoc._width = 250; + this.props.addDocTab?.(this.rootDoc, "close"); + Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); } } @@ -426,6 +471,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> break; case 'jump': //Jump Cut targetDoc.presTransition = 0; + activeItem.presZoomButton = true; activeItem.presSwitchButton = !activeItem.presSwitchButton; if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut'; else activeItem.presMovement = 'None'; @@ -449,6 +495,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } else { doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); !this.childDocs.includes(doc) && (doc.presZoomButton = true); + if (this.rootDoc.expandBoolean) doc.presExpandInlineButton = true; } }); return true; @@ -488,20 +535,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return list; } + @action + selectPres = () => { + const presDocView = DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc)!; + SelectionManager.SelectDoc(presDocView, false); + } + //Regular click @action selectElement = (doc: Doc) => { this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex)); + this.selectPres(); } //Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { if (!this._selectedArray.includes(doc)) { - this._selectedArray.push(this.childDocs[this.childDocs.indexOf(doc)]); + this._selectedArray.push(doc); this._eleArray.push(ref); this._dragArray.push(drag); } + this.selectPres(); } //Shift click @@ -516,17 +571,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this._dragArray.push(drag); } } + this.selectPres(); } - // Key for when the presentaiton is active (according to Selection Manager) + // Key for when the presentaiton is active @action keyEvents = (e: KeyboardEvent) => { let handled = false; const anchorNode = document.activeElement as HTMLDivElement; if (anchorNode && anchorNode.className?.includes("lm_title")) return; if (e.keyCode === 27) { // Escape key - if (this.layoutDoc.presStatus === "edit") this._selectedArray = []; + if (this.layoutDoc.inOverlay) { this.updateMinimize(); } + else if (this.layoutDoc.presStatus === "edit") { this._selectedArray = []; this._eleArray = []; this._dragArray = []; } else this.layoutDoc.presStatus = "edit"; + if (this._presTimer) clearTimeout(this._presTimer); handled = true; } if ((e.metaKey || e.altKey) && e.keyCode === 65) { // Ctrl-A to select all if (this.layoutDoc.presStatus === "edit") { @@ -534,14 +592,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> handled = true; } } if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back - this.back(); + this.back(); if (this._presTimer) clearTimeout(this._presTimer); handled = true; } if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next - this.next(); + this.next(); if (this._presTimer) clearTimeout(this._presTimer); handled = true; } if (e.keyCode === 32) { // spacebar to 'present' or autoplay if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0); - else this.layoutDoc.presStatus = "manual"; + else this.layoutDoc.presStatus = "manual"; if (this._presTimer) clearTimeout(this._presTimer); handled = true; } if (e.keyCode === 8) { // delete selected items @@ -565,23 +623,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action viewPaths = async () => { const srcContext = Cast(this.rootDoc.presCollection, Doc, null); - if (this.pathBoolean) { - if (srcContext) { - this.togglePath(); - srcContext._fitToBox = false; - srcContext._viewType = "freeform"; - srcContext.presPathView = false; - } - } else { - if (srcContext) { - this.togglePath(); - srcContext._fitToBox = true; - srcContext._viewType = "freeform"; - srcContext.presPathView = true; - } + if (this.pathBoolean && srcContext) { + this.togglePath(); + srcContext.presPathView = false; + } else if (srcContext) { + this.togglePath(); + srcContext.presPathView = true; } - const viewType = srcContext?._viewType; - const fit = srcContext?._fitToBox; } // Adds the index in the pres path graphically @@ -589,7 +637,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const order: JSX.Element[] = []; this.childDocs.forEach((doc, index) => { const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(targetDoc.context, Doc, null); + const srcContext = Cast(targetDoc?.context, Doc, null); // Case A: Document is contained within the colleciton if (this.rootDoc.presCollection === srcContext) { order.push( @@ -619,7 +667,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> let pathPoints = ""; this.childDocs.forEach((doc, index) => { const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(targetDoc.context, Doc, null); + const srcContext = Cast(targetDoc?.context, Doc, null); if (targetDoc && this.rootDoc.presCollection === srcContext) { const n1x = NumCast(targetDoc.x) + (NumCast(targetDoc._width) / 2); const n1y = NumCast(targetDoc.y) + (NumCast(targetDoc._height) / 2); @@ -669,16 +717,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } // Converts seconds to ms and updates presTransition - setTransitionTime = (number: String) => { - const timeInMS = Number(number) * 1000; + setTransitionTime = (number: String, change?: number) => { + let timeInMS = Number(number) * 1000; + if (change) timeInMS += change; + if (timeInMS < 100) timeInMS = 100; + if (timeInMS > 10000) timeInMS = 10000; const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); if (targetDoc) targetDoc.presTransition = timeInMS; } // Converts seconds to ms and updates presDuration - setDurationTime = (number: String) => { - const timeInMS = Number(number) * 1000; + setDurationTime = (number: String, change?: number) => { + let timeInMS = Number(number) * 1000; + if (change) timeInMS += change; + if (timeInMS < 100) timeInMS = 100; + if (timeInMS > 20000) timeInMS = 20000; const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); if (targetDoc) targetDoc.presDuration = timeInMS; @@ -689,19 +743,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); if (activeItem && targetDoc) { - const transitionSpeed = targetDoc.presTransition ? String(Number(targetDoc.presTransition) / 1000) : 0.5; - let duration = targetDoc.presDuration ? String(Number(targetDoc.presDuration) / 1000) : 2; + const transitionSpeed = targetDoc.presTransition ? NumCast(targetDoc.presTransition) / 1000 : 0.5; + let duration = targetDoc.presDuration ? NumCast(targetDoc.presDuration) / 1000 : 2; if (targetDoc.type === DocumentType.AUDIO) duration = NumCast(targetDoc.duration); const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return ( - <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> <div className="ribbon-box"> Movement - <div className="presBox-dropdown" onPointerDown={e => e.stopPropagation()}> + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? 'solid 2px #5B9FDD' : 'solid 1px black' }}> {activeItem.presMovement} - <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}> + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> <div className={`presBox-dropdownOption ${activeItem.presMovement === 'None' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('none')}>None</div> <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('zoom')}>Pan and Zoom</div> <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('pan')}>Pan</div> @@ -709,8 +763,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "inline-flex" : "none" }}> - <div className="presBox-subheading" >Transition Speed</div> - <div className="ribbon-property"> {transitionSpeed} s </div> + <div className="presBox-subheading">Transition Speed</div> + <div className="ribbon-property"> + <input className="presBox-input" + type="number" value={transitionSpeed} + onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onChange={action((e) => this.setTransitionTime(e.target.value))} /> s + </div> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={() => this.setTransitionTime(String(transitionSpeed), 1000)}> + <FontAwesomeIcon icon={"caret-up"} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={() => this.setTransitionTime(String(transitionSpeed), -1000)}> + <FontAwesomeIcon icon={"caret-down"} /> + </div> + </div> </div> <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> <div className={`slider-headers ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`}> @@ -727,9 +794,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> <div className="ribbon-doubleButton" > <div className="presBox-subheading">Slide Duration</div> - <div className="ribbon-property"> {duration} s </div> + <div className="ribbon-property"> + <input className="presBox-input" + type="number" value={duration} + onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onChange={action((e) => this.setDurationTime(e.target.value))} /> s + </div> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={() => this.setDurationTime(String(duration), 1000)}> + <FontAwesomeIcon icon={"caret-up"} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={() => this.setDurationTime(String(duration), -1000)}> + <FontAwesomeIcon icon={"caret-down"} /> + </div> + </div> </div> - <input type="range" step="0.1" min="0.1" max="10" value={duration} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} /> + <input type="range" step="0.1" min="0.1" max="20" value={duration} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} /> <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> <div className="slider-text">Short</div> <div className="slider-text">Medium</div> @@ -738,12 +818,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> <div className="ribbon-box"> Effects - <div className="presBox-dropdown" - onPointerDown={e => e.stopPropagation()} - > + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? 'solid 2px #5B9FDD' : 'solid 1px black' }}> {effect} - <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}> + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}> <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'None'}>None</div> <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Fade'}>Fade In</div> <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Flip'}>Flip</div> @@ -774,7 +852,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> Apply to all </div> </div> - </div> + </div > ); } } @@ -793,6 +871,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return effect; } + @undoBatch + @action applyTo = (array: Doc[]) => { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); @@ -803,12 +883,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> tagDoc.presTransition = targetDoc.presTransition; tagDoc.presDuration = targetDoc.presDuration; tagDoc.presEffect = targetDoc.presEffect; + tagDoc.presEffectDirection = targetDoc.presEffectDirection; + curDoc.presMovement = activeItem.presMovement; + curDoc.presHideTillShownButton = activeItem.presHideTillShownButton; + curDoc.presHideAfterButton = activeItem.presHideAfterButton; } }); } - - private inputRef = React.createRef<HTMLInputElement>(); - @computed get optionsDropdown() { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); @@ -838,9 +919,41 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } }}>Presentation pin view</div> </div> - <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> - <div className="ribbon-button" onClick={this.progressivizeText}>Store original website</div> + <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan X</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewX)} + onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> + </div> + </div> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan Y</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewY)} + onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> + </div> + </div> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scale</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewScale)} + onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> + </div> + </div> </div> + {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> + <div className="ribbon-button" onClick={this.progressivizeText}>Store original website</div> + </div> */} </div> </div> </div > @@ -865,11 +978,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="title" style={{ alignSelf: 'center' }}>Title</div> <div className="content">Text goes here</div> </div> - {/* <div className="layout" style={{ border: this.layout === 'twoColumns' ? 'solid 2px #5b9ddd' : '' }} onClick={() => runInAction(() => { this.layout = 'twoColumns'; this.createNewSlide(this.layout); })}> - <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div> - <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div> - <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div> - </div> */} </div> </div> </div > @@ -884,13 +992,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get newDocumentDropdown() { return ( <div> - <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> Slide Title: <br></br> - <input className="ribbon-textInput" placeholder="..." type="text" name="fname" ref={this.inputRef} onChange={(e) => { - e.stopPropagation(); - runInAction(() => this.title = e.target.value); - }}></input> + <input className="ribbon-textInput" placeholder="..." type="text" name="fname" + onFocus={() => { + document.removeEventListener("keydown", this.keyEvents, true); + }} + onChange={(e) => { + e.stopPropagation(); + e.preventDefault(); + runInAction(() => this.title = e.target.value); + }}></input> </div> <div className="ribbon-box"> Choose type: @@ -1007,7 +1120,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // Case in which the document has keyframes to navigate to next key frame @undoBatch @action - nextKeyframe = (tagDoc: Doc): void => { + nextKeyframe = (tagDoc: Doc, activeItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); const currentFrame = Cast(tagDoc.currentFrame, "number", null); if (currentFrame === undefined) { @@ -1016,23 +1129,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); - CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); + CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc.currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc.currentFrame), NumCast(tagDoc.lastFrame)); - if (tagDoc.zoomProgressivize) { + if (activeItem.zoomProgressivize) { const resize = document.getElementById('resizable'); if (resize) { - resize.style.width = this.checkList(tagDoc, tagDoc["viewfinder-width-indexed"]) + 'px'; - resize.style.height = this.checkList(tagDoc, tagDoc["viewfinder-height-indexed"]) + 'px'; - resize.style.top = this.checkList(tagDoc, tagDoc["viewfinder-top-indexed"]) + 'px'; - resize.style.left = this.checkList(tagDoc, tagDoc["viewfinder-left-indexed"]) + 'px'; + resize.style.width = this.checkList(tagDoc, activeItem["viewfinder-width-indexed"]) + 'px'; + resize.style.height = this.checkList(tagDoc, activeItem["viewfinder-height-indexed"]) + 'px'; + resize.style.top = this.checkList(tagDoc, activeItem["viewfinder-top-indexed"]) + 'px'; + resize.style.left = this.checkList(tagDoc, activeItem["viewfinder-left-indexed"]) + 'px'; } } } @undoBatch @action - prevKeyframe = (tagDoc: Doc): void => { + prevKeyframe = (tagDoc: Doc, activeItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); const currentFrame = Cast(tagDoc.currentFrame, "number", null); if (currentFrame === undefined) { @@ -1041,13 +1154,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); tagDoc.currentFrame = Math.max(0, (currentFrame || 0) - 1); - if (tagDoc.zoomProgressivize) { + if (activeItem.zoomProgressivize) { const resize = document.getElementById('resizable'); if (resize) { - resize.style.width = this.checkList(tagDoc, tagDoc["viewfinder-width-indexed"]) + 'px'; - resize.style.height = this.checkList(tagDoc, tagDoc["viewfinder-height-indexed"]) + 'px'; - resize.style.top = this.checkList(tagDoc, tagDoc["viewfinder-top-indexed"]) + 'px'; - resize.style.left = this.checkList(tagDoc, tagDoc["viewfinder-left-indexed"]) + 'px'; + resize.style.width = this.checkList(tagDoc, activeItem["viewfinder-width-indexed"]) + 'px'; + resize.style.height = this.checkList(tagDoc, activeItem["viewfinder-height-indexed"]) + 'px'; + resize.style.top = this.checkList(tagDoc, activeItem["viewfinder-top-indexed"]) + 'px'; + resize.style.left = this.checkList(tagDoc, activeItem["viewfinder-left-indexed"]) + 'px'; } } } @@ -1067,58 +1180,72 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> case DocumentType.AUDIO: type = "Audio"; break; case DocumentType.VID: type = "Video"; break; case DocumentType.IMG: type = "Image"; break; + case DocumentType.WEB: type = "Web page"; break; default: type = "Other node"; break; } } return type; } + @observable private openActiveColorPicker: boolean = false; + @observable private openViewedColorPicker: boolean = false; + + + @computed get progressivizeDropdown() { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - + const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black"; + const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black"; if (activeItem && targetDoc) { return ( <div> <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> {this.stringType} selected - <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Child documents</div> - <div className="ribbon-button" style={{ display: activeItem.presProgressivize ? "flex" : "none", backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div> + <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Contents</div> + <div className="ribbon-button" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div> </div> - <div className="ribbon-doubleButton" style={{ display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Internal zoom</div> - <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Viewfinder</div> - {/* <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editSnapZoomProgressivize ? "#aedef8" : "" }} onClick={this.editSnapZoomProgressivize}>Snapshot</div> */} + <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> + <div className="presBox-subheading">Active text color</div> + <div className="ribbon-property" style={{ backgroundColor: activeFontColor }} onClick={action(() => { console.log("hi"); this.openActiveColorPicker = !this.openActiveColorPicker; })}> + </div> + </div> + {this.activeColorPicker} + <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> + <div className="presBox-subheading">Viewed font color</div> + <div className="ribbon-property" style={{ backgroundColor: viewedFontColor }} onClick={action(() => this.openViewedColorPicker = !this.openViewedColorPicker)}> + </div> </div> - {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> - <div className="ribbon-button" onClick={this.progressivizeText}>Text progressivize</div> - <div className="ribbon-button" style={{ display: activeItem.textProgressivize ? "flex" : "none", backgroundColor: targetDoc.editTextProgressivize ? "#aedef8" : "" }} onClick={this.editTextProgressivize}>Edit</div> + {this.viewedColorPicker} + {/* <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Zoom</div> + <div className="ribbon-button" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Edit</div> </div> */} - <div className="ribbon-doubleButton" style={{ display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll progressivize</div> - <div className="ribbon-button" style={{ display: activeItem.scrollProgressivize ? "flex" : "none", backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> + <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll</div> + <div className="ribbon-button" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> </div> </div> <div className="ribbon-final-box" style={{ display: activeItem.zoomProgressivize || activeItem.scrollProgressivize || activeItem.presProgressivize || activeItem.textProgressivize ? "grid" : "none" }}> Frames <div className="ribbon-doubleButton"> <div className="ribbon-frameSelector"> - <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc); }}> + <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}> <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> </div> - <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: targetDoc.editing ? "#5a9edd" : "#5a9edd" }} + <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.editing ? "white" : "black", backgroundColor: targetDoc.editing ? "#5B9FDD" : "#AEDDF8" }} onClick={action(() => targetDoc.editing = !targetDoc.editing)} > {NumCast(targetDoc.currentFrame)} </div> - <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc); }}> + <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}> <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> </div> </div> <Tooltip title={<><div className="dash-tooltip">{"Last frame"}</div></>}><div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div></Tooltip> </div> - <div className="ribbon-button" style={{ height: 20, backgroundColor: "#5a9edd" }} onClick={() => console.log(" TODO: play frames")}>Play</div> + <div className="ribbon-button" style={{ height: 20, backgroundColor: "#AEDDF8" }} onClick={() => console.log(" TODO: play frames")}>Play</div> </div> </div> </div> @@ -1126,43 +1253,70 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } + @undoBatch + @action + switchActive = (color: ColorState) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const val = String(color.hex); + targetDoc["pres-text-color"] = val; + return true; + } + @undoBatch + @action + switchPresented = (color: ColorState) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const val = String(color.hex); + targetDoc["pres-text-viewed-color"] = val; + return true; + } + + @computed get activeColorPicker() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + return !this.openActiveColorPicker ? (null) : <SketchPicker onChange={this.switchActive} + presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', + '#FFFFFF', '#f1efeb', 'transparent']} + color={StrCast(targetDoc["pres-text-color"])} />; + } + + @computed get viewedColorPicker() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + return !this.openViewedColorPicker ? (null) : <SketchPicker onChange={this.switchPresented} + presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', + '#FFFFFF', '#f1efeb', 'transparent']} + color={StrCast(targetDoc["pres-text-viewed-color"])} />; + } + turnOffEdit = () => { this.childDocs.forEach((doc) => { doc.editSnapZoomProgressivize = false; doc.editZoomProgressivize = false; doc.editScrollProgressivize = false; const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); - targetDoc.editSnapZoomProgressivize = false; - targetDoc.editZoomProgressivize = false; - targetDoc.editScrollProgressivize = false; - if (doc.type === DocumentType.WEB) { - doc.presWebsite = doc.data; + if (targetDoc) { + targetDoc.editZoomProgressivize = false; + targetDoc.editScrollProgressivize = false; } }); } //Toggle whether the user edits or not @action - editSnapZoomProgressivize = (e: React.MouseEvent) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - if (!targetDoc.editSnapZoomProgressivize) { - targetDoc.editSnapZoomProgressivize = true; - } else { - targetDoc.editSnapZoomProgressivize = false; - } - - } - - //Toggle whether the user edits or not - @action editZoomProgressivize = (e: React.MouseEvent) => { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); if (!targetDoc.editZoomProgressivize) { + if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true; targetDoc.editZoomProgressivize = true; + activeItem.editZoomProgressivize = true; } else { targetDoc.editZoomProgressivize = false; + activeItem.editZoomProgressivize = false; } } @@ -1172,6 +1326,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); if (!targetDoc.editScrollProgressivize) { + if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; } targetDoc.editScrollProgressivize = true; } else { targetDoc.editScrollProgressivize = false; @@ -1185,7 +1340,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); activeItem.scrollProgressivize = !activeItem.scrollProgressivize; const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - targetDoc.scrollProgressivize = !targetDoc.zoomProgressivize; + targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc.currentFrame), true); if (targetDoc.editScrollProgressivize) { targetDoc.editScrollProgressivize = false; @@ -1202,52 +1357,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> activeItem.zoomProgressivize = !activeItem.zoomProgressivize; const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize; - CollectionFreeFormDocumentView.setupZoom(targetDoc, true); - if (targetDoc.editZoomProgressivize) { - targetDoc.editZoomProgressivize = false; + CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc, true); + if (activeItem.editZoomProgressivize) { + activeItem.editZoomProgressivize = false; targetDoc.currentFrame = 0; targetDoc.lastFrame = 0; } } - //Progressivize Text nodes - @action - editTextProgressivize = (e: React.MouseEvent) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - targetDoc.currentFrame = targetDoc.lastFrame; - if (targetDoc?.editTextProgressivize) { - targetDoc.editTextProgressivize = false; - } else { - targetDoc.editTextProgressivize = true; - } - } - - @action - progressivizeText = (e: React.MouseEvent) => { - e.stopPropagation(); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - activeItem.presProgressivize = !activeItem.presProgressivize; - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - targetDoc.presProgressivize = !targetDoc.presProgressivize; - if (activeItem.presProgressivize) { - targetDoc.currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true); - targetDoc.lastFrame = docs.length - 1; - } - } - //Progressivize Child Docs @action editProgressivize = (e: React.MouseEvent) => { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); targetDoc.currentFrame = targetDoc.lastFrame; - if (targetDoc?.editProgressivize) { - targetDoc.editProgressivize = false; - } else { + if (!targetDoc.editProgressivize) { + if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; } targetDoc.editProgressivize = true; + } else { + targetDoc.editProgressivize = false; } } @@ -1258,6 +1386,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); if (!activeItem.presProgressivize) { + targetDoc.editing = false; activeItem.presProgressivize = true; targetDoc.presProgressivize = true; targetDoc.currentFrame = 0; @@ -1267,11 +1396,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> targetDoc.editProgressivize = false; activeItem.presProgressivize = false; targetDoc.presProgressivize = false; - // docs.forEach((doc, index) => { - // doc.appearFrame = 0; - // }); targetDoc.currentFrame = 0; targetDoc.lastFrame = 0; + targetDoc.editing = true; } } @@ -1308,236 +1435,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> else doc.displayMovement = true; } - private _isDraggingTL = false; - private _isDraggingTR = false; - private _isDraggingBR = false; - private _isDraggingBL = false; - private _isDragging = false; - // private _drag = ""; - - // onPointerDown = (e: React.PointerEvent): void => { - // e.stopPropagation(); - // e.preventDefault(); - // if (e.button === 0) { - // this._drag = e.currentTarget.id; - // console.log(this._drag); - // } - // document.removeEventListener("pointermove", this.onPointerMove); - // document.addEventListener("pointermove", this.onPointerMove); - // document.removeEventListener("pointerup", this.onPointerUp); - // document.addEventListener("pointerup", this.onPointerUp); - // } - - - //Adds event listener so knows pointer is down and moving - onPointerMid = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - this._isDragging = true; - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - - //Adds event listener so knows pointer is down and moving - onPointerBR = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - this._isDraggingBR = true; - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - - //Adds event listener so knows pointer is down and moving - onPointerBL = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - this._isDraggingBL = true; - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - - //Adds event listener so knows pointer is down and moving - onPointerTR = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - this._isDraggingTR = true; - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - - //Adds event listener so knows pointer is down and moving - onPointerTL = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - this._isDraggingTL = true; - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - - //Removes all event listeners - onPointerUp = (e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - this._isDraggingTL = false; - this._isDraggingTR = false; - this._isDraggingBL = false; - this._isDraggingBR = false; - this._isDragging = false; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - - //Adjusts the value in NodeStore - onPointerMove = (e: PointerEvent): void => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const tagDocView = DocumentManager.Instance.getDocumentView(targetDoc); - e.stopPropagation(); - e.preventDefault(); - const doc = document.getElementById('resizable'); - if (doc && tagDocView) { - - const scale2 = tagDocView.childScaling(); - const scale3 = tagDocView.props.ScreenToLocalTransform().Scale; - const scale = NumCast(targetDoc._viewScale); - console.log("scale: " + NumCast(targetDoc._viewScale)); - let height = doc.offsetHeight; - let width = doc.offsetWidth; - let top = doc.offsetTop; - let left = doc.offsetLeft; - // const newHeightB = height += (e.movementY * NumCast(targetDoc._viewScale)); - // const newHeightT = height -= (e.movementY * NumCast(targetDoc._viewScale)); - // const newWidthR = width += (e.movementX * NumCast(targetDoc._viewScale)); - // const newWidthL = width -= (e.movementX * NumCast(targetDoc._viewScale)); - // const newLeft = left += (e.movementX * NumCast(targetDoc._viewScale)); - // const newTop = top += (e.movementY * NumCast(targetDoc._viewScale)); - // switch (this._drag) { - // case "": break; - // case "resizer-br": - // doc.style.height = newHeightB + 'px'; - // doc.style.width = newWidthR + 'px'; - // break; - // case "resizer-bl": - // doc.style.height = newHeightB + 'px'; - // doc.style.width = newWidthL + 'px'; - // doc.style.left = newLeft + 'px'; - // break; - // case "resizer-tr": - // doc.style.width = newWidthR + 'px'; - // doc.style.height = newHeightT + 'px'; - // doc.style.top = newTop + 'px'; - // case "resizer-tl": - // doc.style.width = newWidthL + 'px'; - // doc.style.height = newHeightT + 'px'; - // doc.style.top = newTop + 'px'; - // doc.style.left = newLeft + 'px'; - // case "resizable": - // doc.style.top = newTop + 'px'; - // doc.style.left = newLeft + 'px'; - // } - //Bottom right - if (this._isDraggingBR) { - const newHeight = height += (e.movementY * scale); - doc.style.height = newHeight + 'px'; - const newWidth = width += (e.movementX * scale); - doc.style.width = newWidth + 'px'; - // Bottom left - } else if (this._isDraggingBL) { - const newHeight = height += (e.movementY * scale); - doc.style.height = newHeight + 'px'; - const newWidth = width -= (e.movementX * scale); - doc.style.width = newWidth + 'px'; - const newLeft = left += (e.movementX * scale); - doc.style.left = newLeft + 'px'; - // Top right - } else if (this._isDraggingTR) { - const newWidth = width += (e.movementX * scale); - doc.style.width = newWidth + 'px'; - const newHeight = height -= (e.movementY * scale); - doc.style.height = newHeight + 'px'; - const newTop = top += (e.movementY * scale); - doc.style.top = newTop + 'px'; - // Top left - } else if (this._isDraggingTL) { - const newWidth = width -= (e.movementX * scale); - doc.style.width = newWidth + 'px'; - const newHeight = height -= (e.movementY * scale); - doc.style.height = newHeight + 'px'; - const newTop = top += (e.movementY * scale); - doc.style.top = newTop + 'px'; - const newLeft = left += (e.movementX * scale); - doc.style.left = newLeft + 'px'; - } else if (this._isDragging) { - const newTop = top += (e.movementY * scale); - doc.style.top = newTop + 'px'; - const newLeft = left += (e.movementX * scale); - doc.style.left = newLeft + 'px'; - } - this.updateList(targetDoc, targetDoc["viewfinder-width-indexed"], width); - this.updateList(targetDoc, targetDoc["viewfinder-height-indexed"], height); - this.updateList(targetDoc, targetDoc["viewfinder-top-indexed"], top); - this.updateList(targetDoc, targetDoc["viewfinder-left-indexed"], left); - } - } - @action checkList = (doc: Doc, list: any): number => { const x: List<number> = list; if (x && x.length >= NumCast(doc.currentFrame) + 1) { return x[NumCast(doc.currentFrame)]; - } else { + } else if (x) { x.length = NumCast(doc.currentFrame) + 1; x[NumCast(doc.currentFrame)] = x[NumCast(doc.currentFrame) - 1]; return x[NumCast(doc.currentFrame)]; - } - - } - - @action - updateList = (doc: Doc, list: any, val: number) => { - const x: List<number> = list; - if (x && x.length >= NumCast(doc.currentFrame) + 1) { - x[NumCast(doc.currentFrame)] = val; - list = x; - } else { - x.length = NumCast(doc.currentFrame) + 1; - x[NumCast(doc.currentFrame)] = val; - list = x; - } - } - - // scale: NumCast(targetDoc._viewScale), - @computed get zoomProgressivizeContainer() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - if (targetDoc) { - const vfLeft: number = this.checkList(targetDoc, targetDoc["viewfinder-left-indexed"]); - const vfWidth: number = this.checkList(targetDoc, targetDoc["viewfinder-width-indexed"]); - const vfTop: number = this.checkList(targetDoc, targetDoc["viewfinder-top-indexed"]); - const vfHeight: number = this.checkList(targetDoc, targetDoc["viewfinder-height-indexed"]); - return ( - <> - {!targetDoc.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerMid} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}> - <div className='resizers'> - <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerTL}></div> - <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerTR}></div> - <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerBL}></div> - <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerBR}></div> - </div> - </div>} - </> - ); - } + } else return 100; } @computed get progressivizeChildDocs() { @@ -1620,8 +1527,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return width; } + @action + toggleProperties = () => { + if (CurrentUserUtils.propertiesWidth > 0) { + CurrentUserUtils.propertiesWidth = 0; + } else { + CurrentUserUtils.propertiesWidth = 250; + } + } + @computed get toolbar() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; + const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel"; return ( <div id="toolbarContainer" className={'presBox-toolbar'} style={{ display: this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }}> <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> @@ -1634,12 +1551,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <FontAwesomeIcon icon={"exchange-alt"} /> </div> </Tooltip> - <Tooltip title={<><div className="dash-tooltip">{this.expandBoolean ? "Minimize all" : "Expand all"}</div></>}> - <div style={{ opacity: this.childDocs.length > 0 ? 1 : 0.3 }} className={`toolbar-button ${this.expandBoolean ? "active" : ""}`} onClick={() => { if (this.childDocs.length > 0) this.toggleExpand(); this.childDocs.forEach((doc, ind) => { if (this.expandBoolean) doc.presExpandInlineButton = true; else doc.presExpandInlineButton = false; }); }}> + <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.expandBoolean ? "Minimize all" : "Expand all"}</div></>}> + <div className={`toolbar-button ${this.rootDoc.expandBoolean ? "active" : ""}`} onClick={this.toggleExpandMode}> <FontAwesomeIcon icon={"eye"} /> </div> </Tooltip> <div className="toolbar-divider" /> + <Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}> + <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> + <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? '#AEDDF8' : 'white' }} /> + </div> + </Tooltip> </div> ); } @@ -1697,18 +1619,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get playButtons() { // Case 1: There are still other frames and should go through all frames before going to next slide - return (<div className="miniPresOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> - <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="miniPres-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> - <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> + return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> + <div className="presPanel-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <div className="presPanel-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <div className="presPanel-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="presPanel-divider"></div> + <div className="presPanel-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames} </div> - <div className="miniPres-divider"></div> - {this.props.PanelWidth() > 250 ? <div className="miniPres-button-text" onClick={() => this.layoutDoc.presStatus = "edit"}>EXIT</div> - : <div className="miniPres-button" onClick={() => this.layoutDoc.presStatus = "edit"}> + <div className="presPanel-divider"></div> + {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={() => this.layoutDoc.presStatus = "edit"}>EXIT</div> + : <div className="presPanel-button" onClick={() => this.layoutDoc.presStatus = "edit"}> <FontAwesomeIcon icon={"times"} /> </div>} </div>); @@ -1720,28 +1642,44 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > - {this.topPanel} - {this.toolbar} - {this.newDocumentToolbarDropdown} - <div className="presBox-listCont"> - {mode !== CollectionViewType.Invalid ? - <CollectionView {...this.props} - ContainingCollectionDoc={this.props.Document} - PanelWidth={this.props.PanelWidth} - PanelHeight={this.panelHeight} - moveDocument={returnFalse} - childOpacity={returnOne} - childLayoutTemplate={this.childLayoutTemplate} - filterAddDocument={this.addDocumentFilter} - removeDocument={returnFalse} - dontRegisterView={true} - focus={this.selectElement} - ScreenToLocalTransform={this.getTransform} /> - : (null) - } + return this.layoutDoc.inOverlay ? + <div className="miniPres" style={{ width: 250, height: 35, background: '#323232', top: 0, zIndex: 3000000 }}> + {<div className="miniPresOverlay"> + <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <div className="miniPres-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text"> + Slide {this.itemIndex + 1} / {this.childDocs.length} + {this.playButtonFrames} + </div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text" onClick={this.updateMinimize}>EXIT</div> + </div>} </div> - </div>; + : + <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > + {this.topPanel} + {this.toolbar} + {this.newDocumentToolbarDropdown} + <div className="presBox-listCont"> + {mode !== CollectionViewType.Invalid ? + <CollectionView {...this.props} + ContainingCollectionDoc={this.props.Document} + PanelWidth={this.props.PanelWidth} + PanelHeight={this.panelHeight} + moveDocument={returnFalse} + childOpacity={returnOne} + childLayoutTemplate={this.childLayoutTemplate} + filterAddDocument={this.addDocumentFilter} + removeDocument={returnFalse} + dontRegisterView={true} + focus={this.selectElement} + ScreenToLocalTransform={this.getTransform} /> + : (null) + } + </div> + </div>; } } Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, data: Doc) { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 3283f568a..1393e7868 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import { Dictionary } from "typescript-collections"; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, Opt, AclAddonly, AclEdit, AclAdmin } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt, AclAddonly, AclEdit, AclAdmin, DataSym } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { HtmlField } from "../../../fields/HtmlField"; @@ -454,9 +454,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />; } else if (field instanceof WebField) { const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href; - view = <iframe className="webBox-iframe" enable-annotation={true} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} />; + view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} + // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page + sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />; } else { - view = <iframe className="webBox-iframe" enable-annotation={true} ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />; + view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} />; } return view; } @@ -535,7 +537,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum @action highlight = (color: string) => { // creates annotation documents for current highlights - const effectiveAcl = GetEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) ? this.makeAnnotationDocument(color) : undefined; annotationDoc && this.addDocument?.(annotationDoc); return annotationDoc ?? undefined; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 20d9a4fe5..2ded33346 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -23,7 +23,7 @@ import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../../fields/Schema"; -import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types"; +import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types"; import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; @@ -377,6 +377,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public highlightSearchTerms = (terms: string[], alt: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); @@ -384,30 +385,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let tr = this._editorView.state.tr; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); - - - const lastSel = Math.min(flattened.length - 1, this._searchIndex); - flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); - if (alt === true) { - if (this._searchIndex > 1) { - this._searchIndex += -2; - } - else if (this._searchIndex === 1) { - this._searchIndex = length - 1; - } - else if (this._searchIndex === 0 && length !== 1) { - this._searchIndex = length - 2; - } - + if (BoolCast(Doc.GetProto(this.dataDoc).resetSearch) === true) { + this._searchIndex = 0; + Doc.GetProto(this.dataDoc).resetSearch = undefined; } else { + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + if (alt === true) { + if (this._searchIndex > 1) { + this._searchIndex += -2; + } + else if (this._searchIndex === 1) { + this._searchIndex = length - 1; + } + else if (this._searchIndex === 0 && length !== 1) { + this._searchIndex = length - 2; + } + } } - const index = this._searchIndex; - Doc.GetProto(this.dataDoc).searchIndex = index; + const lastSel = Math.min(flattened.length - 1, this._searchIndex); + flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); + flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + + console.log(this._searchIndex); } } @@ -647,7 +649,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const options = cm.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !Doc.UserDoc().noviceMode && optionItems.push({ description: this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); + !Doc.UserDoc().noviceMode && optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); optionItems.push({ description: `${!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); @@ -1579,6 +1581,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onScroll={this.onscrolled} onDrop={this.ondrop} > <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget} style={{ + overflow: this.layoutDoc._singleLine ? "hidden" : undefined, padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`, pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined }} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 459632ec8..b683fb25d 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -163,11 +163,14 @@ export default class RichTextMenu extends AntimodeMenu { } update(view: EditorView, lastState: EditorState | undefined) { - this.updateFromDash(view, lastState, this.editorProps); + RichTextMenu.Instance.updateFromDash(view, lastState, this.editorProps); } @action public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { + RichTextMenu.Instance.finalUpdateFromDash(view, lastState, props); + } + public async finalUpdateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { if (!view || !(view as any).TextView?.props.isSelected(true)) { return; } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index cfa9a1844..f9ae78778 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -4,7 +4,7 @@ const pdfjs = require('pdfjs-dist/es5/build/pdf.js'); import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym, AclAddonly, AclEdit, AclAdmin } from "../../../fields/Doc"; +import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym, AclAddonly, AclEdit, AclAdmin, DataSym } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; @@ -337,7 +337,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu nextAnnotation = () => { this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1); this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]); - this.Document.searchIndex = this.Index; } @@ -400,10 +399,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action search = (searchString: string, fwd: boolean, clear: boolean = false) => { if (clear) { - this._pdfViewer.findController.executeCommand('reset', { query: "" }); + this._pdfViewer?.findController.executeCommand('reset', { query: "" }); } else if (!searchString) { fwd ? this.nextAnnotation() : this.prevAnnotation(); - } else if (this._pdfViewer.pageViewsReady) { + } else if (this._pdfViewer?.pageViewsReady) { this._pdfViewer.findController.executeCommand('findagain', { caseSensitive: false, findPrevious: !fwd, @@ -411,7 +410,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu phraseSearch: true, query: searchString }); - this.Document.searchIndex = this.Index; } else if (this._mainCont.current) { const executeFind = () => { @@ -425,7 +423,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu }; this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); - this.Document.searchIndex = this.Index; } } @@ -576,7 +573,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action highlight = (color: string) => { // creates annotation documents for current highlights - const effectiveAcl = GetEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color); annotationDoc && this.addDocument?.(annotationDoc); return annotationDoc as Doc ?? undefined; diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss index 1e776384a..6ee190b82 100644 --- a/src/client/views/presentationview/PresElementBox.scss +++ b/src/client/views/presentationview/PresElementBox.scss @@ -3,6 +3,7 @@ $dark-blue: #5B9FDD; $light-background: #ececec; .presElementBox-item { + cursor: grab; display: grid; grid-template-columns: max-content max-content max-content max-content; background-color: #d5dce2; @@ -161,6 +162,7 @@ $light-background: #ececec; } .presElementBox-closeIcon { + cursor: pointer; position: absolute; border-radius: 100%; z-index: 300; @@ -177,6 +179,7 @@ $light-background: #ececec; } .presElementBox-expand { + cursor: pointer; position: absolute; border-radius: 100%; z-index: 300; @@ -193,6 +196,7 @@ $light-background: #ececec; } .presElementBox-expand-selected { + cursor: pointer; position: absolute; border-radius: 100%; right: 3px; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index a6dbb76ef..a25a8ee33 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -19,6 +19,7 @@ import { PresBox } from "../nodes/PresBox"; import { DocumentType } from "../../documents/DocumentTypes"; import { Tooltip } from "@material-ui/core"; import { DragManager } from "../../util/DragManager"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; export const presSchema = createSchema({ presentationTargetDoc: Doc, @@ -59,111 +60,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc } /** - * The function that is called on click to turn Hiding document till press option on/off. - * It also sets the beginning and end opacitys. - */ - @action - onHideDocumentUntilPressClick = (e: React.MouseEvent) => { - e.stopPropagation(); - this.rootDoc.presHideTillShownButton = !this.rootDoc.presHideTillShownButton; - if (!this.rootDoc.presHideTillShownButton) { - if (this.indexInPres >= this.itemIndex && this.targetDoc) { - this.targetDoc.opacity = 1; - } - } else { - if (this.presStatus !== "edit" && this.indexInPres > this.itemIndex && this.targetDoc) { - this.targetDoc.opacity = 0; - } - } - } - - /** - * The function that is called on click to turn Hiding document after presented option on/off. - * It also makes sure that the option swithches from fade-after to this one, since both - * can't coexist. - */ - @action - onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => { - e.stopPropagation(); - this.rootDoc.presHideAfterButton = !this.rootDoc.presHideAfterButton; - if (!this.rootDoc.presHideAfterButton) { - if (this.indexInPres <= this.itemIndex && this.targetDoc) { - this.targetDoc.opacity = 1; - } - } else { - if (this.rootDoc.presFadeButton) this.rootDoc.presFadeButton = false; - if (this.presStatus !== "edit" && this.indexInPres < this.itemIndex && this.targetDoc) { - this.targetDoc.opacity = 0; - } - } - } - - @action - progressivize = (e: React.MouseEvent) => { - e.stopPropagation(); - this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize; - const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null); - const docs = rootTarget.type === DocumentType.COL ? DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]) : - DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget) + "-annotations"]); - if (this.rootDoc.presProgressivize) { - rootTarget.currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true); - rootTarget.lastFrame = docs.length - 1; - } - } - - /** - * The function that is called on click to turn fading document after presented option on/off. - * It also makes sure that the option swithches from hide-after to this one, since both - * can't coexist. - */ - @action - onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { - e.stopPropagation(); - this.rootDoc.presFadeButton = !this.rootDoc.presFadeButton; - if (!this.rootDoc.presFadeButton) { - if (this.indexInPres <= this.itemIndex && this.targetDoc) { - this.targetDoc.opacity = 1; - } - } else { - this.rootDoc.presHideAfterButton = false; - if (this.presStatus !== "edit" && (this.indexInPres < this.itemIndex) && this.targetDoc) { - this.targetDoc.opacity = 0.5; - } - } - } - - /** - * The function that is called on click to turn navigation option of docs on/off. - */ - @action - onNavigateDocumentClick = (e: React.MouseEvent) => { - e.stopPropagation(); - this.rootDoc.presNavButton = !this.rootDoc.presNavButton; - if (this.rootDoc.presNavButton) { - this.rootDoc.presZoomButton = false; - if (this.itemIndex === this.indexInPres) { - this.props.focus(this.rootDoc); - } - } - } - - /** - * The function that is called on click to turn zoom option of docs on/off. - */ - @action - onZoomDocumentClick = (e: React.MouseEvent) => { - e.stopPropagation(); - - this.rootDoc.presZoomButton = !this.rootDoc.presZoomButton; - if (this.rootDoc.presZoomButton) { - this.rootDoc.presNavButton = false; - if (this.itemIndex === this.indexInPres) { - this.props.focus(this.rootDoc); - } - } - } - /** * Returns a local transformed coordinate array for given coordinates. */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; @@ -233,12 +129,21 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc private _itemRef: React.RefObject<HTMLDivElement> = React.createRef(); private _dragRef: React.RefObject<HTMLDivElement> = React.createRef(); + @action headerDown = (e: React.PointerEvent<HTMLDivElement>) => { - const element = document.elementFromPoint(e.clientX, e.clientY)?.parentElement; + const element = e.target as any; e.stopPropagation(); e.preventDefault(); - if (element) { - if (PresBox.Instance._eleArray.includes(element)) { + if (element && !(e.ctrlKey || e.metaKey)) { + if (PresBox.Instance._eleArray.includes(this._itemRef.current!)) { + setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); + } else { + PresBox.Instance._selectedArray = []; + PresBox.Instance._selectedArray.push(this.rootDoc); + PresBox.Instance._eleArray = []; + PresBox.Instance._eleArray.push(this._itemRef.current!); + PresBox.Instance._dragArray = []; + PresBox.Instance._dragArray.push(this._dragRef.current!); setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); } } @@ -293,8 +198,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc } } + @action + toggleProperties = () => { + if (CurrentUserUtils.propertiesWidth === 0) { + CurrentUserUtils.propertiesWidth = 250; + } + } + render() { - const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree; const className = "presElementBox-item" + (PresBox.Instance._selectedArray.includes(this.rootDoc) ? " presElementBox-active" : ""); const pbi = "presElementBox-interaction"; return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : ( @@ -319,6 +230,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc PresBox.Instance._dragArray.push(this._dragRef.current!); } }} + onDoubleClick={e => { + this.toggleProperties(); + this.props.focus(this.rootDoc); + PresBox.Instance._eleArray = []; + PresBox.Instance._eleArray.push(this._itemRef.current!); + PresBox.Instance._dragArray = []; + PresBox.Instance._dragArray.push(this._dragRef.current!); + }} onPointerDown={this.headerDown} onPointerUp={this.headerUp} > @@ -347,14 +266,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc <div ref={this._highlightTopRef} onPointerOver={this.onPointerTop} onPointerLeave={this.onPointerLeave} className="presElementBox-highlightTop" style={{ zIndex: 299, backgroundColor: "rgba(0,0,0,0)" }} /> <div ref={this._highlightBottomRef} onPointerOver={this.onPointerBottom} onPointerLeave={this.onPointerLeave} className="presElementBox-highlightBottom" style={{ zIndex: 299, backgroundColor: "rgba(0,0,0,0)" }} /> <div className="presElementBox-highlight" style={{ backgroundColor: PresBox.Instance._selectedArray.includes(this.rootDoc) ? "#AEDDF8" : "rgba(0,0,0,0)" }} /> - <div className="presElementBox-buttons" style={{ display: this.rootDoc.presExpandInlineButton ? "grid" : "none" }}> - <button title="Zoom" className={pbi + (this.rootDoc.presZoomButton ? "-selected" : "")} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Navigate" className={pbi + (this.rootDoc.presNavButton ? "-selected" : "")} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Hide Before" className={pbi + (this.rootDoc.presHideTillShownButton ? "-selected" : "")} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={"file"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Hide After" className={pbi + (this.rootDoc.presHideAfterButton ? "-selected" : "")} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Progressivize" className={pbi + (this.rootDoc.presProgressivize ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Effect" className={pbi + (this.rootDoc.presEffect ? "-selected" : "")}>E</button> - </div> {this.renderEmbeddedInline} </div> ); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 25fc8ff0b..770a03cb1 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable, runInAction, reaction, IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; @@ -54,6 +54,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @observable private _visibleElements: JSX.Element[] = []; @observable private _visibleDocuments: Doc[] = []; + static NUM_SEARCH_RESULTS_PER_PAGE = 25; + private _resultsSet = new Map<Doc, number>(); private _resultsRef = React.createRef<HTMLDivElement>(); public inputRef = React.createRef<HTMLInputElement>(); @@ -86,7 +88,19 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } @observable setupButtons = false; + private _disposers: { [name: string]: IReactionDisposer } = {}; + componentDidMount = () => { + this._disposers.filters = reaction(() => Cast(this.props.Document._docFilters, listSpec("string")), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + newFilters => { + if (this.searchFullDB) { + runInAction(() => this._pageStart = 0); + this.submitSearch(); + // newFilters?.forEach(f => { + // console.log(f); + // }) + } + }); if (this.setupButtons === false) { runInAction(() => this.setupButtons = true); @@ -113,6 +127,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } } + componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); + } + @action getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc) @@ -123,15 +141,35 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc onChange(e: React.ChangeEvent<HTMLInputElement>) { this.layoutDoc._searchString = e.target.value; this.newsearchstring = e.target.value; - - if (e.target.value === "") { + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + d._docFilters = new List(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection.props.Document._docFilters = new List(); + this.props.Document.selectedDoc = undefined; + } this._results.forEach(result => { Doc.UnBrushDoc(result[0]); result[0].searchMatch = undefined; }); - this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); if (this.currentSelectedCollection !== undefined) { this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); this.currentSelectedCollection = undefined; @@ -153,6 +191,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.layoutDoc._searchString = this.newsearchstring; + runInAction(() => this._pageStart = 0); if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) { runInAction(() => this.open = true); @@ -185,7 +224,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } } - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB]; + public _allIcons: string[] = [DocumentType.INK, DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB]; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied @observable private _filterOpen: boolean = false; @@ -205,26 +244,100 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc getFinalQuery(query: string): string { //alters the query so it looks in the correct fields - //if this is true, then not all of the field boxes are checked + //if this is true, th`en not all of the field boxes are checked //TODO: data - if (this.fieldFiltersApplied) { - query = this.applyBasicFieldFilters(query); - query = query.replace(/\s+/g, ' ').trim(); + const initialfilters = Cast(this.props.Document._docFilters, listSpec("string"), []); + + const type: string[] = []; + + const filters: string[] = []; + + for (let i = 0; i < initialfilters.length; i = i + 3) { + if (initialfilters[i + 2] !== undefined) { + filters.push(initialfilters[i]); + filters.push(initialfilters[i + 1]); + filters.push(initialfilters[i + 2]); + } } - //alters the query based on if all words or any words are required - //if this._wordstatus is false, all words are required and a + is added before each - if (!this._basicWordStatus) { - query = this.basicRequireWords(query); - query = query.replace(/\s+/g, ' ').trim(); + const finalfilters: { [key: string]: string[] } = {}; + + for (let i = 0; i < filters.length; i = i + 3) { + if (finalfilters[filters[i]] !== undefined) { + finalfilters[filters[i]].push(filters[i + 1]); + } + else { + finalfilters[filters[i]] = [filters[i + 1]]; + } } - // if should be searched in a specific collection - if (this._collectionStatus) { - query = this.addCollectionFilter(query); - query = query.replace(/\s+/g, ' ').trim(); + for (const key in finalfilters) { + const values = finalfilters[key]; + if (values.length === 1) { + const mod = "_t:"; + const newWords: string[] = []; + const oldWords = values[0].split(" "); + oldWords.forEach((word, i) => { + i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""); + }); + query = `(${query}) AND (${newWords.join(" ")})`; + } + else { + for (let i = 0; i < values.length; i++) { + const mod = "_t:"; + const newWords: string[] = []; + const oldWords = values[i].split(" "); + oldWords.forEach((word, i) => { + i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""); + }); + const v = "(" + newWords.join(" ") + ")"; + if (i === 0) { + query = `(${query}) AND (${v}`; + if (values.length === 1) { + query = query + ")"; + } + } + else if (i === values.length - 1) { + query = query + " OR " + v + ")"; + } + else { + query = query + " OR " + v; + } + } + } } + + + // let limit = typepos.length + // typepos.forEach(i => { + // if (i === 0) { + // if (i + 1 === limit) { + // query = query + " && " + filters[i] + "_t:" + filters; + // } + // else if (filters[i] === filters[i + 3]) { + // query = query + " && (" + filters[i] + "_t:" + filters; + // } + // else { + // query = query + " && " + filters[i] + "_t:" + filters; + // } + + // } + // else if (i + 3 > filters.length) { + + // } + // else { + + // } + + // }); + + // query = this.applyBasicFieldFilters(query); + + + + query = query.replace(/-\s+/g, ''); + query = query.replace(/-/g, ""); return query; } @@ -235,7 +348,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @action filterDocsByType(docs: Doc[]) { const finalDocs: Doc[] = []; - const blockedTypes: string[] = ["preselement", "docholder", "collection", "search", "searchitem", "script", "fonticonbox", "button", "label"]; + const blockedTypes: string[] = [DocumentType.PRESELEMENT, DocumentType.DOCHOLDER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; docs.forEach(doc => { const layoutresult = Cast(doc.type, "string"); if (layoutresult && !blockedTypes.includes(layoutresult)) { @@ -299,6 +412,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc searchCollection(query: string) { const selectedCollection: DocumentView = SelectionManager.SelectedDocuments()[0]; query = query.toLowerCase(); + if (selectedCollection !== undefined) { this.currentSelectedCollection = selectedCollection; if (this.filter === true) { @@ -312,6 +426,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc while (docs.length > 0) { newarray = []; docs.forEach((d) => { + d ? console.log(Cast(d.context, Doc)) : null; if (d.data !== undefined) { newarray.push(...DocListCast(d.data)); } @@ -375,15 +490,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc applyBasicFieldFilters(query: string) { let finalQuery = ""; - if (this._titleFieldStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); - } - if (this._authorFieldStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); - } - if (this._deletedDocsStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); - } + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); + if (this._deletedDocsStatus) { finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT); } @@ -394,8 +503,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc let mod = ""; switch (type) { case Keys.AUTHOR: mod = " author_t:"; break; - case Keys.DATA: break; // TODO - case Keys.TITLE: mod = " _title_t:"; break; + case Keys.TITLE: mod = " title_t:"; break; case Keys.TEXT: mod = " text_t:"; break; } @@ -412,10 +520,33 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @action submitSearch = async (reset?: boolean) => { + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + //d._docFilters = new List(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection.props.Document._docFilters = new List(); + this.props.Document.selectedDoc = undefined; + } if (reset) { this.layoutDoc._searchString = ""; } - this.props.Document._docFilters = undefined; + //this.props.Document._docFilters = new List(); this.noresults = ""; this.dataDoc[this.fieldKey] = new List<Doc>([]); @@ -423,9 +554,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this.children = 0; this.buckets = []; this.new_buckets = {}; - const query = StrCast(this.layoutDoc._searchString); + let query = StrCast(this.layoutDoc._searchString); Doc.SetSearchQuery(query); - this.getFinalQuery(query); + this.searchFullDB ? query = this.getFinalQuery(query) : console.log("local"); this._results.forEach(result => { Doc.UnBrushDoc(result[0]); result[0].searchMatch = undefined; @@ -473,22 +604,21 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } private get filterQuery() { - const types = this.filterTypes; - const baseExpr = "NOT baseProto_b:true"; + const types = ["preselement", "docholder", "search", "searchitem", "script", "fonticonbox", "button", "label"]; // this.filterTypes; + const baseExpr = "NOT baseProto_b:true AND NOT system_b:true"; const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true"; const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox"; - // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; + const typeExpr = !types ? "" : ` ${types.map(type => `NOT ({!join from=id to=proto_i}type_t:${type}) AND NOT type_t:${type}`).join(" AND ")}`; // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello - const query = [baseExpr, includeDeleted, includeIcons].join(" AND ").replace(/AND $/, ""); + const query = [baseExpr, includeDeleted, includeIcons, typeExpr].join(" AND ").replace(/AND $/, ""); return query; } getDataStatus() { return this._deletedDocsStatus; } - private NumResults = 25; + private NumResults = 50; private lockPromise?: Promise<void>; getResults = async (query: string) => { - console.log("Get"); if (this.lockPromise) { await this.lockPromise; } @@ -496,6 +626,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => { // happens at the beginning + this.realTotalResults = res.numFound; if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { this._numTotalResults = res.numFound; } @@ -513,28 +644,17 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc const index = this._resultsSet.get(doc); const highlight = highlights[doc[Id]]; const line = lines.get(doc[Id]) || []; - const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; - doc ? console.log(Cast(doc.context, Doc)) : null; - if (this.findCommonElements(hlights)) { - } - else { - const layoutresult = Cast(doc.type, "string"); - if (layoutresult) { - if (this.new_buckets[layoutresult] === undefined) { - this.new_buckets[layoutresult] = 1; - } - else { - this.new_buckets[layoutresult] = this.new_buckets[layoutresult] + 1; - } - } - if (index === undefined) { - this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights, line]); - } else { - this._results[index][1].push(...hlights); - this._results[index][2].push(...line); - } + const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)).filter(k => k) : []; + // if (this.findCommonElements(hlights)) { + // } + if (index === undefined) { + this._resultsSet.set(doc, this._results.length); + this._results.push([doc, hlights, line]); + } else { + this._results[index][1].push(...hlights); + this._results[index][2].push(...line); } + }); }); @@ -586,7 +706,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc y += 300; } } - return Docs.Create.SearchDocument({ _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString), searchQuery: StrCast(this.layoutDoc._searchString) }); + return Docs.Create.SchemaDocument(Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []), DocListCast(this.dataDoc[this.fieldKey]), { _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString) }); } @action.bound @@ -597,6 +717,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._searchbarOpen = true; } + realTotalResults: number = 0; + @action.bound closeSearch = () => { //this.closeResults(); @@ -615,26 +737,21 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._curRequest = undefined; } + @observable _pageStart: number = 0; + @observable _pageCount: number = SearchBox.NUM_SEARCH_RESULTS_PER_PAGE; + @observable children: number = 0; @action resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { if (!this._resultsRef.current) return; - this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); - - const scrollY = e ? e.currentTarget.scrollTop : this._resultsRef.current ? this._resultsRef.current.scrollTop : 0; - const itemHght = 53; - const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); - //const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght))); - const endIndex = 30; - //this._endIndex = endIndex === -1 ? 12 : endIndex; this._endIndex = 30; - const headers = new Set<string>(["title", "author", "*lastModified", "text"]); - if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { - if (this.noresults === "") { - this.noresults = "No search results :("; - } - return; - } + const headers = new Set<string>(["title", "author", "text", "type", "data", "*lastModified", "context"]); + // if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { + // if (this.noresults === "") { + // this.noresults = "No search results :("; + // } + // return; + // } if (this._numTotalResults <= this._maxSearchIndex) { this._numTotalResults = this._results.length; @@ -642,7 +759,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc // only hit right at the beginning // visibleElements is all of the elements (even the ones you can't see) - else if (this._visibleElements.length !== this._numTotalResults) { + if (this._visibleElements.length !== this._numTotalResults) { // undefined until a searchitem is put in there this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults); this._visibleDocuments = Array<Doc>(this._numTotalResults === -1 ? 0 : this._numTotalResults); @@ -651,62 +768,51 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc this._isSorted = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); } - for (let i = 0; i < this._numTotalResults; i++) { + let max = this.NumResults; + max > this._results.length ? max = this._results.length : console.log(""); + for (let i = this._pageStart; i < max; i++) { //if the index is out of the window then put a placeholder in //should ones that have already been found get set to placeholders? - if (i < startIndex || i > endIndex) { - if (this._isSearch[i] !== "placeholder") { - this._isSearch[i] = "placeholder"; - this._isSorted[i] = "placeholder"; - this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; - } - } - else { - if (this._isSearch[i] !== "search") { - let result: [Doc, string[], string[]] | undefined = undefined; - if (i >= this._results.length) { - this.getResults(StrCast(this.layoutDoc._searchString)); - if (i < this._results.length) result = this._results[i]; - if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - const lines = new List<string>(result[2]); - result[0].lines = lines; - result[0].highlighting = highlights.join(", "); - highlights.forEach((item) => headers.add(item)); - this._visibleDocuments[i] = result[0]; - this._isSearch[i] = "search"; - Doc.BrushDoc(result[0]); - result[0].searchMatch = true; - Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); - this.children++; - } - } - else { - result = this._results[i]; - if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - const lines = new List<string>(result[2]); - highlights.forEach((item) => headers.add(item)); - result[0].lines = lines; - result[0].highlighting = highlights.join(", "); - result[0].searchMatch = true; - if (i < this._visibleDocuments.length) { - this._visibleDocuments[i] = result[0]; - this._isSearch[i] = "search"; - Doc.BrushDoc(result[0]); - Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); - this.children++; - } - } + + if (this._isSearch[i] !== "search") { + let result: [Doc, string[], string[]] | undefined = undefined; + + result = this._results[i]; + if (result) { + const highlights = Array.from([...Array.from(new Set(result[1]).values())]); + const lines = new List<string>(result[2]); + highlights.forEach((item) => headers.add(item)); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + result[0].searchMatch = true; + if (i < this._visibleDocuments.length) { + this._visibleDocuments[i] = result[0]; + this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; } + } + } } - const schemaheaders: SchemaHeaderField[] = []; this.headerscale = headers.size; - headers.forEach((item) => schemaheaders.push(new SchemaHeaderField(item, "#f1efeb"))); - this.headercount = schemaheaders.length; - this.props.Document._schemaHeaders = new List<SchemaHeaderField>(schemaheaders); + if (Cast(this.props.Document._docFilters, listSpec("string"), []).length === 0) { + const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []); + if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") { + const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i); + headers.forEach(header => { + if (oldSchemaHeaders.includes(header) === false) { + newSchemaHeaders.push(new SchemaHeaderField(header, "#f1efeb")); + } + }); + this.headercount = newSchemaHeaders.length; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders); + } else if (this.props.Document._schemaHeaders === undefined) { + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]); + } + } if (this._maxSearchIndex >= this._numTotalResults) { this._visibleElements.length = this._results.length; this._visibleDocuments.length = this._results.length; @@ -733,6 +839,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); } + @computed get viewspec() { return Cast(this.props.Document._docFilters, listSpec("string"), []); } + getTransform = () => { return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight } @@ -749,56 +857,87 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc @observable filter = false; - //Make id layour document + @action newpage() { + this._pageStart += SearchBox.NUM_SEARCH_RESULTS_PER_PAGE; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.resultsScrolled(); + } render() { this.props.Document._chromeStatus === "disabled"; this.props.Document._searchDoc = true; const cols = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length; let length = 0; - cols > 5 ? length = 1076 : length = cols * 205 + 51; + length = cols * 205 + 51; let height = 0; const rows = this.children; - rows > 8 ? height = 31 + 31 * 8 : height = 31 * rows + 31; + height = 31 + 31 * 6; return ( <div style={{ pointerEvents: "all" }} className="searchBox-container"> <div className="searchBox-bar"> <div style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div> <div style={{ display: "flex", alignItems: "center" }}> - <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" - style={{ color: "black", padding: 1, left: 35, position: "relative" }} /> - + <Tooltip title={<div className="dash-tooltip" >drag search results as collection</div>} ><div> + <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" + style={{ cursor: "hand", color: "black", padding: 1, left: 35, position: "relative" }} /> + </div></Tooltip> + <div style={{ + position: "relative", + left: 245, + zIndex: 9000, + color: "grey", + background: "white", + }}> {`${this._results.length}` + " of " + `${this.realTotalResults}`}</div> <div style={{ cursor: "default", left: 250, position: "relative", }}> <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} ><div> <FontAwesomeIcon icon={"filter"} size="lg" - style={{ padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }} + style={{ cursor: "hand", padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }} onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined); }} onClick={action(() => { - const dofilter = (currentSelectedCollection: DocumentView) => { - let docs = DocListCast(currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(currentSelectedCollection.dataDoc)]); + ///DONT Change without emailing andy r first. + this.filter = !this.filter && !this.searchFullDB; + if (this.filter === true && this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter); + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); while (docs.length > 0) { - const newarray: Doc[] = []; - docs.filter(d => d.data !== undefined).forEach((d) => { - d._searchDocs = new List<Doc>(this.docsforfilter); - newarray.push(...DocListCast(d.data)); + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(this.docsforfilter); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } }); docs = newarray; } - }; - this.filter = !this.filter && !this.searchFullDB; - if (this.filter === true && this.currentSelectedCollection !== undefined) { - this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter); - - dofilter(this.currentSelectedCollection); - this.currentSelectedCollection.props.Document._docFilters = new List<string>(Cast(this.props.Document._docFilters, listSpec("string"), [])); + this.currentSelectedCollection.props.Document._docFilters = new List<string>(this.viewspec); this.props.Document.selectedDoc = this.currentSelectedCollection.props.Document; } else if (this.filter === false && this.currentSelectedCollection !== undefined) { - - dofilter(this.currentSelectedCollection); + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + d._docFilters = new List(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); - this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._docFilters = new List(); this.props.Document.selectedDoc = undefined; } } @@ -832,6 +971,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc docs.forEach((d) => { if (d.data !== undefined) { d._searchDocs = new List<Doc>(); + d._docFilters = new List(); const newdocs = DocListCast(d.data); newdocs.forEach((newdoc) => { newarray.push(newdoc); @@ -840,7 +980,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc }); docs = newarray; } - this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._docFilters = new List(); this.currentSelectedCollection.props.Document._searchDocs = undefined; this.currentSelectedCollection = undefined; } @@ -866,6 +1006,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc docs.forEach((d) => { if (d.data !== undefined) { d._searchDocs = new List<Doc>(); + d._docFilters = new List(); const newdocs = DocListCast(d.data); newdocs.forEach((newdoc) => { newarray.push(newdoc); @@ -874,7 +1015,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc }); docs = newarray; } - this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._docFilters = new List(); this.currentSelectedCollection.props.Document._searchDocs = undefined; this.currentSelectedCollection = undefined; } @@ -900,7 +1041,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc removeDocument={returnFalse} PanelHeight={this.open === true ? () => height : () => 0} PanelWidth={this.open === true ? () => length : () => 0} - overflow={cols > 5 || rows > 8 ? true : false} + overflow={length > window.innerWidth || rows > 6 ? true : false} focus={this.selectElement} ScreenToLocalTransform={Transform.Identity} /> @@ -920,4 +1061,4 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc </div > ); } -} +}
\ No newline at end of file diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index b535fea5a..ea53bc9a2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -806,7 +806,8 @@ export namespace Doc { target[targetKey] = new PrefetchProxy(templateDoc); } else { titleTarget && (Doc.GetProto(target).title = titleTarget); - Doc.GetProto(target)[targetKey] = new PrefetchProxy(templateDoc); + const setDoc = [AclAdmin, AclEdit].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target; + setDoc[targetKey] = new PrefetchProxy(templateDoc); } } return target; @@ -1044,6 +1045,7 @@ export namespace Doc { if (docFilters[i] === key && (docFilters[i + 1] === value || modifiers === "match")) { if (docFilters[i + 2] === modifiers && modifiers && docFilters[i + 1] === value) return; docFilters.splice(i, 3); + container._docFilters = new List<string>(docFilters); break; } } diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 07c90f5a2..22ae454f8 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -114,7 +114,7 @@ export class SchemaHeaderField extends ObjectField { } [ToScriptString]() { - return `invalid`; + return `header(${this.heading},${this.type}})`; } [ToString]() { return `SchemaHeaderField`; diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 4cc3a7cc7..9391f56ac 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -56,6 +56,9 @@ async function deserializeScript(script: ScriptField) { if (script.script.originalScript === 'self.userDoc.noviceMode') { return (script as any).script = (ScriptField.NoviceMode ?? (ScriptField.NoviceMode = ComputedField.MakeFunction('self.userDoc.noviceMode')))?.script; } + if (script.script.originalScript === `selectMainMenu(self)`) { + return (script as any).script = (ScriptField.SelectMenu ?? (ScriptField.SelectMenu = ComputedField.MakeFunction('selectMainMenu(self)')))?.script; + } const captures: ProxyField<Doc> = (script as any).captures; if (captures) { const doc = (await captures.value())!; @@ -89,6 +92,7 @@ export class ScriptField extends ObjectField { public static DeiconifyView: Opt<ScriptField>; public static ConvertToButtons: Opt<ScriptField>; public static NoviceMode: Opt<ScriptField>; + public static SelectMenu: Opt<ScriptField>; constructor(script: CompiledScript, setterscript?: CompiledScript) { super(); diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index ada13226e..848a648e1 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -102,7 +102,7 @@ export const documentSchema = createSchema({ _lockedTransform: "boolean",// whether a freeformview can pan/zoom // drag drop properties - stayInCollection: "boolean",// whether document can be dropped into a different collection + _stayInCollection: "boolean",// whether document can be dropped into a different collection dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move") targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move' diff --git a/src/fields/util.ts b/src/fields/util.ts index 4c71572db..3d832636f 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -161,8 +161,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number) if (target[AclSym] && Object.keys(target[AclSym]).length) { // if the current user is the author of the document / the current user is a member of the admin group - // but not if the doc in question is an alias - the current user will be the author of their alias rather than the original author - if ((Doc.CurrentUserEmail === (target.__fields?.author || target.author) && !(target.aliasOf || target.__fields?.aliasOf)) || currentUserGroups.includes("admin")) return AclAdmin; + if (Doc.CurrentUserEmail === (target.__fields?.author || target.author) || currentUserGroups.includes("admin")) return AclAdmin; // if the ACL is being overriden or the property being modified is one of the playground fields (which can be freely modified) if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; @@ -218,9 +217,10 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc changed = true; // maps over the aliases of the document - if (target.aliases) { - DocListCast(target.aliases).map(alias => { - distributeAcls(key, acl, alias, inheritingFromCollection); + const aliases = DocListCast(target.aliases); + if (aliases.length) { + aliases.map(alias => { + alias !== target && distributeAcls(key, acl, alias, inheritingFromCollection); }); } diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index 7251e07a1..a5aaab63e 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -62,7 +62,7 @@ export class SearchManager extends ApiManager { subscription: "/dashsearch", secureHandler: async ({ req, res }) => { const solrQuery: any = {}; - ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); + ["q", "fq", "start", "rows", "sort", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; @@ -136,6 +136,9 @@ export namespace SolrManager { const term = ToSearchTerm(value); if (term !== undefined) { const { suffix, value } = term; + if (key.endsWith('lastModified')) { + update["lastModified" + suffix] = value; + } update[key + suffix] = value; dynfield = true; } @@ -176,7 +179,8 @@ export namespace SolrManager { "audio": ["_t", "url"], "web": ["_t", "url"], "date": ["_d", value => new Date(value.date).toISOString()], - // "proxy": ["_i", "fieldId"], + "proxy": ["_i", "fieldId"], + "prefetch_proxy": ["_i", "fieldId"], "list": ["_l", list => { const results = []; for (const value of list.fields) { diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index bd8fe97eb..e088cd2c4 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -279,7 +279,7 @@ function delay(ms: number) { * * On failure, returns undefined. */ -async function captureYoutubeScreenshot(targetUrl: string){ +async function captureYoutubeScreenshot(targetUrl: string) { // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); // const page = await browser.newPage(); // // await page.setViewport({ width: 1920, height: 1080 }); diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 63cfa41f0..ec351c283 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -39,7 +39,6 @@ export namespace WebSocket { io = sio().listen(resolvedPorts.socket); } logPort("websocket", resolvedPorts.socket); - console.log(); io.on("connection", function (socket: Socket) { _socket = socket; @@ -204,7 +203,7 @@ export namespace WebSocket { Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); if (newValue.type === Types.Text) { // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR - Search.updateDocument({ id: newValue.id, data: (newValue as any).data }); + Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } }); } } @@ -230,8 +229,7 @@ export namespace WebSocket { "script": ["_t", value => value.script.originalScript], "RichTextField": ["_t", value => value.Text], "date": ["_d", value => new Date(value.date).toISOString()], - // "proxy": ["_i", "fieldId"], - // "proxy": ["", "fieldId"], + "proxy": ["_i", "fieldId"], "list": ["_l", list => { const results = []; for (const value of list.fields) { |