import { computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { ComputedField, ScriptField } from "../../fields/ScriptField";
import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { SharingPermissions } from "../../fields/util";
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
import { DocumentType } from "../documents/DocumentTypes";
import { Networking } from "../Network";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
import { TreeView } from "../views/collections/TreeView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
import { LabelBox } from "../views/nodes/LabelBox";
import { OverlayView } from "../views/OverlayView";
import { DocumentManager } from "./DocumentManager";
import { DragManager } from "./DragManager";
import { makeTemplate } from "./DropConverter";
import { HistoryUtil } from "./History";
import { LinkManager } from "./LinkManager";
import { Scripting } from "./Scripting";
import { SearchUtil } from "./SearchUtil";
import { SelectionManager } from "./SelectionManager";
import { ColorScheme } from "./SettingsManager";
import { SharingManager } from "./SharingManager";
import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
interface Button {
title?: string;
toolTip?: string;
icon?: string;
btnType?: ButtonType;
click?: string;
numBtnType?: NumButtonType;
numBtnMin?: number;
numBtnMax?: number;
switchToggle?: boolean;
script?: string;
checkResult?: string;
width?: number;
list?: string[];
ignoreClick?: boolean;
buttonText?: string;
}
export let resolvedPorts: { server: number, socket: number };
const headerViewVersion = "0.1";
export class CurrentUserUtils {
private static curr_id: string;
//TODO tfs: these should be temporary...
private static mainDocId: string | undefined;
public static searchBtn: Doc;
public static get id() { return this.curr_id; }
public static get MainDocId() { return this.mainDocId; }
public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
@computed public static get UserDocument() { return Doc.UserDoc(); }
@observable public static GuestTarget: Doc | undefined;
@observable public static GuestDashboard: Doc | undefined;
@observable public static GuestMobile: Doc | undefined;
@observable public static propertiesWidth: number = 0;
@observable public static searchPanelWidth: number = 0;
// sets up the default User Templates - slideView, headerView
static setupUserTemplateButtons(doc: Doc) {
// Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location)
if (doc["template-mobile-button"] === undefined) {
const queryTemplate = this.mobileButton({
title: "NEW MOBILE BUTTON",
onClick: undefined,
},
[this.createToolButton({
ignoreClick: true,
icon: "mobile",
btnType: ButtonType.ToolButton,
backgroundColor: "transparent"
}),
this.mobileTextContainer({},
[this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
doc["template-mobile-button"] = CurrentUserUtils.createToolButton({
onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton,
});
}
if (doc["template-button-slides"] === undefined) {
const slideTemplate = Docs.Create.MultirowDocument(
[
Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) })
],
{ _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }
);
slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
doc["template-button-slides"] = CurrentUserUtils.createToolButton({
onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card",
btnType: ButtonType.ToolButton
});
}
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", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
linkTemplate.system = true;
Doc.GetProto(linkTemplate).layout =
"
" +
" " +
" " +
"
";
(linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView");
const rtf2 = {
doc: {
type: "doc", content: [
{
type: "paragraph",
content: [{
type: "dashField",
attrs: {
fieldKey: "src",
hideKey: false
}
}]
},
{ type: "paragraph" },
{
type: "paragraph",
content: [{
type: "dashField",
attrs: {
fieldKey: "dst",
hideKey: false
}
}]
}]
},
selection: { type: "text", anchor: 1, head: 1 },
storedMarks: []
};
linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
doc["template-button-link"] = CurrentUserUtils.createToolButton({
onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true,
btnType: ButtonType.ToolButton
});
}
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, 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: [{
type: "paragraph",
content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }]
}]
},
selection: { type: "text", anchor: 1, head: 1 },
storedMarks: []
};
Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS");
Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'");
// Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'");
// 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, system: true });
box.isTemplateDoc = makeTemplate(box, true, "switch");
doc["template-button-switch"] = CurrentUserUtils.createToolButton({
onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true,
btnType: ButtonType.ToolButton
});
}
if (doc["template-button-detail"] === undefined) {
const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create;
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", system: true
});
const details = TextDocument("", { title: "details", _height: 200, _autoHeight: true, system: true });
const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 75, _autoHeight: true, system: true });
const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 150, _autoHeight: true, system: true });
const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"];
const detailedTemplate = {
doc: {
type: "doc", content: buxtonFieldKeys.map(fieldKey => ({
type: "paragraph",
content: [{ type: "dashField", attrs: { fieldKey } }]
}))
},
selection: { type: "text", anchor: 1, head: 1 },
storedMarks: []
};
details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
const shared = { _autoHeight: true, _xMargin: 0 };
const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" };
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([
new SchemaHeaderField("[A Short Description]", "dimgray", undefined, undefined, undefined, false),
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, _chromeHidden: true, system: true });
detailView.isTemplateDoc = makeTemplate(detailView);
details.title = "Details";
short.title = "A Short Description";
long.title = "Long Description";
doc["template-button-detail"] = CurrentUserUtils.createToolButton({
onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
dragFactory: new PrefetchProxy(detailView) as any as Doc,
title: "detailView",
icon: "window-maximize",
system: true,
btnType: ButtonType.ToolButton,
});
}
const requiredTypes = [
doc["template-button-slides"] as Doc,
doc["template-mobile-button"] as Doc,
doc["template-button-detail"] as Doc,
doc["template-button-link"] as Doc,
//doc["template-button-switch"] as Doc]
];
if (doc["template-buttons"] === undefined) {
doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
hidden: ComputedField.MakeFunction("IsNoviceMode()") as any,
_stayInCollection: true, _hideContextMenu: true, _forceActive: true,
_autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
}));
} else {
const curButnTypes = Cast(doc["template-buttons"], Doc, null);
DocListCastAsync(curButnTypes.data).then(async curBtns => {
curBtns && await Promise.all(curBtns);
requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
});
}
return doc["template-buttons"] as Doc;
}
// setup the different note type skins
static setupNoteTemplates(doc: Doc) {
if (doc["template-note-Note"] === undefined) {
const noteView = Docs.Create.TextDocument("", {
title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, icon: "sticky-note",
_fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
});
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", backgroundColor: "pink", system: true, icon: "lightbulb",
_fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
});
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", backgroundColor: "lightblue", system: true, icon: "book-open",
_fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
});
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", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
// layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true,
// _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
// });
// noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo");
// doc["template-note-Todo"] = new PrefetchProxy(noteView);
// }
// const taskStatusValues = [
// { 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", 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, 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, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc];
DocListCastAsync(curNoteTypes.data).then(async curNotes => {
curNotes && await Promise.all(curNotes);
requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype));
});
}
return doc["template-notes"] as Doc;
}
// creates Note templates, and initial "user" templates
static setupDocTemplates(doc: Doc) {
const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc);
const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc);
if (doc.templateDocs === undefined) {
doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], {
title: "template layouts", _xPadding: 0, system: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
}));
}
}
// setup templates for different document types when they are iconified from Document Decorations
static setupDefaultIconTemplates(doc: Doc) {
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)"), system: true
});
// Docs.Create.TextDocument("", {
// title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
// });
// Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
iconView.isTemplateDoc = makeTemplate(iconView);
doc["template-icon-view"] = new PrefetchProxy(iconView);
}
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)"), system: true
});
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
}
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)"), 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)"), 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)"), 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, 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,
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
curIcons && await Promise.all(curIcons);
requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
});
}
return doc["template-icons"] as Doc;
}
static creatorBtnDescriptors(doc: Doc): {
title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean,
click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc
}[] {
if (doc.emptyPresentation === undefined) {
doc.emptyPresentation = Docs.Create.PresDocument(new List(),
{ title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List(["system"]) });
((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyCollection === undefined) {
doc.emptyCollection = Docs.Create.FreeformDocument([],
{ _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List(["system"]) });
((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyPane === undefined) {
doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _backgroundGridShow: true, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List(["system"]) });
((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptySlide === undefined) {
const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", _autoHeight: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) });
Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text');
FormattedTextBox.SelectOnLoad = textDoc[Id];
doc.emptySlide = textDoc;
}
if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) {
const json = {
doc: {
type: "doc",
content: [
{
type: "paragraph", attrs: {}, content: [{
type: "dashField",
attrs: { fieldKey: "author", docid: "", hideKey: false },
marks: [{ type: "strong" }]
}, {
type: "dashField",
attrs: { fieldKey: "creationDate", docid: "", hideKey: false },
marks: [{ type: "strong" }]
}]
}]
},
selection: { type: "text", anchor: 1, head: 1 },
storedMarks: []
};
const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), {
title: "text", version: headerViewVersion, _height: 70, _headerPointerEvents: "all",
_headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true,
cloneFieldFilter: new List(["system"])
}, "header");
const headerBtnHgt = 10;
headerTemplate[DataSym].layout =
"" +
` ` +
" " +
` Metadata` +
"";
// "" +
// " " +
// " " +
// "
";
(headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
doc.emptyHeader = headerTemplate;
((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyComparison === undefined) {
doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "comparison box", _width: 300, _height: 300, system: true, cloneFieldFilter: new List(["system"]) });
}
if (doc.emptyScript === undefined) {
doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List(["system"]) });
((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyScreenshot === undefined) {
doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) });
}
if (doc.emptyWall === undefined) {
doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) });
(doc.emptyWall as Doc).videoWall = true;
}
if (doc.emptyAudio === undefined) {
doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) });
((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyNote === undefined) {
doc.emptyNote = Docs.Create.TextDocument("", {
_width: 200, title: "text note", _autoHeight: true, system: true,
_fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
cloneFieldFilter: new List(["system"])
});
((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0;
}
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", system: true });
}
if (doc.emptyButton === undefined) {
doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List(["system"]) });
((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyWebpage === undefined) {
doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List(["system"]) });
}
if (doc.activeMobileMenu === undefined) {
this.setupActiveMobileMenu(doc);
}
return [
{ toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, },
{ toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', 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(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true },
{ toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc },
{ toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', 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(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', 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(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc },
{ toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc },
{ toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', 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(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc },
// { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
{ toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript 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 },
{ toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader 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" })' },
];
}
// setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
static async setupCreatorButtons(doc: Doc) {
let alreadyCreatedButtons: string[] = [];
const dragCreatorSet = await Cast(doc.myItemCreators, Doc, null);
if (dragCreatorSet) {
const dragCreators = await Cast(dragCreatorSet.data, listSpec(Doc));
if (dragCreators) {
const dragDocs = await Promise.all(dragCreators);
alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title));
}
}
const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({
_nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35,
icon,
title,
toolTip,
btnType: ButtonType.ToolButton,
ignoreClick,
_dropAction: "alias",
onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
onClick: click ? ScriptField.MakeScript(click) : undefined,
backgroundColor: backgroundColor ? backgroundColor : Colors.DARK_GRAY,
color: Colors.WHITE,
_hideContextMenu: true,
_removeDropProperties: new List(["_stayInCollection"]),
_stayInCollection: true,
dragFactory,
clickFactory,
hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
system: true,
}));
if (dragCreatorSet === undefined) {
doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
_autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
}));
} else {
creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb));
}
return doc.myItemCreators as Doc;
}
static async menuBtnDescriptions(doc: Doc) {
return [
{ title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' },
{ title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' },
{ title: "Files", target: Cast(doc.myFilesystem, Doc, null), icon: "folder-open", click: 'selectMainMenu(self)' },
{ title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
{ title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
{ title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' },
{ title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
{ title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
{ title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" },
];
}
static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (doc.menuStack === undefined) {
await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing
const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments, hidden }) =>
Docs.Create.FontIconDocument({
icon,
btnType: ButtonType.MenuButton,
_stayInCollection: true,
_hideContextMenu: true,
_chromeHidden: true,
system: true,
dontUndo: true,
title,
target,
hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
_dropAction: "alias",
_removeDropProperties: new List(["dropAction", "_stayInCollection"]),
_width: 60,
_height: 60,
watchedDocuments,
onClick: ScriptField.MakeScript(click, { scriptContext: "any" })
})
);
menuBtns.forEach(menuBtn => {
if (menuBtn.title === "Search") {
this.searchBtn = menuBtn;
}
});
menuBtns.forEach(menuBtn => {
if (menuBtn.title === "Search") {
doc.searchBtn = menuBtn;
}
});
doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
title: "menuItemPanel",
childDropAction: "alias",
_chromeHidden: true,
backgroundColor: Colors.DARK_GRAY,
boxShadow: "rgba(0,0,0,0)",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
ignoreClick: true,
_gridGap: 0,
_yMargin: 0,
_yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
}));
}
// this resets all sidebar buttons to being deactivated
PromiseValue(Cast(doc.menuStack, Doc)).then(stack => {
stack && PromiseValue(stack.data).then(btns => {
DocListCastAsync(btns).then(bts => bts?.forEach(btn => {
btn.dontUndo = true;
btn.system = true;
if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files
btn.target = Doc.UserDoc().myFilesystem;
btn.title = "My Files";
}
}));
});
});
return doc.menuStack as Doc;
}
// Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
static setupActiveMobileMenu(doc: Doc) {
if (doc.activeMobileMenu === undefined) {
doc.activeMobileMenu = this.setupMobileMenu();
}
return doc.activeMobileMenu as Doc;
}
// Sets up mobileMenu stacking document
static setupMobileMenu() {
const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), {
_width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,
}));
return menu;
}
// SEts up mobile buttons for inside mobile menu
static setupMobileButtons(doc?: Doc, buttons?: string[]) {
const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
{ title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " },
{ title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
{ title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
{ title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
{ title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
{ title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
];
// returns a list of mobile buttons
return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
this.mobileButton({
title: data.title,
_lockedPosition: true,
onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
backgroundColor: data.backgroundColor, system: true
},
[this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
);
}
// sets up the main document for the mobile button
static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
...opts,
_removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
borderRounding: "5px", boxShadow: "0 0", 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,
_removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", 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,
title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, 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,
title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true
}) as any as Doc
static setupThumbButtons(doc: Doc) {
const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
{ title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" },
{ title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" },
{ 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" },
{ title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" },
{ title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" },
];
return docProtoData.map(data => Docs.Create.FontIconDocument({
_nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon,
_dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick,
onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
clipboard: data.clipboard,
onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
backgroundColor: data.backgroundColor,
_removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, system: true
}));
}
static setupThumbDoc(userDoc: Doc) {
if (!userDoc.thumbDoc) {
const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
_width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons",
_autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true
});
thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
_width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true
});
userDoc.thumbDoc = thumbDoc;
}
return Cast(userDoc.thumbDoc, Doc);
}
static setupMobileInkingDoc(userDoc: Doc) {
return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true });
}
static setupMobileUploadDoc(userDoc: Doc) {
// const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
title: "Upload Images From the Web", _lockedPosition: true, system: true
});
const uploadDoc = Docs.Create.StackingDocument([], {
title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeHidden: true,
});
return Docs.Create.StackingDocument([webDoc, uploadDoc], {
_width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeHidden: true,
});
}
static setupLibrary(userDoc: Doc) {
return CurrentUserUtils.setupDashboards(userDoc);
}
// setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker.
// when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
static async setupToolsBtnPanel(doc: Doc) {
// setup a masonry view of all he creators
const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc);
const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
doc["tabs-button-tools"] = undefined;
if (doc.myCreators === undefined) {
doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true,
_width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true,
}));
}
// setup a color picker
if (doc.myColorPicker === undefined) {
const color = Docs.Create.ColorDocument({
title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true
});
doc.myColorPicker = new PrefetchProxy(color);
}
if (doc.myTools === undefined) {
const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], {
title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0",
})) as any as Doc;
doc.myTools = toolsStack;
}
}
static async setupDashboards(doc: Doc) {
// setup dashboards library item
await doc.myDashboards;
if (doc.myDashboards === undefined) {
const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
const newDashboardButton: Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], {
title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
}));
const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()');
// (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, shareDashboard!, removeDashboard!]);
// (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
// (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "user-friends", "times"]);
(doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
(doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
(doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "chalkboard", "tv", "camera", "users", "times"]);
(doc.myDashboards as any as Doc).childContextMenuFilters = new List([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]);
}
return doc.myDashboards as any as Doc;
}
static async setupPresentations(doc: Doc) {
await doc.myTrails;
if (doc.myTrails === undefined) {
const newTrail = ScriptField.MakeScript(`createNewPresentation()`);
const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
title: "My Trails", _showTitle: "title", _height: 100,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
explainer: "All of the trails that you have created will appear here."
}));
(doc.myTrails as any as Doc).contextMenuScripts = new List([newTrail!]);
(doc.myTrails as any as Doc).contextMenuLabels = new List(["Create New Trail"]);
(doc.myTrails as any as Doc).childContextMenuIcons = new List(["plus"]);
}
return doc.myTrails as any as Doc;
}
static async setupFilesystem(doc: Doc) {
await doc.myFilesystem;
if (doc.myFilesystem === undefined) {
doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
// doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true });
const newFolder = ScriptField.MakeFunction(`makeTopLevelFolder()`, { scriptContext: "any" })!;
const newFolderButton: Doc = Docs.Create.FontIconDocument({
onClick: newFolder, _forceActive: true, toolTip: "Create new folder",
_stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30,
buttonText: "New folder", icon: "folder-plus", system: true
});
doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], {
title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
}));
(doc.myFilesystem as any as Doc).contextMenuScripts = new List([newFolder]);
(doc.myFilesystem as any as Doc).contextMenuLabels = new List(["Create new folder"]);
(doc.myFilesystem as any as Doc).childContextMenuIcons = new List(["plus"]);
}
return doc.myFilesystem as any as Doc;
}
static setupRecentlyClosedDocs(doc: Doc) {
if (doc.myRecentlyClosedDocs === undefined) {
const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
}));
(doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List([clearAll!]);
(doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List(["Empty recently closed"]);
(doc.myRecentlyClosedDocs as any as Doc).contextMenuIcons = new List(["trash"]);
}
}
static setupFilterDocs(doc: Doc) {
// setup Filter item
if (doc.currentFilter === undefined) {
doc.currentFilter = Docs.Create.FilterDocument({
title: "Unnamed Filter", _height: 150,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true
});
const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
(doc.currentFilter as Doc).contextMenuScripts = new List([clearAll!]);
(doc.currentFilter as Doc).contextMenuLabels = new List(["Clear All"]);
(doc.currentFilter as Doc).filterBoolean = "AND";
}
}
static setupUserDoc(doc: Doc) {
if (doc.myUserDoc === undefined) {
doc.treeViewOpen = true;
doc.treeViewExpandedView = "fields";
doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], {
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
})) as any as Doc;
}
}
static setupSidebarContainer(doc: Doc) {
if (doc.sidebar === undefined) {
const sidebarContainer = new Doc();
sidebarContainer.system = true;
doc.sidebar = new PrefetchProxy(sidebarContainer);
}
return doc.sidebar as Doc;
}
// setup the list of sidebar mode buttons which determine what is displayed in the sidebar
static async setupSidebarButtons(doc: Doc) {
CurrentUserUtils.setupSidebarContainer(doc);
await CurrentUserUtils.setupToolsBtnPanel(doc);
CurrentUserUtils.setupImportSidebar(doc);
CurrentUserUtils.setupDashboards(doc);
CurrentUserUtils.setupPresentations(doc);
CurrentUserUtils.setupFilesystem(doc);
CurrentUserUtils.setupRecentlyClosedDocs(doc);
CurrentUserUtils.setupUserDoc(doc);
}
static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
_lockedPosition: true, system: true, flexDirection: "row"
})) as any as Doc
static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
...opts, btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _removeDropProperties: new List(["_dropAction", "stayInCollection"]), _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.createToolButton({ onClick: ScriptField.MakeScript("undo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true });
}
if (doc["dockedBtn-redo"] === undefined) {
doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("redo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true });
}
if (doc.dockedBtns === undefined) {
doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
}
(doc["dockedBtn-undo"] as Doc).dontUndo = true;
(doc["dockedBtn-redo"] as Doc).dontUndo = true;
}
static textTools(doc: Doc) {
const tools: Button[] =
[
{
title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true,
list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia",
"Comic Sans MS", "Tahoma", "Impact", "Crimson Text"],
script: 'setFont'
},
{ title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize' },
{ title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor' },
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', checkResult: 'toggleBold(true)' },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', checkResult: 'toggleItalic(true)' },
{ title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', checkResult: 'toggleUnderline(true)' },
{ title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet")', checkResult: 'setBulletList("bullet", true)' },
{ title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal")', checkResult: 'setBulletList("decimal", true)' },
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'},
// { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'},
// { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'},
{ title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left")', checkResult: 'setAlignment("left", true)' },
{ title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center")', checkResult: 'setAlignment("center", true)' },
{ title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right")', checkResult: 'setAlignment("right", true)' },
];
return tools;
}
static inkTools(doc: Doc) {
const tools: Button[] = [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', checkResult: 'setActiveInkTool("pen" , true)' },
// { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', checkResult: 'setActiveInkTool("highlighter", true)' },
{ title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', checkResult: 'setActiveInkTool("circle" , true)' },
// { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', checkResult: 'setActiveInkTool("square" , true)' },
{ title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', checkResult: 'setActiveInkTool("line" , true)' },
{ title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor" },
{ title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth' },
{ title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor' },
];
return tools;
}
static schemaTools(doc: Doc) {
const tools: Button[] =
[
{
title: "Show preview",
toolTip: "Show preview of selected document",
btnType: ButtonType.ToggleButton,
buttonText: "Show Preview",
icon: "eye",
click: 'toggleSchemaPreview()',
checkResult: 'toggleSchemaPreview(true)'
},
];
return tools;
}
static webTools(doc: Doc) {
const tools: Button[] =
[
{ title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack()' },
{ title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward()' },
//{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' },
{ title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL' },
];
return tools;
}
static async contextMenuTools(doc: Doc) {
return [
{
title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true,
list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn,
CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid],
script: 'setView',
}, // Always show
{
title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip",
script: "setBackgroundColor", hidden: 'selectedDocumentType()'
}, // Only when a document is selected
{
title: "Header Color", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading",
script: "setHeaderColor", hidden: 'selectedDocumentType()',
}, // Only when a document is selected
{ title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', checkResult: 'toggleOverlay(true)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform
// { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected
{ title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available
{ title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available
{ title: "Web", type: "webTools", subMenu: true, hidden: 'selectedDocumentType("web")' }, // Only when Web is selected
{ title: "Schema", type: "schemaTools", subMenu: true, hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected
];
}
// Sets up the default context menu buttons
static async setupContextMenuButtons(doc: Doc) {
if (doc.contextMenuBtns === undefined) {
const docList: Doc[] = [];
(await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded, checkResult }) => {
const menuDocList: Doc[] = [];
if (subMenu) {
// default is textTools
let tools: Button[];
switch (type) {
case "inkTools":
tools = CurrentUserUtils.inkTools(doc);
break;
case "schemaTools":
tools = CurrentUserUtils.schemaTools(doc);
break;
case "webTools":
tools = CurrentUserUtils.webTools(doc);
break;
case "textTools":
tools = CurrentUserUtils.textTools(doc);
break;
default:
tools = CurrentUserUtils.textTools(doc);
break;
}
tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle, checkResult }) => {
menuDocList.push(Docs.Create.FontIconDocument({
_nativeWidth: width ? width : 25,
_nativeHeight: 25,
_width: width ? width : 25,
_height: 25,
icon,
toolTip,
numBtnType,
numBtnMin,
numBtnMax,
script,
btnType: btnType,
btnList: new List(list),
ignoreClick: ignoreClick,
_stayInCollection: true,
_hideContextMenu: true,
_lockedPosition: true,
system: true,
dontUndo: true,
title,
switchToggle,
color: Colors.WHITE,
backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent",
_dropAction: "alias",
_removeDropProperties: new List(["dropAction", "_stayInCollection"]),
onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined
}));
});
docList.push(CurrentUserUtils.linearButtonList({
linearViewSubMenu: true,
flexGap: 0,
ignoreClick: true,
linearViewExpandable: true,
icon: title,
_height: 30,
backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent",
linearViewIsExpanded: expanded ? !(ComputedField.MakeFunction(expanded) as any) : undefined,
hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
}, menuDocList));
} else {
docList.push(Docs.Create.FontIconDocument({
_nativeWidth: width ? width : 25,
_nativeHeight: 25,
_width: width ? width : 25,
_height: 25,
icon,
toolTip,
script,
btnType,
btnList: new List(list),
ignoreClick,
_stayInCollection: true,
_hideContextMenu: true,
_lockedPosition: true,
system: true,
dontUndo: true,
title,
color: Colors.WHITE,
backgroundColor: "transparent",
_dropAction: "alias",
hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
_removeDropProperties: new List(["dropAction", "_stayInCollection"]),
onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined
}));
}
});
doc.contextMenuBtns = CurrentUserUtils.linearButtonList({ title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList);
}
}
// sets up the default set of documents to be shown in the Overlay layer
static setupOverlays(doc: Doc) {
if (doc.myOverlayDocs === undefined) {
doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true }));
}
}
// the initial presentation Doc to use
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, _fitWidth: true, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true
}));
}
}
// Sharing sidebar is where shared documents are contained
static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (doc.myLinkDatabase === undefined) {
let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
if (!linkDocs) {
linkDocs = new Doc(linkDatabaseId, true);
(linkDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail;
(linkDocs as Doc).author = Doc.CurrentUserEmail;
(linkDocs as Doc).data = new List([]);
(linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
}
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
// TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document
if (doc.mySharedDocs === undefined) {
let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer");
if (!sharedDocs) {
sharedDocs = Docs.Create.TreeDocument([], {
title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
_chromeHidden: true, boxShadow: "0 0",
explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
}, sharingDocumentId + "outer", sharingDocumentId);
(sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
}
if (sharedDocs instanceof Doc) {
Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name });
sharedDocs.childContextMenuFilters = new List([dashboardFilter!,]);
sharedDocs.childContextMenuScripts = new List([addToDashboards!,]);
sharedDocs.childContextMenuLabels = new List(["Add to Dashboards",]);
sharedDocs.childContextMenuIcons = new List(["user-plus",]);
}
doc.mySharedDocs = new PrefetchProxy(sharedDocs);
}
}
// Import sidebar is where shared documents are contained
static setupImportSidebar(doc: Doc) {
if (doc.myImportDocs === undefined) {
const newImportButton: Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true });
doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
explainer: "This is where documents that are Imported into Dash will go."
}));
}
}
// Search sidebar is where searches within the document are performed
static setupSearchSidebar(doc: Doc) {
if (doc.mySearchPanel === undefined) {
doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({
backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true,
childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true
})) as any as Doc;
}
}
static setupClickEditorTemplates(doc: Doc) {
if (doc["clickFuncs-child"] === undefined) {
// to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
"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", system: true
});
const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
"openOnRight(self.doubleClickView)",
{}), { 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", 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));
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, 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, 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, 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, system: true
}, "onChildDoubleClick");
const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)",
"onCheckedClick-params": new List(["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", system: true });
}
PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
return doc.clickFuncs as Doc;
}
static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]),
async () => {
const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
// Document properties on load
doc.system = true;
doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
doc.title = Doc.CurrentUserEmail;
doc._raiseWhenDragged = true;
doc._showLabel = false;
doc._showMenuLabel = true;
doc.textAlign = StrCast(doc.textAlign, "left");
doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
doc.activeInkWidth = Number(StrCast(doc.activeInkWidth, "1"));
doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
doc.activeFillColor = StrCast(doc.activeFillColor, "");
doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
doc.activeDash = StrCast(doc.activeDash, "0");
doc.fontSize = StrCast(doc.fontSize, "12px");
doc.fontFamily = StrCast(doc.fontFamily, "Arial");
doc.fontColor = StrCast(doc.fontColor, "black");
doc.fontHighlight = StrCast(doc.fontHighlight, "");
doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
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"]);
doc.savedFilters = new List();
doc.filterDocCount = 0;
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.setupImportSidebar(doc); // sets up the import sidebar
this.setupSearchSidebar(doc); // sets up the search sidebar
this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
this.setupOverlays(doc); // documents in overlay layer
this.setupContextMenuButtons(doc); // set up context menu buttons
this.setupDockedButtons(doc); // the bottom bar of font icons
await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId);
if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
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 });
// uncomment this to setup a default note style that uses the custom header layout
// PromiseValue(doc.emptyHeader).then(factory => {
// if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) {
// const deleg = Doc.delegateDragFactory(factory as Doc);
// deleg.title = "header";
// doc.defaultTextLayout = new PrefetchProxy(deleg);
// Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg);
// }
// });
setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
doc.fieldInfos = await Docs.setupFieldInfos();
if (doc.activeDashboard instanceof Doc) {
// undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
}
if (doc.activeCollectionBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default.
doc.activeCollectionBackground = undefined;
}
return doc;
}
public static async loadCurrentUser() {
return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
if (response) {
const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response);
Doc.CurrentUserEmail = result.email;
resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts"));
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";")));
return result;
} else {
throw new Error("There should be a user! Why does Dash think there isn't one?");
}
});
}
public static async loadUserDocument(id: string) {
this.curr_id = id;
await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
if (userDocumentId !== "guest") {
return DocServer.GetRefField(userDocumentId).then(async field => {
Docs.newAccount = !(field instanceof Doc);
await Docs.Prototypes.initialize();
const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
(await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
const a1 = await Cast(link?.anchor1, Doc, null);
const a2 = await Cast(link?.anchor2, Doc, null);
});
return updated;
});
} else {
throw new Error("There should be a user id! Why does Dash think there isn't one?");
}
});
}
public static _urlState: HistoryUtil.DocUrl;
public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => {
CurrentUserUtils.MainDocId = doc[Id];
if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
!("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })]));
userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc);
}
const state = CurrentUserUtils._urlState;
if (state.sharing === true && !userDoc) {
DocServer.Control.makeReadOnly();
} else {
fromHistory || HistoryUtil.pushState({
type: "doc",
docId: doc[Id],
readonly: state.readonly,
nro: state.nro,
sharing: false,
});
if (state.readonly === true || state.readonly === null) {
DocServer.Control.makeReadOnly();
} else if (state.safe) {
if (!state.nro) {
DocServer.Control.makeReadOnly();
}
CollectionView.SetSafeMode(true);
} else if (state.nro || state.nro === null || state.readonly === false) {
} else if (doc.readOnly) {
DocServer.Control.makeReadOnly();
} else {
DocServer.Control.makeEditable();
}
}
return true;
}
public static importDocument = () => {
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 = Docs.newAccount ? undefined : await DocServer.GetRefField(json);
if (doc instanceof 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 importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null);
const disposer = OverlayView.ShowSpinner();
DocListCastAsync(importDocs.data).then(async list => {
const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {});
if (results.length !== input.files?.length) {
alert("Error uploading files - possibly due to unsupported file types");
}
list?.splice(0, 0, ...results);
disposer();
});
} else {
console.log("No file selected");
}
};
input.click();
}
public static async snapshotDashboard(userDoc: Doc) {
const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
CurrentUserUtils.openDashboard(userDoc, copy);
}
public static createNewDashboard = async (userDoc: Doc, id?: string) => {
const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true);
const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc;
const dashboardCount = DocListCast(dashboards.data).length + 1;
const emptyPane = Cast(userDoc.emptyPane, Doc, null);
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
const freeformOptions: DocumentOptions = {
x: 0,
y: 400,
_width: 1500,
_height: 1000,
_fitWidth: true,
_backgroundGridShow: true,
title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
};
const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
freeformDoc.context = dashboardDoc;
// switching the tabs from the datadoc to the regular doc
const dashboardTabs = dashboardDoc[DataSym].data;
dashboardDoc[DataSym].data = new List();
dashboardDoc.data = dashboardTabs;
// collating all docs on the dashboard to make a data-all field
const allDocs = new List();
const allDocs2 = new List(); // Array.from, spread, splice all cause so stack or acl issues for some reason
DocListCast(dashboardTabs).forEach(doc => {
const tabDocs = DocListCast(doc.data);
allDocs.push(...tabDocs);
allDocs2.push(...tabDocs);
});
dashboardDoc[DataSym]["data-all"] = allDocs;
dashboardDoc["data-all"] = allDocs2;
DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc);
DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
userDoc.activePresentation = presentation;
Doc.AddDocToList(dashboards, "data", dashboardDoc);
CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
}
public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
const tbox = Docs.Create.TextDocument("", {
_xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor,
_width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title
});
const template = Doc.UserDoc().defaultTextLayout;
if (template instanceof Doc) {
tbox._width = NumCast(template._width);
tbox.layoutKey = "layout_" + StrCast(template.title);
Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
}
return tbox;
}
public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); }
public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); }
public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); }
public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); }
public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); }
public static get OverlayDocs() { return DocListCast((Doc.UserDoc().myOverlayDocs as Doc)?.data); }
public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; }
@computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; }
}
Scripting.addGlobal(function openDragFactory(dragFactory: Doc) {
const copy = Doc.copyDragFactory(dragFactory);
if (copy) {
CollectionDockingView.AddSplit(copy, "right");
const view = DocumentManager.Instance.getFirstDocumentView(copy);
view && SelectionManager.SelectView(view, false);
}
});
Scripting.addGlobal(function MySharedDocs() { return Doc.SharingDoc(); },
"document containing all shared Docs");
Scripting.addGlobal(function IsNoviceMode() { return Doc.UserDoc().noviceMode; },
"is Dash in novice mode");
Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); },
"creates a snapshot copy of a dashboard");
Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); },
"creates a new dashboard when called");
Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); },
"creates a new presentation when called");
Scripting.addGlobal(function createNewFolder() { return MainView.Instance.createNewFolder(); },
"creates a new folder in myFiles when called");
Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
"returns all the links to the document or its annotations", "(doc: any)");
Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); },
"imports files from device directly into the import sidebar");
Scripting.addGlobal(function shareDashboard(dashboard: Doc) {
SharingManager.Instance.open(undefined, dashboard);
},
"opens sharing dialog for Dashboard");
Scripting.addGlobal(async function removeDashboard(dashboard: Doc) {
const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data);
if (dashboards && dashboards.length > 1) {
if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboards.find(doc => doc !== dashboard)!);
Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard);
}
},
"Remove Dashboard from Dashboards");
Scripting.addGlobal(async function addToDashboards(dashboard: Doc) {
const dashboardAlias = Doc.MakeAlias(dashboard);
const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
// moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly
// dashboard["data-all"] = new List(allDocs);
// dashboardAlias["data-all"] = new List((allDocs || []).map(doc => Doc.MakeAlias(doc)));
// const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
// dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
dashboardAlias.data = new List(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
//new List();
DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias);
},
"adds Dashboard to set of Dashboards");
/**
* Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard.
*/
Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) {
if (dashboard[DataSym] instanceof Doc) {
const allDocs = DocListCast(dashboard["data-all"]);
const onScreenTab = DocListCast(dashboard.data)[0];
const onScreenDocs = DocListCast(onScreenTab.data);
return new List(allDocs.reduce((result: Doc[], doc) => {
!onScreenDocs.includes(doc) && !onScreenDocs.includes(doc.aliasOf as Doc) && (result.push(doc));
return result;
}, []));
}
return [];
});
Scripting.addGlobal(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) {
let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined;
if (selected && checkParent) {
const parentDoc: Doc = Cast(selected.context, Doc, null);
selected = parentDoc;
}
if (selected && docType && selected.type === docType) return false;
else if (selected && colType && selected.viewType === colType) return false;
else if (selected && !colType && !docType) return false;
else return true;
});
Scripting.addGlobal(function makeTopLevelFolder() {
const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder);
});
Scripting.addGlobal(function toggleComicMode() {
Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic";
});