From 62a34043949f051533ca549be48b774696331d43 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Mon, 5 Aug 2019 17:37:43 -0400 Subject: richtext + buttons --- src/client/views/MainView.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f5a6715e5..669b8f018 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -400,6 +400,7 @@ export class MainView extends React.Component { ; @@ -523,12 +525,8 @@ export class MainView extends React.Component { /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ @computed get miscButtons() { - let logoutRef = React.createRef(); - return [ this.isSearchVisible ?
: null, -
-
]; } @@ -568,7 +566,7 @@ export class MainView extends React.Component { let next = () => PresBox.CurrentPresentation.next(); let back = () => PresBox.CurrentPresentation.back(); let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres(); - let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document); }); + let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document, undefined, "onRight"); }); return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : ; } diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index e4ef8313d..9e5e62e03 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -117,13 +117,13 @@ export class TemplateMenu extends React.Component { @action toggleChrome = (): void => { this.props.docs.map(dv => { - let layout = dv.Document.layout instanceof Doc ? dv.Document.layout as Doc : dv.Document; + let layout = dv.Document.layout instanceof Doc ? dv.Document.layout : dv.Document; layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled"); }); } render() { - let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout as Doc : this.props.docs[0].Document; + let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout : this.props.docs[0].Document; let templateMenu: Array = []; this.props.templates.forEach((checked, template) => templateMenu.push()); diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index 7197f4b49..66ad6a76d 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -8,7 +8,6 @@ import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { Cast, NumCast } from "../../../new_fields/Types"; import { List } from "../../../new_fields/List"; import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../new_fields/Schema"; -import { FlyoutProps } from "./Timeline"; import { Transform } from "../../util/Transform"; import { InkField, StrokeData } from "../../../new_fields/InkField"; import { TimelineMenu } from "./TimelineMenu"; @@ -138,48 +137,13 @@ export class Keyframe extends React.Component { @observable private _mouseToggled = false; @observable private _doubleClickEnabled = false; - @computed - private get regiondata() { - let index = this.regions.indexOf(this.props.RegionData); - return RegionData(this.regions[index] as Doc); - } - - @computed - private get regions() { - return Cast(this.props.node.regions, listSpec(Doc)) as List; - } - - @computed - private get firstKeyframe() { - let first: (Doc | undefined) = undefined; - DocListCast(this.regiondata.keyframes!).forEach(kf => { - if (kf.type !== KeyframeFunc.KeyframeType.fade) { - if (!first || first && NumCast(kf.time) < NumCast(first.time)) { - first = kf; - } - } - }); - return first; - } - - @computed - private get lastKeyframe() { - let last: (Doc | undefined) = undefined; - DocListCast(this.regiondata.keyframes!).forEach(kf => { - if (kf.type !== KeyframeFunc.KeyframeType.fade) { - if (!last || last && NumCast(kf.time) > NumCast(last.time)) { - last = kf; - } - } - }); - return last; - } - - @computed - private get keyframes(){ - return DocListCast(this.regiondata.keyframes); - } - + @computed private get regiondata() { return RegionData(this.regions[this.regions.indexOf(this.props.RegionData)] as Doc);} + @computed private get regions() { return Cast(this.props.node.regions, listSpec(Doc)) as List;} + @computed private get keyframes(){ return DocListCast(this.regiondata.keyframes); } + @computed private get pixelPosition(){ return KeyframeFunc.convertPixelTime(this.regiondata.position, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement);} + @computed private get pixelDuration(){ return KeyframeFunc.convertPixelTime(this.regiondata.duration, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } + @computed private get pixelFadeIn() { return KeyframeFunc.convertPixelTime(this.regiondata.fadeIn, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } + @computed private get pixelFadeOut(){ return KeyframeFunc.convertPixelTime(this.regiondata.fadeOut, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } @computed private get inks() { if (this.props.collection.data_ext) { @@ -191,38 +155,18 @@ export class Keyframe extends React.Component { } } - @computed - private get pixelPosition(){ - return KeyframeFunc.convertPixelTime(this.regiondata.position, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); - } - - @computed - private get pixelDuration(){ - return KeyframeFunc.convertPixelTime(this.regiondata.duration, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); - } - - @computed - private get pixelFadeIn() { - return KeyframeFunc.convertPixelTime(this.regiondata.fadeIn, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); - } - - @computed - private get pixelFadeOut(){ - return KeyframeFunc.convertPixelTime(this.regiondata.fadeOut, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); - } - - async componentWillMount() { - if (!this.regiondata.keyframes) { - this.regiondata.keyframes = new List(); - } - let fadeIn = await this.makeKeyData(this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade)!; - let fadeOut = await this.makeKeyData(this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, KeyframeFunc.KeyframeType.fade)!; - let start = await this.makeKeyData(this.regiondata.position, KeyframeFunc.KeyframeType.fade)!; - let finish = await this.makeKeyData(this.regiondata.position + this.regiondata.duration, KeyframeFunc.KeyframeType.fade)!; - (fadeIn.key! as Doc).opacity = 1; - (fadeOut.key! as Doc).opacity = 1; - (start.key! as Doc).opacity = 0.1; - (finish.key! as Doc).opacity = 0.1; + componentWillMount() { + runInAction(async () => { + if (!this.regiondata.keyframes) this.regiondata.keyframes = new List(); + let fadeIn = await this.makeKeyData(this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade)!; + let fadeOut = await this.makeKeyData(this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, KeyframeFunc.KeyframeType.fade)!; + let start = await this.makeKeyData(this.regiondata.position, KeyframeFunc.KeyframeType.fade)!; + let finish = await this.makeKeyData(this.regiondata.position + this.regiondata.duration, KeyframeFunc.KeyframeType.fade)!; + (fadeIn.key! as Doc).opacity = 1; + (fadeOut.key! as Doc).opacity = 1; + (start.key! as Doc).opacity = 0.1; + (finish.key! as Doc).opacity = 0.1; + }); } @action @@ -336,7 +280,7 @@ export class Keyframe extends React.Component { let bar = this._bar.current!; let offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement); let leftRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions); - let firstkf: (Doc | undefined) = this.firstKeyframe; + let firstkf: (Doc | undefined) = this.keyframes[0]; if (firstkf && this.regiondata.position + this.regiondata.fadeIn + offset >= NumCast(firstkf!.time)) { let dif = NumCast(firstkf!.time) - (this.pixelPosition + this.pixelFadeIn); this.regiondata.position = NumCast(firstkf!.time) - this.regiondata.fadeIn; @@ -364,8 +308,8 @@ export class Keyframe extends React.Component { let bar = this._bar.current!; let offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement); let rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions); - if (this.lastKeyframe! && this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= NumCast((this.lastKeyframe! as Doc).time)) { - let dif = this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut - NumCast((this.lastKeyframe! as Doc).time); + if (this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= NumCast((this.keyframes[this.keyframes.length - 1]).time)) { + let dif = this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut - NumCast((this.keyframes[this.keyframes.length - 1]).time); this.regiondata.duration -= dif; } else if (this.regiondata.duration + offset < this.regiondata.fadeIn + this.regiondata.fadeOut) { // nokeyframes, just fades this.regiondata.duration = this.regiondata.fadeIn + this.regiondata.fadeOut; @@ -532,6 +476,7 @@ export class Keyframe extends React.Component { e.stopPropagation(); let div = ref.current!; div.style.opacity = "1"; + Doc.BrushDoc(this.props.node); } onContainerOut = (e: React.PointerEvent, ref: React.RefObject) => { @@ -539,6 +484,7 @@ export class Keyframe extends React.Component { e.stopPropagation(); let div = ref.current!; div.style.opacity = "0"; + Doc.UnBrushDoc(this.props.node); } @@ -623,7 +569,6 @@ export class Keyframe extends React.Component { } } render() { - console.log("RERENDERING"); return (
; -} - - @observer export class Timeline extends React.Component { @@ -35,26 +24,23 @@ export class Timeline extends React.Component { private readonly MAX_CONTAINER_HEIGHT: number = 800; private readonly DEFAULT_TICK_INCREMENT: number = 1000; - @observable private _isMinimized = false; - @observable private _tickSpacing = this.DEFAULT_TICK_SPACING; - @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT; - @observable private _scrubberbox = React.createRef(); - @observable private _scrubber = React.createRef(); @observable private _trackbox = React.createRef(); @observable private _titleContainer = React.createRef(); @observable private _timelineContainer = React.createRef(); - @observable private _timelineWrapper = React.createRef(); @observable private _infoContainer = React.createRef(); + @observable private _roundToggleRef = React.createRef(); + @observable private _roundToggleContainerRef = React.createRef(); @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _isPlaying: boolean = false; //scrubber playing - @observable private _isFrozen: boolean = true; //timeline freeze @observable private _totalLength: number = 0; @observable private _visibleLength: number = 0; @observable private _visibleStart: number = 0; - @observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT; + @observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT; + @observable private _tickSpacing = this.DEFAULT_TICK_SPACING; + @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT; @observable private _time = 100000; //DEFAULT @observable private _ticks: number[] = []; @observable private _playButton = faPlayCircle; @@ -273,39 +259,7 @@ export class Timeline extends React.Component { } } - @action - onTimelineDown = (e: React.PointerEvent) => { - e.preventDefault(); - if (e.nativeEvent.which === 1 && !this._isFrozen) { - document.addEventListener("pointermove", this.onTimelineMove); - document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onTimelineMove); }); - } - } - @action - onTimelineMove = (e: PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - let timelineContainer = this._timelineWrapper.current!; - let left = parseFloat(timelineContainer.style.left!); - let top = parseFloat(timelineContainer.style.top!); - timelineContainer.style.left = `${left + e.movementX}px`; - timelineContainer.style.top = `${top + e.movementY}px`; - } - - @action - minimize = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - let timelineContainer = this._timelineContainer.current!; - if (this._isMinimized) { - this._isMinimized = false; - timelineContainer.style.visibility = "visible"; - } else { - this._isMinimized = true; - timelineContainer.style.visibility = "hidden"; - } - } @action toReadTime = (time: number): string => { @@ -321,21 +275,6 @@ export class Timeline extends React.Component { timelineContextMenu = (e:MouseEvent): void => { let subitems: ContextMenuProps[] = []; - let timelineContainer = this._timelineWrapper.current!; - subitems.push({ - description: "Pin to Top", event: action(() => { - if (!this._isFrozen) { - timelineContainer.style.left = "0px"; - timelineContainer.style.top = "0px"; - timelineContainer.style.transition = "none"; - } - }), icon: faArrowUp - }); - subitems.push({ - description: this._isFrozen ? "Unfreeze Timeline" : "Freeze Timeline", event: action(() => { - this._isFrozen = !this._isFrozen; - }), icon: "thumbtack" - }); subitems.push({ description: this._timelineVisible ? "Hide Timeline" : "Show Timeline", event: action(() => { this._timelineVisible = !this._timelineVisible; @@ -358,7 +297,8 @@ export class Timeline extends React.Component { let currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement); let currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement); this._infoContainer.current!.scrollLeft = currPixel - offset; - this._visibleStart = currPixel - offset; + this._visibleStart = currPixel - offset > 0 ? currPixel - offset : 0; + this._visibleStart += this._visibleLength + this._visibleStart > this._totalLength ? this._totalLength - (this._visibleStart + this._visibleLength) :0; this.changeCurrentBarX(currCurrent); } @@ -393,44 +333,70 @@ export class Timeline extends React.Component { private timelineToolBox = (scale:number) => { let size = 50 * scale; //50 is default - return ( + return ( +
-
-
-
- +
+
+
+ +
+
+
); } + + @action + private toggleChecked = (e:React.PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + let roundToggle = this._roundToggleRef.current!; + let roundToggleContainer = this._roundToggleContainerRef.current!; + if (BoolCast(this.props.Document.isAnimating)){ + roundToggle.style.transform = "translate(0px, 0px)"; + roundToggle.style.animationName = "turnoff"; + roundToggleContainer.style.animationName = "turnoff"; + + this.props.Document.isAnimating = false; + } else { + roundToggle.style.transform = "translate(45px, 0px)"; + roundToggle.style.animationName = "turnon"; + roundToggleContainer.style.animationName = "turnon"; + this.props.Document.isAnimating = true; + } + } render() { return ( -
-
- -
- {this.timelineToolBox(0.5)} -
-
- {this._ticks.map(element => { - if(element % this._tickIncrement === 0) return

{this.toReadTime(element)}

; - })} +
+
+
+
+
+
+ {this._ticks.map(element => { + if(element % this._tickIncrement === 0) return

{this.toReadTime(element)}

; + })} +
+
+
+
+
+ {DocListCast(this.children).map(doc => )} +
-
-
+
+ {DocListCast(this.children).map(doc =>
{Doc.BrushDoc(doc);}} onPointerOut={() => {Doc.UnBrushDoc(doc);}}>

{doc.title}

)}
-
- {DocListCast(this.children).map(doc => )} +
+
-
- {DocListCast(this.children).map(doc =>

{doc.title}

)} -
-
- -
+ { this.timelineToolBox(1) }
- {BoolCast(this.props.Document.isAnimating) ?
: this.timelineToolBox(1) } + +
); } diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx index f3b985297..59c25596e 100644 --- a/src/client/views/animationtimeline/TimelineMenu.tsx +++ b/src/client/views/animationtimeline/TimelineMenu.tsx @@ -4,6 +4,7 @@ import {observer} from "mobx-react"; import "./TimelineMenu.scss"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChartLine, faRoad, faClipboard, faPen, faTrash, faTable } from "@fortawesome/free-solid-svg-icons"; +import { Utils } from "../../../Utils"; @observer @@ -40,7 +41,7 @@ export class TimelineMenu extends React.Component { if (type === "input"){ let inputRef = React.createRef(); let text = ""; - this._currentMenu.push(
{ + this._currentMenu.push(
{ e.stopPropagation(); text = e.target.value; }} onKeyDown={(e) => { @@ -52,23 +53,23 @@ export class TimelineMenu extends React.Component { }}/>
); } else if (type === "button") { let buttonRef = React.createRef(); - this._currentMenu.push(

{ + this._currentMenu.push(

{ e.preventDefault(); e.stopPropagation(); event(e); this.closeMenu(); }}>{title}

); - } + } } @action addMenu = (title:string) => { - this._currentMenu.unshift(

{title}

); + this._currentMenu.unshift(

{title}

); } render() { return ( -
+
{this._currentMenu}
); diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx index c68d9bb3a..3ec410216 100644 --- a/src/client/views/animationtimeline/Track.tsx +++ b/src/client/views/animationtimeline/Track.tsx @@ -32,18 +32,24 @@ export class Track extends React.Component { @observable private _onKeyframe: (Doc | undefined) = undefined; @observable private _onRegionData: (Doc | undefined) = undefined; @observable private _storedState: (Doc | undefined) = undefined; - - @computed - private get regions() { - return Cast(this.props.node.regions, listSpec(Doc)) as List; - } + @observable private filterList = [ + "regions", + "cursors", + "hidden", + "nativeHeight", + "nativeWidth", + "schemaColumns", + "baseLayout", + "backgroundLayout", + "layout", + ]; + + @computed private get regions() { return Cast(this.props.node.regions, listSpec(Doc)) as List;} componentWillMount() { - if (!this.props.node.regions) { - this.props.node.regions = new List(); - } - - + runInAction(() => { + if (!this.props.node.regions) this.props.node.regions = new List(); + }); } componentDidMount() { @@ -54,11 +60,11 @@ export class Track extends React.Component { this.props.node.hidden = false; this.props.node.opacity = 1; }); - } componentWillUnmount() { runInAction(() => { + //disposing reactions if (this._currentBarXReaction) this._currentBarXReaction(); if (this._timelineVisibleReaction) this._timelineVisibleReaction(); }); @@ -166,17 +172,7 @@ export class Track extends React.Component { }); } - private filterList = [ - "regions", - "cursors", - "hidden", - "nativeHeight", - "nativeWidth", - "schemaColumns", - "baseLayout", - "backgroundLayout", - "layout", - ]; + @action private filterKeys = (keys: string[]): string[] => { @@ -294,7 +290,7 @@ export class Track extends React.Component { return (
-
+
{Doc.BrushDoc(this.props.node);}}onPointerOut={() => {Doc.UnBrushDoc(this.props.node);}}> {DocListCast(this.regions).map((region) => { return ; })} diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 56d12bd84..0168c466f 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -20,7 +20,8 @@ export enum CollectionViewType { Docking, Tree, Stacking, - Masonry + Masonry, + Pivot, } export namespace CollectionViewType { @@ -32,7 +33,8 @@ export namespace CollectionViewType { ["docking", CollectionViewType.Docking], ["tree", CollectionViewType.Tree], ["stacking", CollectionViewType.Stacking], - ["masonry", CollectionViewType.Masonry] + ["masonry", CollectionViewType.Masonry], + ["pivot", CollectionViewType.Pivot] ]); export const valueOf = (value: string) => { diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 0e7e0afa7..6f5abd05b 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,8 +1,5 @@ @import "../../views/globalCssVariables.scss"; -.collectiondockingview-content { - height: 100%; -} .lm_active .messageCounter{ color:white; background: #999999; @@ -21,7 +18,7 @@ .collectiondockingview-container { width: 100%; - height: 100%; + height:100%; border-style: solid; border-width: $COLLECTION_BORDER_WIDTH; position: absolute; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8fcba99e3..b047e77a8 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -9,7 +9,7 @@ import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { FieldId } from "../../../new_fields/RefField"; @@ -30,7 +30,9 @@ import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; import React = require("react"); import { ButtonSelector } from './ParentDocumentSelector'; +import { DocumentType } from '../../documents/DocumentTypes'; library.add(faFile); +const _global = (window /* browser */ || global /* node */) as any; @observer export class CollectionDockingView extends React.Component { @@ -533,12 +535,11 @@ interface DockedFrameProps { } @observer export class DockedFrameRenderer extends React.Component { - _mainCont: HTMLDivElement | undefined = undefined; + _mainCont: HTMLDivElement | null = null; @observable private _panelWidth = 0; @observable private _panelHeight = 0; @observable private _document: Opt; @observable private _dataDoc: Opt; - @observable private _isActive: boolean = false; get _stack(): any { @@ -576,6 +577,13 @@ export class DockedFrameRenderer extends React.Component { } componentDidMount() { + let observer = new _global.ResizeObserver(action((entries: any) => { + for (let entry of entries) { + this._panelWidth = entry.contentRect.width; + this._panelHeight = entry.contentRect.height; + } + })); + observer.observe(this.props.glContainer._element[0]); this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); @@ -594,13 +602,21 @@ export class DockedFrameRenderer extends React.Component { } } - panelWidth = () => this._document!.ignoreAspect ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth())); - panelHeight = () => this._document!.ignoreAspect ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), NumCast(this._document!.nativeHeight, this._panelHeight))); + panelWidth = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth())); + panelHeight = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight())); - nativeWidth = () => !this._document!.ignoreAspect ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0; - nativeHeight = () => !this._document!.ignoreAspect ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0; + nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0; + nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0; contentScaling = () => { + if (this._document!.type === DocumentType.PDF) { + if ((this._document && this._document.fitWidth) || + this._panelHeight / NumCast(this._document!.nativeHeight) > this._panelWidth / NumCast(this._document!.nativeWidth)) { + return this._panelWidth / NumCast(this._document!.nativeWidth); + } else { + return this._panelHeight / NumCast(this._document!.nativeHeight); + } + } const nativeH = this.nativeHeight(); const nativeW = this.nativeWidth(); if (!nativeW || !nativeH) return 1; @@ -619,6 +635,7 @@ export class DockedFrameRenderer extends React.Component { get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; } addDocTab = (doc: Doc, dataDoc: Opt, location: string) => { + SelectionManager.DeselectAll(); if (doc.dockingConfig) { MainView.Instance.openWorkspace(doc); return true; @@ -630,13 +647,10 @@ export class DockedFrameRenderer extends React.Component { return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } } - @computed get docView() { - if (!this._document) { - return (null); - } - let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc; - return { getScale={returnOne} />; } - @computed get content() { - return ( -
{ - this._mainCont = ref; - if (ref) { - this._panelWidth = Number(getComputedStyle(ref).width!.replace("px", "")); - this._panelHeight = Number(getComputedStyle(ref).height!.replace("px", "")); - } - })} - style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> - {this.docView} -
); - } - render() { - if (!this._isActive || !this._document) return null; - let theContent = this.content; - return !this._document ? (null) : - { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}> - {({ measureRef }) =>
- {theContent} -
} -
; + return (!this._isActive || !this._document) ? (null) : + (
this._mainCont = ref} + style={{ + transform: `translate(${this.previewPanelCenteringOffset}px, 0px)`, + height: this._document && this._document.fitWidth ? undefined : "100%" + }}> + {this.docView(this._document)} +
); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss index 50201bae8..62ec8a5be 100644 --- a/src/client/views/collections/CollectionPDFView.scss +++ b/src/client/views/collections/CollectionPDFView.scss @@ -1,26 +1,4 @@ -.collectionPdfView-buttonTray { - top: 15px; - left: 20px; - position: relative; - transform-origin: left top; - position: absolute; -} -.collectionPdfView-thumb { - width: 25px; - height: 25px; - transform-origin: left top; - position: absolute; - background: darkgray; -} - -.collectionPdfView-slider { - width: 25px; - height: 25px; - transform-origin: left top; - position: absolute; - background: lightgray; -} .collectionPdfView-cont { width: 100%; @@ -29,28 +7,5 @@ top: 0; left: 0; z-index: -1; + overflow: hidden !important; } - -.collectionPdfView-cont-dragging { - span { - user-select: none; - } -} - -.collectionPdfView-backward { - color: white; - font-size: 24px; - top: 0px; - left: 0px; - position: absolute; - background-color: rgba(50, 50, 50, 0.2); -} - -.collectionPdfView-forward { - color: white; - font-size: 24px; - top: 0px; - left: 45px; - position: absolute; - background-color: rgba(50, 50, 50, 0.2); -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 8eda4d9ee..cc8142ec0 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,10 +1,9 @@ -import { computed } from "mobx"; +import { trace } from "mobx"; import { observer } from "mobx-react"; import { Id } from "../../../new_fields/FieldSymbols"; import { emptyFunction } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; -import { PDFBox } from "../nodes/PDFBox"; import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPDFView.scss"; @@ -17,35 +16,18 @@ export class CollectionPDFView extends React.Component { return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt); } - private _pdfBox?: PDFBox; - private _buttonTray: React.RefObject = React.createRef(); - - @computed - get uIButtons() { - return ( -
- - -
- ); - } - onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "PDFOptions", event: emptyFunction, icon: "file-pdf" }); } } - setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; }; - subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { - return (<> - - {renderProps.active() ? this.uIButtons : (null)} - ); + return (); } render() { + trace(); return ( {this.subView} diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 4dac27e60..179e44266 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -34,7 +34,7 @@ export interface CellProps { row: number; col: number; rowProps: CellInfo; - CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; + CollectionView: Opt; ContainingCollection: Opt; Document: Doc; fieldKey: string; @@ -151,7 +151,7 @@ export class CollectionSchemaCell extends React.Component { fieldExt: "", ruleProvider: undefined, ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView.props.Document, + ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document, isSelected: returnFalse, select: emptyFunction, renderDepth: this.props.renderDepth + 1, @@ -301,7 +301,7 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { render() { let reference = React.createRef(); let onItemDown = (e: React.PointerEvent) => { - (!this.props.CollectionView.props.isSelected() ? undefined : + (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined : SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); }; return ( diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 7bd2a1971..8d931f812 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -246,7 +246,7 @@ export interface SchemaTableProps { PanelHeight: () => number; PanelWidth: () => number; childDocs?: Doc[]; - CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; + CollectionView: Opt; ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; fieldKey: string; @@ -804,7 +804,7 @@ export class SchemaTable extends React.Component { csv.substring(0, csv.length - 1); let dbName = StrCast(this.props.Document.title); let res = await Gateway.Instance.PostSchema(csv, dbName); - if (self.props.CollectionView.props.addDocument) { + if (self.props.CollectionView && self.props.CollectionView.props.addDocument) { let schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document }); if (schemaDoc) { //self.props.CollectionView.props.addDocument(schemaDoc, false); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ccf131797..45de0fefa 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -42,7 +42,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); } @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } - @computed get showAddAGroup() { return (this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')); } + @computed get showAddAGroup() { return (this.sectionFilter && this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.ContainingCollectionDoc.chromeStatus !== 'disabled')); } @computed get columnWidth() { return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250)); @@ -160,13 +160,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { if (!d) return 0; let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); - if (!d.ignoreAspect && nw && nh) { + if (!d.ignoreAspect && !d.fitWidth && nw && nh) { let aspect = nw && nh ? nh / nw : 1; let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid); return wid * aspect; } - return d[HeightSym](); + return d.fitWidth ? Math.min(this.props.PanelHeight() - 2 * this.yMargin, d[HeightSym]()) : d[HeightSym](); } columnDividerDown = (e: React.PointerEvent) => { @@ -347,7 +347,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } onToggle = (checked: Boolean) => { - this.props.CollectionView.props.Document.chromeStatus = checked ? "collapsed" : "view-mode"; + this.props.ContainingCollectionDoc && (this.props.ContainingCollectionDoc.chromeStatus = checked ? "collapsed" : "view-mode"); } onContextMenu = (e: React.MouseEvent): void => { @@ -391,10 +391,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { style={{ width: this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
} - {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? : null} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index b3b7b40dd..240adf428 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -266,7 +266,7 @@ export class CollectionStackingViewFieldColumn extends React.Component {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */} @@ -297,7 +297,7 @@ export class CollectionStackingViewFieldColumn extends React.Component : (null); for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; return ( -
{headingView}
- {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? + {(this.props.parent.props.ContainingCollectionDoc && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'view-mode' && this.props.parent.props.ContainingCollectionDoc.chromeStatus !== 'disabled') ?
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index c11dd6150..069269b06 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,12 +1,12 @@ import { action, computed, IReactionDisposer, reaction } from "mobx"; import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { BoolCast, Cast } from "../../../new_fields/Types"; +import { Cast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from "../../../Utils"; @@ -30,10 +30,11 @@ export interface CollectionViewProps extends FieldViewProps { PanelWidth: () => number; PanelHeight: () => number; chromeCollapsed: boolean; + setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } export interface SubCollectionViewProps extends CollectionViewProps { - CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; + CollectionView: Opt; ruleProvider: Doc | undefined; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 5f4742834..d3072ff1e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -18,6 +18,7 @@ import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionStackingView } from './CollectionStackingView'; import { CollectionTreeView } from "./CollectionTreeView"; import { CollectionViewBaseChrome } from './CollectionViewChromes'; +import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; export const COLLECTION_BORDER_WIDTH = 2; library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); @@ -59,8 +60,10 @@ export class CollectionView extends React.Component { case CollectionViewType.Tree: return (); case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } case CollectionViewType.Freeform: default: + this.props.Document.freeformLayoutEngine = undefined; return (); } return (null); @@ -89,7 +92,7 @@ export class CollectionView extends React.Component { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; - subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; delete this.props.Document.usePivotLayout; }, icon: "signature" }); + subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" }); if (CollectionBaseView.InSafeMode()) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } @@ -103,10 +106,10 @@ export class CollectionView extends React.Component { }, icon: "ellipsis-v" }); subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); + subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" }); switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { - subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) }); - subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true }); + subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) }); break; } } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 20786f690..cefa9eebc 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -260,7 +260,7 @@ export class CollectionViewBaseChrome extends React.Component { - if (!this.document.usePivotLayout) { + if (StrCast(this.document.freeformLayoutEngine) !== "pivot") { return (null); } return (Tree View +
ViewDefResult[]) { + let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map(); + const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200); + const pivotColumnGroups = new Map, Doc[]>(); + + for (const doc of childDocs) { + const val = doc[StrCast(pivotDoc.pivotField, "title")]; + if (val) { + !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []); + pivotColumnGroups.get(val)!.push(doc); + } + } + + const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity); + const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize))); + const docMap = new Map(); + const groupNames: PivotData[] = []; + + let x = 0; + pivotColumnGroups.forEach((val, key) => { + let y = 0; + let xCount = 0; + groupNames.push({ + type: "text", + text: String(key), + x, + y: pivotAxisWidth + 50, + width: pivotAxisWidth * 1.25 * numCols, + height: 100, + fontSize: NumCast(pivotDoc.pivotFontSize, 10) + }); + for (const doc of val) { + docMap.set(doc, { + x: x + xCount * pivotAxisWidth * 1.25, + y: -y, + width: pivotAxisWidth, + height: doc.nativeWidth ? (NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth + }); + xCount++; + if (xCount >= numCols) { + xCount = 0; + y += pivotAxisWidth * 1.25; + } + } + x += pivotAxisWidth * 1.25 * (numCols + 1); + }); + + childPairs.map(pair => { + let defaultPosition = { + x: NumCast(pair.layout.x), + y: NumCast(pair.layout.y), + z: NumCast(pair.layout.z), + width: NumCast(pair.layout.width), + height: NumCast(pair.layout.height) + }; + const pos = docMap.get(pair.layout) || defaultPosition; + layoutPoolData.set(pair, { transition: "transform 1s", ...pos }); + }); + return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) }; +} + +export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void { + return () => { + let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record, requiredType?: string) => { + let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox + const scriptField = Cast(doc[key], ScriptField); + let scriptingBox = overlayDisposer()} // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below + onSave={(text, onError) => { + const script = CompileScript(text, { params, requiredType, typecheck: false }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + } else { + doc[key] = new ScriptField(script); + overlayDisposer(); + } + }} />; + overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options); + }; + addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined); + addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); + }; +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5157d0c75..c6e8d7cf7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,22 +1,23 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; +import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; +import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; +import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; -import { CompileScript } from "../../../util/Scripting"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch, UndoManager } from "../../../util/UndoManager"; @@ -26,12 +27,12 @@ import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView"; +import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView"; +import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; -import { OverlayElementOptions, OverlayView } from "../../OverlayView"; import PDFMenu from "../../pdf/PDFMenu"; -import { ScriptBox } from "../../ScriptBox"; import { CollectionSubView } from "../CollectionSubView"; +import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; @@ -40,9 +41,6 @@ import React = require("react"); import v5 = require("uuid/v5"); import { Timeline } from "../../animationtimeline/Timeline"; import { number } from "prop-types"; -import { DocServer } from "../../../DocServer"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -54,132 +52,10 @@ export const panZoomSchema = createSchema({ arrangeInit: ScriptField, useClusters: "boolean", isRuleProvider: "boolean", - fitToBox: "boolean" + fitToBox: "boolean", + panTransformType: "string", }); -export interface ViewDefBounds { - x: number; - y: number; - z?: number; - width: number; - height: number; -} - -export interface ViewDefResult { - ele: JSX.Element; - bounds?: ViewDefBounds; -} - -export namespace PivotView { - - export interface PivotData { - type: string; - text: string; - x: number; - y: number; - width: number; - height: number; - fontSize: number; - } - - export const elements = (target: CollectionFreeFormView) => { - let collection = target.Document; - const field = StrCast(collection.pivotField) || "title"; - const width = NumCast(collection.pivotWidth) || 200; - const groups = new Map, Doc[]>(); - - for (const doc of target.childDocs) { - const val = doc[field]; - if (val === undefined) continue; - - const l = groups.get(val); - if (l) { - l.push(doc); - } else { - groups.set(val, [doc]); - } - } - - let minSize = Infinity; - - groups.forEach((val, key) => minSize = Math.min(minSize, val.length)); - - const numCols = NumCast(collection.pivotNumColumns) || Math.ceil(Math.sqrt(minSize)); - const fontSize = NumCast(collection.pivotFontSize); - - const docMap = new Map(); - const groupNames: PivotData[] = []; - - let x = 0; - groups.forEach((val, key) => { - let y = 0; - let xCount = 0; - groupNames.push({ - type: "text", - text: String(key), - x, - y: width + 50, - width: width * 1.25 * numCols, - height: 100, fontSize: fontSize - }); - for (const doc of val) { - docMap.set(doc, { - x: x + xCount * width * 1.25, - y: -y, - width, - height: width - }); - xCount++; - if (xCount >= numCols) { - xCount = 0; - y += width * 1.25; - } - } - x += width * 1.25 * (numCols + 1); - }); - - let elements = target.viewDefsToJSX(groupNames); - let docViews = target.childDocs.reduce((prev, doc) => { - let minim = BoolCast(doc.isMinimized); - if (minim === undefined || !minim) { - let defaultPosition = (): ViewDefBounds => { - return { - x: NumCast(doc.x), - y: NumCast(doc.y), - z: NumCast(doc.z), - width: NumCast(doc.width), - height: NumCast(doc.height) - }; - }; - const pos = docMap.get(doc) || defaultPosition(); - prev.push({ - ele: , - bounds: { - x: pos.x, - y: pos.y, - z: pos.z, - width: NumCast(pos.width), - height: NumCast(pos.height) - } - }); - } - return prev; - }, elements); - - return docViews; - }; - -} - type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); @@ -187,50 +63,26 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _lastX: number = 0; private _lastY: number = 0; - private get _pwidth() { return this.props.PanelWidth(); } - private get _pheight() { return this.props.PanelHeight(); } - private _timelineRef = React.createRef(); - private get parentScaling() { - return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1; - } - - ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) { - let bounds = boundsList.reduce((bounds, b) => { - var [sptX, sptY] = [b.x, b.y]; - let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }); - return bounds; - } - - @computed get actualContentBounds() { - return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; - } - - @computed get contentBounds() { - let bounds = this.actualContentBounds; - let res = { - panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, - panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, - scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling - }; - if (res.scale === 0) res.scale = 1; - return res; - } - - @computed get fitToBox() { return this.props.fitToBox || this.Document.fitToBox; } - @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; } - @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; } - public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') + private _clusterDistance: number = 75; + private _hitCluster = false; + @observable _clusterSets: (Doc[])[] = []; + @observable _timelineRef = React.createRef(); + + @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; } + @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; } + @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); } + @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; } + @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; } + private get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } - private panX = () => this.contentBounds.panX; - private panY = () => this.contentBounds.panY; - private zoomScaling = () => this.contentBounds.scale; - private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections - private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections + private easing = () => this.props.Document.panTransformType === "Ease"; + private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0; + private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0; + private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ? + Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) : + this.Document.scale || 1); + private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections + private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1); private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); @@ -248,47 +100,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { - this.props.addDocument(newBox, false); - this.bringToFront(newBox); - this.updateCluster(newBox); - return true; + let added = this.props.addDocument(newBox, false); + added && this.bringToFront(newBox); + added && this.updateCluster(newBox); + return added; } private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv => - SelectionManager.SelectDoc(dv!, true)); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); } + public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); } + public getActiveDocuments = () => { - const curPage = FieldValue(this.Document.curPage, -1); - return this.childLayoutPairs.filter(pair => { - var page = NumCast(pair.layout!.page, -1); - return page === curPage || page === -1; - }).map(pair => pair.layout); + return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); } @computed get fieldExtensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey); } - intersectRect(r1: { left: number, top: number, width: number, height: number }, - r2: { left: number, top: number, width: number, height: number }) { - return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); - } - _clusterDistance = 75; - boundsOverlap(doc: Doc, doc2: Doc) { - var x2 = NumCast(doc2.x) - this._clusterDistance; - var y2 = NumCast(doc2.y) - this._clusterDistance; - var w2 = NumCast(doc2.width) + this._clusterDistance; - var h2 = NumCast(doc2.height) + this._clusterDistance; - var x = NumCast(doc.x) - this._clusterDistance; - var y = NumCast(doc.y) - this._clusterDistance; - var w = NumCast(doc.width) + this._clusterDistance; - var h = NumCast(doc.height) + this._clusterDistance; - if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) { - return true; - } - return false; + @action + onDrop = (e: React.DragEvent): void => { + var pt = this.getTransform().transformPoint(e.pageX, e.pageY); + super.onDrop(e, { x: pt[0], y: pt[1] }); } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { @@ -304,7 +140,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let y = (z ? ypo : yp) - de.data.offset[1]; let dropX = NumCast(de.data.droppedDocuments[0].x); let dropY = NumCast(de.data.droppedDocuments[0].y); - de.data.droppedDocuments.forEach(d => { + de.data.droppedDocuments.forEach(action((d: Doc) => { d.x = x + NumCast(d.x) - dropX; d.y = y + NumCast(d.y) - dropY; if (!NumCast(d.width)) { @@ -316,7 +152,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; } this.bringToFront(d); - }); + })); de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); } @@ -339,18 +175,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } - tryDragCluster(e: PointerEvent) { - let probe = this.getTransform().transformPoint(e.clientX, e.clientY); - let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { + pickCluster(probe: number[]) { + return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { let cx = NumCast(cd.x) - this._clusterDistance; let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(cd.width) + 2 * this._clusterDistance; let ch = NumCast(cd.height) + 2 * this._clusterDistance; - if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) { - return NumCast(cd.cluster); - } - return cluster; + return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? + NumCast(cd.cluster) : cluster; }, -1); + } + tryDragCluster(e: PointerEvent) { + let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); if (cluster !== -1) { let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); @@ -375,36 +211,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } - @observable sets: (Doc[])[] = []; @undoBatch - @action updateClusters(useClusters: boolean) { - this.Document.useClusters = useClusters; - this.sets.length = 0; - this.childLayoutPairs.map(pair => pair.layout).map(c => { - let included = []; - for (let i = 0; i < this.sets.length; i++) { - for (let member of this.sets[i]) { - if (this.boundsOverlap(c, member)) { - included.push(i); - break; - } - } - } - if (included.length === 0) { - this.sets.push([c]); - } else if (included.length === 1) { - this.sets[included[0]].push(c); - } else { - this.sets[included[0]].push(c); - for (let s = 1; s < included.length; s++) { - this.sets[included[0]].push(...this.sets[included[s]]); - this.sets[included[s]].length = 0; - } - } - }); - this.sets.map((set, i) => set.map(member => member.cluster = i)); + this.props.Document.useClusters = useClusters; + this._clusterSets.length = 0; + this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c)); } @undoBatch @@ -412,28 +224,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { updateCluster(doc: Doc) { let childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.props.Document.useClusters) { - this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); + this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; - this.sets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) { + this._clusterSets.map((set, i) => set.map(member => { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { doc.cluster = i; } })); - if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { + if (doc.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } - this.sets.map((set, i) => { + this._clusterSets.map((set, i) => { if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { doc.cluster = i; } }); if (doc.cluster === -1) { - doc.cluster = this.sets.length; - this.sets.push([doc]); + doc.cluster = this._clusterSets.length; + this._clusterSets.push([doc]); } else { - for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]); - this.sets[doc.cluster].push(doc); + for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]); + this._clusterSets[doc.cluster].push(doc); } } } @@ -442,13 +254,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let clusterColor = ""; let cluster = NumCast(doc.cluster); if (this.Document.useClusters) { - if (this.sets.length <= cluster) { + if (this._clusterSets.length <= cluster) { setTimeout(() => this.updateCluster(doc), 0); } else { // choose a cluster color from a palette let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; clusterColor = colors[cluster % colors.length]; - let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined; + let set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); @@ -459,6 +271,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { + this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -476,8 +289,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble) { - if (this.props.Document.useClusters && this.tryDragCluster(e)) { + if (!e.cancelBubble && !this.isAnnotationOverlay) { + if (this._hitCluster && this.tryDragCluster(e)) { e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); document.removeEventListener("pointermove", this.onPointerMove); @@ -512,8 +325,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; - let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale, - this._pheight / this.zoomScaling() * cscale); + let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale, + this.props.PanelHeight() / this.zoomScaling() * cscale); if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; @@ -529,7 +342,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { - if (BoolCast(this.props.Document.lockedPosition)) return; + if (this.props.Document.lockedPosition || this.isAnnotationOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -546,13 +359,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); this.props.Document.scale = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); - e.preventDefault(); } } @action setPan(panX: number, panY: number) { - if (!BoolCast(this.props.Document.lockedPosition)) { + if (!this.props.Document.lockedPosition) { this.props.Document.panTransformType = "None"; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); @@ -562,12 +374,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - @action - onDrop = (e: React.DragEvent): void => { - var pt = this.getTransform().transformPoint(e.pageX, e.pageY); - super.onDrop(e, { x: pt[0], y: pt[1] }); - } - bringToFront = (doc: Doc, sendToBack?: boolean) => { if (sendToBack || doc.isBackground) { doc.zIndex = 0; @@ -599,54 +405,45 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } SelectionManager.DeselectAll(); - const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; - const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; - const newState = HistoryUtil.getState(); - newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; - HistoryUtil.pushState(newState); - - let px = this.Document.panX; - let py = this.Document.panY; - let s = this.Document.scale; - this.setPan(newPanX, newPanY); - - this.props.Document.panTransformType = "Ease"; - this.props.focus(this.props.Document); - if (willZoom) { - this.setScaleToZoom(doc, scale); + if (this.props.Document.scrollHeight) { + let annotOn = Cast(doc.annotationOn, Doc) as Doc; + let offset = annotOn && (NumCast(annotOn.height) / 2); + this.props.Document.scrollY = NumCast(doc.y) - offset; + } else { + const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; + const newState = HistoryUtil.getState(); + newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; + HistoryUtil.pushState(newState); + + let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType }; + + this.setPan(newPanX, newPanY); + this.Document.panTransformType = "Ease"; + this.props.focus(this.props.Document); + willZoom && this.setScaleToZoom(doc, scale); + + afterFocus && setTimeout(() => { + if (afterFocus && afterFocus()) { + this.Document.panX = savedState.px; + this.Document.panY = savedState.py; + this.Document.scale = savedState.s; + this.Document.panTransformType = savedState.pt; + } + }, 1000); } - console.log("Focused " + this.Document.title + " " + s); - afterFocus && setTimeout(() => { - if (afterFocus && afterFocus()) { - console.log("UnFocused " + this.Document.title + " " + s); - this.Document.panX = px; - this.Document.panY = py; - this.Document.scale = s; - } - }, 1000); + } setScaleToZoom = (doc: Doc, scale: number = 0.5) => { - let p = this.props; - let PanelHeight = p.PanelHeight(); - let panelWidth = p.PanelWidth(); - - let docHeight = NumCast(doc.height); - let docWidth = NumCast(doc.width); - let targetHeight = scale * PanelHeight; - let targetWidth = scale * panelWidth; - - let maxScaleX: number = targetWidth / docWidth; - let maxScaleY: number = targetHeight / docHeight; - let maxApplicableScale = Math.min(maxScaleX, maxScaleY); - this.Document.scale = maxApplicableScale; + this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height)); } zoomToScale = (scale: number) => { this.Document.scale = scale; } - getScale = () => this.Document.scale ? this.Document.scale : 1; + getScale = () => this.Document.scale || 1; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { @@ -663,7 +460,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelHeight: childLayout[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView.props.Document, + ContainingCollectionDoc: this.props.Document, focus: this.focusDocument, backgroundColor: this.getClusterColor, parentActive: this.props.active, @@ -690,7 +487,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelHeight: layoutDoc[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView.props.Document, + ContainingCollectionDoc: this.props.ContainingCollectionDoc, focus: this.focusDocument, backgroundColor: returnEmptyString, parentActive: this.props.active, @@ -713,110 +510,109 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } viewDefsToJSX = (views: any[]) => { - let elements: ViewDefResult[] = []; - if (Array.isArray(views)) { - elements = views.reduce((prev, ele) => { - const jsx = this.viewDefToJSX(ele); - jsx && prev.push(jsx); - return prev; - }, elements); - } - return elements; + return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!); } private viewDefToJSX(viewDef: any): Opt { if (viewDef.type === "text") { - const text = Cast(viewDef.text, "string"); + const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below const x = Cast(viewDef.x, "number"); const y = Cast(viewDef.y, "number"); const z = Cast(viewDef.z, "number"); const width = Cast(viewDef.width, "number"); const height = Cast(viewDef.height, "number"); const fontSize = Cast(viewDef.fontSize, "number"); - if ([text, x, y, width, height].some(val => val === undefined)) { - return undefined; - } - - return { - ele:
{text}
, bounds: { x: x!, y: y!, z: z, width: width!, height: height! } - }; + return [text, x, y, width, height].some(val => val === undefined) ? undefined : + { + ele:
+ {text} +
, + bounds: { x: x!, y: y!, z: z, width: width!, height: height! } + }; } } - @computed.struct - get elements() { - if (this.Document.usePivotLayout) return PivotView.elements(this); - let curPage = FieldValue(this.Document.curPage, -1); - const initScript = this.Document.arrangeInit; - let state: any = undefined; - let pairs = this.childLayoutPairs; - let elements: ViewDefResult[] = []; - if (initScript) { - const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log); - if (initResult.success) { - const result = initResult.result; - const { state: scriptState, views } = result; - state = scriptState; - elements = this.viewDefsToJSX(views); - } + lookupLayout = (doc: Doc, dataDoc?: Doc) => { + let data: any = undefined; + let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] }; + switch (this.Document.freeformLayoutEngine) { + case "pivot": computedElementData = this.doPivotLayout; break; + default: computedElementData = this.doFreeformLayout; break; } - let docviews = pairs.reduce((prev, pair) => { - var page = NumCast(pair.layout.page, -1); - if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) { - const pos = this.getCalculatedPositions({ doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }); - state = pos.state === undefined ? state : pos.state; - prev.push({ - ele: , - bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 } - }); + computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => { + if (key.layout === doc && key.data === dataDoc) { + data = value; } - // } - return prev; - }, elements); + }); + return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition }; + } - return docviews; + @computed + get doPivotLayout() { + return computePivotLayout(this.props.Document, this.childDocs, + this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX); } - @computed.struct - get views() { - return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + @computed + get doFreeformLayout() { + let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map(); + let layoutDocs = this.childLayoutPairs.map(pair => pair.layout); + const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log); + let state = initResult && initResult.success ? initResult.result.scriptState : undefined; + let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; + + this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { + const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); + state = pos.state === undefined ? state : pos.state; + layoutPoolData.set(pair, pos); + }); + return { map: layoutPoolData, elements: elements }; } - @computed.struct - get overlayViews() { - return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); + + @computed + get doLayoutComputation() { + let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] }; + switch (this.Document.freeformLayoutEngine) { + case "pivot": computedElementData = this.doPivotLayout; break; + default: computedElementData = this.doFreeformLayout; break; + } + this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => + computedElementData.elements.push({ + ele: , + bounds: this.lookupLayout(pair.layout, pair.data) + })); + + return computedElementData; } + @computed.struct get elements() { return this.doLayoutComputation.elements; } + @computed.struct get views() { return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } + @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } + @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } - arrangeContents = async () => { - const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); + layoutDocsInGrid = () => { UndoManager.RunInBatch(() => { - if (docs) { - let 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.width))); - const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs) { - doc.x = x; - doc.y = y; - x += width + 20; - if (++i === 6) { - i = 0; - x = startX; - y += height + 20; - } + const docs = DocListCast(this.Document[this.props.fieldKey]); + let 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.width))); + const height = Math.max(...docs.map(doc => NumCast(doc.height))); + for (const doc of docs) { + doc.x = x; + doc.y = y; + x += width + 20; + if (++i === 6) { + i = 0; + x = startX; + y += height + 20; } } }, "arrange contents"); @@ -852,10 +648,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this._timelineRef.current!.timelineContextMenu(e.nativeEvent); layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" }); - layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); + layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); layoutItems.push({ @@ -910,54 +706,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { , ...this.views ] - - public static AddCustomLayout(doc: Doc, dataKey: string): () => void { - return () => { - let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record, requiredType?: string) => { - let overlayDisposer: () => void = emptyFunction; - const script = Cast(doc[key], ScriptField); - let originalText: string | undefined = undefined; - if (script) originalText = script.script.originalScript; - // tslint:disable-next-line: no-unnecessary-callback-wrapper - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { - const script = CompileScript(text, { - params, - requiredType, - typecheck: false - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - return; - } - doc[key] = new ScriptField(script); - overlayDisposer(); - }} />; - overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options); - }; - addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined); - addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); - }; - } render() { // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) - this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x; - this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y; - this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x); - this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y); + this.props.Document.fitX = this.contentBounds && this.contentBounds.x; + this.props.Document.fitY = this.contentBounds && this.contentBounds.y; + this.props.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x); + this.props.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); // if fieldExt is set, then children will be stored in the extension document for the fieldKey. // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey); - const easing = () => this.props.Document.panTransformType === "Ease"; return (
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} setPreviewCursor={this.props.setPreviewCursor} + getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> + easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> - + {this.childViews} @@ -974,23 +742,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @observer class CollectionFreeFormOverlayView extends React.Component boolean }> { - @computed get overlayView() { - return (); - } render() { - return this.overlayView; + return ; } } @observer class CollectionFreeFormBackgroundView extends React.Component boolean }> { - @computed get backgroundView() { - return (); - } render() { - return this.props.Document.backgroundLayout ? this.backgroundView : (null); + return !this.props.Document.backgroundLayout ? (null) : + (); } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bbea4a555..82193aefa 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -30,6 +30,8 @@ interface MarqueeViewProps { removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; + isAnnotationOverlay: boolean; + setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @observer @@ -43,6 +45,10 @@ export class MarqueeView extends React.Component @observable _visible: boolean = false; _commandExecuted = false; + componentDidMount() { + this.props.setPreviewCursor && this.props.setPreviewCursor(this.setPreviewCursor); + } + @action cleanupInteractions = (all: boolean = false) => { if (all) { @@ -145,15 +151,10 @@ export class MarqueeView extends React.Component } @action onPointerDown = (e: React.PointerEvent): void => { - this._downX = this._lastX = e.pageX; - this._downY = this._lastY = e.pageY; - this._commandExecuted = false; - PreviewCursor.Visible = false; - this.cleanupInteractions(true); + this._downX = this._lastX = e.clientX; + this._downY = this._lastY = e.clientY; if (e.button === 2 || (e.button === 0 && e.altKey)) { - document.addEventListener("pointermove", this.onPointerMove, true); - document.addEventListener("pointerup", this.onPointerUp, true); - document.addEventListener("keydown", this.marqueeCommand, true); + this.setPreviewCursor(e.clientX, e.clientY, true); if (e.altKey) { //e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors. e.preventDefault(); @@ -176,6 +177,8 @@ export class MarqueeView extends React.Component e.stopPropagation(); e.preventDefault(); } + } else { + this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this } if (e.altKey) { e.preventDefault(); @@ -185,16 +188,13 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); - // console.log("pointer up!"); if (this._visible) { - // console.log("visible"); let mselect = this.marqueeSelect(); if (!e.shiftKey) { SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } - //console.log("invisible"); this.cleanupInteractions(true); if (e.altKey) { @@ -202,11 +202,28 @@ export class MarqueeView extends React.Component } } + setPreviewCursor = (x: number, y: number, drag: boolean) => { + if (drag) { + this._downX = this._lastX = x; + this._downY = this._lastY = y; + this._commandExecuted = false; + PreviewCursor.Visible = false; + this.cleanupInteractions(true); + document.addEventListener("pointermove", this.onPointerMove, true); + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("keydown", this.marqueeCommand, true); + } else { + this._downX = x; + this._downY = y; + PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument); + } + } + @action onClick = (e: React.MouseEvent): void => { if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument); + this.setPreviewCursor(e.clientX, e.clientY, false); // let the DocumentView stopPropagation of this event when it selects this document } else { // why do we get a click event when the cursor have moved a big distance? // let's cut it off here so no one else has to deal with it. @@ -297,8 +314,8 @@ export class MarqueeView extends React.Component y: bounds.top, panX: 0, panY: 0, - backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, - defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, + backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, + defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, title: "a nested collection", diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 81b0249dd..cad404d1f 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -18,6 +18,7 @@ import { DocServer } from "../../DocServer"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { docs_v1 } from "googleapis"; +import { Utils } from "../../../Utils"; enum FollowModes { OPENTAB = "Open in Tab", @@ -242,6 +243,7 @@ export class LinkFollowBox extends React.Component { let proto = Doc.GetProto(LinkFollowBox.linkDoc); let targetContext = await Cast(proto.targetContext, Doc); let sourceContext = await Cast(proto.sourceContext, Doc); + let guid = StrCast(LinkFollowBox.linkDoc[Id]); const shouldZoom = options ? options.shouldZoom : false; let dockingFunc = (document: Doc) => { (this._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; @@ -251,6 +253,14 @@ export class LinkFollowBox extends React.Component { } else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) { + if (guid) { + let views = DocumentManager.Instance.getDocumentViews(jumpToDoc); + views.length && (views[0].props.Document.scrollToLinkID = guid); + } else { + jumpToDoc.linkHref = Utils.prepend("/doc/" + StrCast(LinkFollowBox.linkDoc[Id])); + } + } } else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 82fe3df23..835554ac0 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -28,9 +28,7 @@ interface LinkMenuItemProps { export class LinkMenuItem extends React.Component { private _drag = React.createRef(); @observable private _showMore: boolean = false; - @action toggleShowMore() { - this._showMore = !this._showMore; - } + @action toggleShowMore() { this._showMore = !this._showMore; } onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index fcf483659..dd063ec9d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -12,6 +12,7 @@ import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { + dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; x?: number; y?: number; width?: number; @@ -32,14 +33,14 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } - @computed get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); } - @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); } + get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } + get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : this.Document.x || 0; } + get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : this.Document.y || 0; } + get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.props.Document[WidthSym](); } + get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.props.Document[HeightSym](); } + @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; } @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } - @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); } @computed get renderScriptDim() { if (this.Document.renderScript) { @@ -72,11 +73,12 @@ export class CollectionFreeFormDocumentView extends DocComponent this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) + .scale(1 / this.contentScaling()) borderRounding = () => { let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; - let br = StrCast(((this.layoutDoc.layout as Doc) || this.Document).borderRounding); + let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout : undefined; + let br = StrCast((ld || this.props.Document).borderRounding); br = !br && ruleRounding ? ruleRounding : br; if (br.endsWith("%")) { let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight)); @@ -98,6 +100,9 @@ export class CollectionFreeFormDocumentView extends DocComponent this.dataProvider ? this.dataProvider.width : this.panelWidth(); + finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight(); + render() { return (
); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ac68fea3..ea669b23c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -227,15 +227,18 @@ export class DocumentView extends DocComponent(Docu else if (linkedDocs.length) { SelectionManager.DeselectAll(); let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); + let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored); let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); if (firstUnshown.length) first = [firstUnshown[0]]; - let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; + if (secondUnshown.length) second = [secondUnshown[0]]; + let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined; // @TODO: shouldn't always follow target context let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined]; let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined]; - if (!linkedFwdDocs.some(l => l instanceof Promise)) { + if (linkedFwdDocs && !linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, @@ -258,7 +261,7 @@ export class DocumentView extends DocComponent(Docu this._hitTemplateDrag = true; } } - if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. + if (this.active && e.button === 0 && !this.Document.lockedPosition) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -266,11 +269,11 @@ export class DocumentView extends DocComponent(Docu } onPointerMove = (e: PointerEvent): void => { if (e.cancelBubble && this.active) { - document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && this.active) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) { + if (!e.altKey && !this.topMost && e.buttons === 1) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); @@ -300,7 +303,7 @@ export class DocumentView extends DocComponent(Docu await swapViews(this.props.Document, "", "layoutNative"); let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; - let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : + let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : this.Document.type === DocumentType.PDF ? Docs.Create.PdfDocument("http://www.msn.com", options) : this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); @@ -354,7 +357,7 @@ export class DocumentView extends DocComponent(Docu // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); de.data.linkSourceDocument !== this.props.Document && - (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc)); + (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc, undefined, "in-text link being created")); // TODODO this is where in text links get passed } } @@ -605,7 +608,7 @@ export class DocumentView extends DocComponent(Docu ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; - const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; + const nativeHeight = this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%"; const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle"); const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 49fc2263d..b93c78cfd 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -50,7 +50,6 @@ export interface FieldViewProps { PanelWidth: () => number; PanelHeight: () => number; setVideoBox?: (player: VideoBox) => void; - setPdfBox?: (player: PDFBox) => void; ContentScaling: () => number; ChromeHeight?: () => number; } @@ -96,7 +95,7 @@ export class FieldView extends React.Component { return

{field.date.toLocaleString()}

; } else if (field instanceof Doc) { - return

{field.title}

; + return

{field.title && field.title.toString()}

; //return

{field.title + " : id= " + field[Id]}

; // let returnHundred = () => 100; // return ( diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 0d7277cbe..45e516015 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -164,13 +164,13 @@ ol { counter-reset: deci1 0;} .upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 } .lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; } .lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10;} -.decimal1:before { content: counter(deci1) ")"; counter-increment: deci1; display:inline-block; width: 30} -.decimal2:before { content: counter(deci1) "." counter(deci2) ")"; counter-increment: deci2; display:inline-block; width: 35} -.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ")"; counter-increment: deci3; display:inline-block; width: 35} -.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ")"; counter-increment: deci4; display:inline-block; width: 40} -.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ")"; counter-increment: deci5; display:inline-block; width: 40} -.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ")"; counter-increment: deci6; display:inline-block; width: 45} -.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ")"; counter-increment: deci7; display:inline-block; width: 50} -.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ")"; counter-increment: ualph; display:inline-block; width: 35 } -.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ")"; counter-increment: lroman;display:inline-block; width: 50 } -.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ")"; counter-increment: lalpha; display:inline-block; width: 35} +.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;} +.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35} +.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35} +.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40} +.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40} +.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45} +.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50} +.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 } +.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 } +.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35} diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index eb4718581..63a16f90c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -80,6 +80,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; private _searchReactionDisposer?: Lambda; + private _scrollToRegionReactionDisposer: Opt; private _reactionDisposer: Opt; private _textReactionDisposer: Opt; private _heightReactionDisposer: Opt; @@ -138,6 +139,52 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.isOverlay) { DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } + + this._scrollToRegionReactionDisposer = reaction( + () => StrCast(this.props.Document.scrollToLinkID), + async (scrollToLinkID) => { + let findLinkFrag = (frag: Fragment, editor: EditorView) => { + const nodes: Node[] = []; + frag.forEach((node, index) => { + let examinedNode = findLinkNode(node, editor); + if (examinedNode && examinedNode.textContent) { + nodes.push(examinedNode); + start += index; + } + }); + return { frag: Fragment.fromArray(nodes), start: start }; + }; + let findLinkNode = (node: Node, editor: EditorView) => { + if (!node.isText) { + const content = findLinkFrag(node.content, editor); + return node.copy(content.frag); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); + return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; + }; + + let start = -1; + if (this._editorView && scrollToLinkID) { + let editor = this._editorView; + let ret = findLinkFrag(editor.state.doc.content, editor); + + if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) { + let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start + if (ret.frag.firstChild) { + selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + } + editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); + const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); + setTimeout(() => this.unhighlightSearchTerms(), 2000); + } + this.props.Document.scrollToLinkID = undefined; + } + + }, + { fireImmediately: true } + ); } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @@ -696,6 +743,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView && this._editorView.destroy(); this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), + handleScrollToSelection: (editorView) => { + let ref = editorView.domAtPos(editorView.state.selection.from); + let refNode = ref.node as any; + while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; + let r1 = refNode && refNode.getBoundingClientRect(); + let r3 = self._ref.current!.getBoundingClientRect(); + r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + return true; + }, dispatchTransaction: this.dispatchTransaction, nodeViews: { image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, @@ -736,6 +792,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { + this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer(); this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); @@ -757,6 +814,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); } + let ctrlKey = e.ctrlKey; + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + e.preventDefault(); + } + } + + onPointerUp = (e: React.PointerEvent): void => { + FormattedTextBoxComment.textBox = this; + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + e.stopPropagation(); + } + } + + @action + onFocused = (e: React.FocusEvent): void => { + document.removeEventListener("keypress", this.recordKeyHandler); + document.addEventListener("keypress", this.recordKeyHandler); + this.tryUpdateHeight(); + if (!this.props.isOverlay) { + FormattedTextBox.InputBoxOverlay = this; + } else { + if (this._ref.current) { + this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll; + } + } + } + onPointerWheel = (e: React.WheelEvent): void => { + // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time + if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + e.stopPropagation(); + } + } + + onClick = (e: React.MouseEvent): void => { let ctrlKey = e.ctrlKey; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; @@ -771,8 +862,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); if (node) { let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - href = link && link.attrs.href; - location = link && link.attrs.location; + if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). + href = link && link.attrs.href; + location = link && link.attrs.location; + } } if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { @@ -783,14 +876,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let proto = Doc.GetProto(linkDoc); let targetContext = await Cast(proto.targetContext, Doc); let jumpToDoc = await Cast(linkDoc.anchor2, Doc); + if (jumpToDoc) { if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); return; } } - if (targetContext) { + if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } else if (jumpToDoc) { DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); @@ -812,39 +905,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - e.preventDefault(); - } - } - - onPointerUp = (e: React.PointerEvent): void => { - FormattedTextBoxComment.textBox = this; - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { - e.stopPropagation(); - } - } - - @action - onFocused = (e: React.FocusEvent): void => { - document.removeEventListener("keypress", this.recordKeyHandler); - document.addEventListener("keypress", this.recordKeyHandler); - this.tryUpdateHeight(); - if (!this.props.isOverlay) { - FormattedTextBox.InputBoxOverlay = this; - } else { - if (this._ref.current) { - this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll; - } - } - } - onPointerWheel = (e: React.WheelEvent): void => { - // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time - if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - e.stopPropagation(); - } - } - - onClick = (e: React.MouseEvent): void => { // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); @@ -852,7 +912,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let node = this._editorView!.state.doc.nodeAt(pos.pos); let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + let hit = this._editorView!.domAtPos(pos.pos).node as any; + let beforeEle = document.querySelector("." + hit.className) as Element; + let before = beforeEle ? window.getComputedStyle(beforeEle, ':before') : undefined; + let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined; + if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) { + let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined; + if (ol && ol.type === schema.nodes.ordered_list && !e.shiftKey) { + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); + } else { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } + } } } } @@ -903,7 +974,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 624593245..004f50590 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -38,6 +38,7 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", + fitWidth: "boolean" }); interface Window { diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index c88a94c28..2917c81cb 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -1,77 +1,67 @@ .pdfBox-cont, .pdfBox-cont-interactive { - display: flex; + display: inline-block; flex-direction: row; height: 100%; - overflow-y: scroll; - overflow-x: hidden; - .pdfBox-scrollHack { - pointer-events: none; - } + width:100%; + overflow: hidden; + position:absolute; + z-index: -1; } .pdfBox-cont { pointer-events: none; - .pdfPage-textlayer { - span { - pointer-events: none !important; - user-select: none; + .collectionFreeFormView-none { + pointer-events: none; + } + .pdfViewer-text { + .textLayer { + span { + user-select: none; + } } } } .pdfBox-cont-interactive { pointer-events: all; - .pdfPage-textlayer { - span { - pointer-events: all !important; - user-select: text; + .pdfViewer-text { + .textLayer { + span { + user-select: text; + } } } } -.react-pdf__Page { - transform-origin: left top; - position: absolute; - top: 0; - left: 0; -} - -.react-pdf__Page__textContent span { - user-select: text; -} - -.react-pdf__Document { - position: absolute; -} - .pdfBox-settingsCont { position: absolute; right: 0; - top: 0; + top: 3; + pointer-events: all; .pdfBox-settingsButton { border-bottom-left-radius: 50%; display: flex; justify-content: space-evenly; align-items: center; - height: 70px; + height: 30px; background: none; padding: 0; .pdfBox-settingsButton-arrow { width: 0; height: 0; - border-top: 25px solid transparent; - border-bottom: 25px solid transparent; - border-right: 25px solid #121721; + border-top: 15px solid transparent; + border-bottom: 15px solid transparent; + border-right: 15px solid #121721; transition: all 0.5s; } .pdfBox-settingsButton-iconCont { background: #121721; - height: 50px; + height: 30px; width: 70px; display: flex; justify-content: center; @@ -86,16 +76,15 @@ } .pdfBox-settingsFlyout { - width: 600px; position: absolute; background: #323232; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - left: -400px; + right: 20px; border-radius: 7px; padding: 20px; display: flex; flex-direction: column; - font-size: 30px; + font-size: 14px; transition: all 0.5s; .pdfBox-settingsFlyout-title { @@ -108,4 +97,69 @@ grid-template-columns: 47.5% 5% 47.5%; } } -} \ No newline at end of file +} + +.pdfBox-overlayCont { + position: absolute; + width: 100%; + height: 40px; + background: #121721; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + overflow: hidden; + transition: left .5s; + pointer-events: all; + + .pdfBox-searchBar { + width: 70%; + font-size: 14px; + } +} + +.pdfBox-overlayButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 30px; + background: none; + padding: 0; + position: absolute; + pointer-events: all; + + .pdfBox-overlayButton-arrow { + width: 0; + height: 0; + border-top: 15px solid transparent; + border-bottom: 15px solid transparent; + border-right: 15px solid #121721; + transition: all 0.5s; + } + + .pdfBox-overlayButton-iconCont, + .pdfBox-nextIcon, + .pdfBox-prevIcon { + background: #121721; + height: 30px; + width: 70px; + display: flex; + justify-content: center; + align-items: center; + margin-left: -2px; + border-radius: 3px; + } +} + +.pdfBox-overlayButton:hover { + background: none; +} + +.pdfBox-nextIcon { + left: 20; top: 5; height: 30px; position: absolute; +} +.pdfBox-prevIcon { + left: 50; top: 5; height: 30px; position: absolute; +} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 764051d62..fe71e76fd 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import 'react-image-lightbox/style.css'; import { Doc, Opt, WidthSym } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; -import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; +import { ScriptField } from '../../../new_fields/ScriptField'; import { Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; import { KeyCodes } from '../../northstar/utils/KeyCodes'; @@ -19,82 +19,59 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { undoBatch } from '../../util/UndoManager'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; +import { Utils } from '../../../Utils'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer export class PDFBox extends DocComponent(PdfDocument) { - public static LayoutString() { return FieldView.LayoutString(PDFBox); } - private _mainCont: React.RefObject = React.createRef(); - private _reactionDisposer?: IReactionDisposer; + public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(PDFBox, "data", fieldExt); } private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; + private _searchString: string = ""; + private _isChildActive = false; + private _pdfViewer: PDFViewer | undefined; private _keyRef: React.RefObject = React.createRef(); private _valueRef: React.RefObject = React.createRef(); private _scriptRef: React.RefObject = React.createRef(); + @observable private _searching: boolean = false; @observable private _flyout: boolean = false; - @observable private _alt = false; @observable private _pdf: Opt; + @observable private _pageControls = false; @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } componentDidMount() { - this.props.setPdfBox && this.props.setPdfBox(this); - - this.props.Document.curPage = ComputedField.MakeFunction("Math.floor(Number(this.panY) / Number(this.nativeHeight) + 1)"); - const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); if (pdfUrl instanceof PdfField) { Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); } - this._reactionDisposer = reaction( - () => this.Document.panY, - () => this._mainCont.current && this._mainCont.current.scrollTo({ top: this.Document.panY || 0, behavior: "auto" }) - ); - } - - componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - } - - public GetPage() { - return Math.floor((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1; - } - - @action - public BackPage() { - let cp = Math.ceil((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1; - cp = cp - 1; - if (cp > 0) { - this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0); - } } - - @action - public GotoPage = (p: number) => { - if (p > 0 && p <= NumCast(this.dataDoc.numPages)) { - this.Document.panY = (p - 1) * (this.Document.nativeHeight || 0); + loaded = (nw: number, nh: number, np: number) => { + this.dataDoc.numPages = np; + if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { + let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); + this.Document.nativeWidth = nw * 96 / 72; + this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72; } + !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw)); } - @action - public ForwardPage() { - let cp = this.GetPage() + 1; - if (cp <= NumCast(this.dataDoc.numPages)) { - this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0); - } - } - - @action - setPanY = (y: number) => { - this.Document.panY = y; - } + public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } + public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); } + public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); } + public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); } + public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); }; + public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); } + @undoBatch @action private applyFilter = () => { let scriptText = this._scriptValue ? this._scriptValue : @@ -102,10 +79,6 @@ export class PDFBox extends DocComponent(PdfDocumen this.props.Document.filterScript = ScriptField.MakeFunction(scriptText); } - scrollTo = (y: number) => { - this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); - } - private resetFilters = () => { this._keyValue = this._valueValue = this._scriptValue = ""; this._keyRef.current && (this._keyRef.current.value = ""); @@ -117,83 +90,112 @@ export class PDFBox extends DocComponent(PdfDocumen private newValueChange = (e: React.ChangeEvent) => this._valueValue = e.currentTarget.value; private newScriptChange = (e: React.ChangeEvent) => this._scriptValue = e.currentTarget.value; + whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); + active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; + setPdfViewer = (pdfViewer: PDFViewer) => { this._pdfViewer = pdfViewer; }; + searchStringChanged = (e: React.ChangeEvent) => this._searchString = e.currentTarget.value; + settingsPanel() { + let pageBtns = <> + + + ; return !this.props.active() ? (null) : - (
e.stopPropagation()}> - + + +
+ -
-
- Annotation View Settings -
-
- - -
-
- -
-
- - + this.gotoPage(Number(e.currentTarget.value))} + style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }} + onClick={action(() => this._pageControls = !this._pageControls)} /> + {this._pageControls ? pageBtns : (null)} +
e.stopPropagation()}> + +
+
+ Annotation View Settings +
+
+ + +
+
+ +
+
+ + +
); } - loaded = (nw: number, nh: number, np: number) => { - this.dataDoc.numPages = np; - if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { - let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); - this.Document.nativeWidth = nw; - this.Document.nativeHeight = this.Document.nativeHeight ? nw * oldaspect : nh; - this.Document.height = this.Document[WidthSym]() * (nh / nw); - this.Document.scrollHeight = np * this.Document.nativeHeight; - } - } + specificContextMenu = (e: React.MouseEvent): void => { + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); + let funcs: ContextMenuProps[] = []; + pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" }); - @action - onScroll = (e: React.UIEvent) => { - if (e.currentTarget && this.props.ContainingCollectionDoc) { - this.props.Document.panTransformType = "None"; - this.Document.panY = e.currentTarget.scrollTop; - } + ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); + let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive"); return (!(pdfUrl instanceof PdfField) || !this._pdf ?
{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}
: -
-
- { + let hit = document.elementFromPoint(e.clientX, e.clientY); + if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation + e.button === 0 && e.stopPropagation(); + } + }}> + {this.settingsPanel()}
); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index e376fbddb..5afd85430 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -161,10 +161,8 @@ export class PresBox extends React.Component { //FieldViewProps? if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.showButton) { let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); - if (prevScale !== undefined) { - if (prevScale !== curScale) { - DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); - } + if (prevScale !== undefined && prevScale !== curScale) { + DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); } } } diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index a9fa883c8..3ed85f6a5 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -85,7 +85,15 @@ class RegionAnnotation extends React.Component { @action onPointerDown = async (e: React.PointerEvent) => { - if (e.button === 0) { + if (e.button === 2 || e.ctrlKey) { + PDFMenu.Instance.Status = "annotation"; + PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this); + PDFMenu.Instance.Pinned = false; + PDFMenu.Instance.AddTag = this.addTag.bind(this); + PDFMenu.Instance.PinToPres = this.pinToPres; + PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); + } + else if (e.button === 0) { let targetDoc = await Cast(this.props.document.target, Doc); if (targetDoc) { let context = await Cast(targetDoc.targetContext, Doc); @@ -96,14 +104,6 @@ class RegionAnnotation extends React.Component { } } } - if (e.button === 2) { - PDFMenu.Instance.Status = "annotation"; - PDFMenu.Instance.Delete = this.deleteAnnotation.bind(this); - PDFMenu.Instance.Pinned = false; - PDFMenu.Instance.AddTag = this.addTag.bind(this); - PDFMenu.Instance.PinToPres = this.pinToPres; - PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); - } } addTag = (key: string, value: string): boolean => { diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 3ed81faef..2202351ee 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -1,11 +1,10 @@ import React = require("react"); import "./PDFMenu.scss"; -import { observable, action, runInAction } from "mobx"; +import { observable, action, } from "mobx"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { emptyFunction, returnFalse } from "../../../Utils"; import { Doc } from "../../../new_fields/Doc"; -import { handleBackspace } from "../nodes/PDFBox"; @observer export default class PDFMenu extends React.Component { @@ -238,8 +237,8 @@ export default class PDFMenu extends React.Component { ,
- - + +
, , diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index a2f3911c5..8027e93a3 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -1,93 +1,66 @@ - -.pdfViewer-viewer { - pointer-events:inherit; + +.pdfViewer-viewer, .pdfViewer-viewer-zoomed { + pointer-events: inherit; width: 100%; - .pdfViewer-visibleElements { - .pdfPage-cont { - .pdfPage-textLayer { - div { - user-select: text; - } - span { - color: transparent; - position: absolute; - white-space: pre; - cursor: text; - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; - } - } - } + height: 100%; + position: absolute; + overflow-y: auto; + overflow-x: hidden; + + // .canvasWrapper { + // transform: scale(0.75); + // transform-origin: top left; + // } + // .textLayer { + // transform: scale(0.75); + // transform-origin: top left; + // } + + .page { + position: relative; } - .pdfViewer-text { - transform: scale(1.5); - transform-origin: top left; + .collectionfreeformview-container { + pointer-events: none; + } + + .pdfViewer-dragAnnotationBox { + position:absolute; + background-color: transparent; + opacity: 0.1; } + .pdfViewer-overlay { + transform: scale(2.14359); + transform-origin: left top; + position: absolute; + top: 0px; + left: 0px; + display: inline-block; + width:100%; + } .pdfViewer-annotationLayer { position: absolute; top: 0; width: 100%; pointer-events: none; + .pdfPage-annotationBox { position: absolute; background-color: red; opacity: 0.1; } } - - .pdfViewer-overlayCont { - position: absolute; - width: 100%; - height: 100px; - background: #121721; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - padding: 20px; - overflow: hidden; - transition: left .5s; - .pdfViewer-overlaySearchBar { - width: 20%; - height: 100%; - font-size: 30px; - padding: 5px; - } - } - - .pdfViewer-overlayButton { - border-bottom-left-radius: 50%; - display: flex; - justify-content: space-evenly; - align-items: center; - height: 70px; - background: none; - padding: 0; + .pdfViewer-waiting { + width: 70%; + height: 70%; + margin : 15%; + transition: 0.4s opacity ease; + opacity: 0.7; position: absolute; - - .pdfViewer-overlayButton-arrow { - width: 0; - height: 0; - border-top: 25px solid transparent; - border-bottom: 25px solid transparent; - border-right: 25px solid #121721; - transition: all 0.5s; - } - - .pdfViewer-overlayButton-iconCont { - background: #121721; - height: 50px; - width: 70px; - display: flex; - justify-content: center; - align-items: center; - margin-left: -2px; - border-radius: 3px; - } - } - - .pdfViewer-overlayButton:hover { - background: none; + z-index: 10; } } +.pdfViewer-viewer-zoomed { + overflow-x: scroll; +} + \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index c94b4e3a4..13fd8ea98 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,28 +1,33 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, trace, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import * as rp from "request-promise"; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc"; +import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils, numberRange } from "../../../Utils"; +import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; -import { KeyCodes } from "../../northstar/utils/KeyCodes"; -import { CompileScript, CompiledScript } from "../../util/Scripting"; -import Annotation from "./Annotation"; -import Page from "./Page"; +import { DragManager } from "../../util/DragManager"; +import { CompiledScript, CompileScript } from "../../util/Scripting"; +import { Transform } from "../../util/Transform"; +import PDFMenu from "./PDFMenu"; import "./PDFViewer.scss"; import React = require("react"); -import requestPromise = require("request-promise"); +import * as rp from "request-promise"; +import { CollectionPDFView } from "../collections/CollectionPDFView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { CollectionView } from "../collections/CollectionView"; +import Annotation from "./Annotation"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); +const pdfjsLib = require("pdfjs-dist"); -export const scale = 2; +pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; interface IViewerProps { pdf: Pdfjs.PDFDocumentProxy; @@ -31,15 +36,23 @@ interface IViewerProps { DataDoc?: Doc; fieldExtensionDoc: Doc; fieldKey: string; + fieldExt: string; + PanelWidth: () => number; + PanelHeight: () => number; + ContentScaling: () => number; + select: (isCtrlPressed: boolean) => void; + renderDepth: number; + isSelected: () => boolean; loaded: (nw: number, nh: number, np: number) => void; - panY: number; - scrollTo: (y: number) => void; active: () => boolean; - setPanY?: (n: number) => void; GoToPage?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; + setPdfViewer: (view: PDFViewer) => void; + ScreenToLocalTransform: () => Transform; + ContainingCollectionView: Opt; + whenActiveChanged: (isActive: boolean) => void; } /** @@ -47,36 +60,37 @@ interface IViewerProps { */ @observer export class PDFViewer extends React.Component { - @observable.shallow private _visibleElements: JSX.Element[] = []; // _visibleElements is the array of JSX elements that gets rendered - @observable private _isPage: string[] = [];// _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @observable private _savedAnnotations: Dictionary = new Dictionary(); @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript; - @observable private _searching: boolean = false; @observable private Index: number = -1; - - private _pageBuffer: number = 1; + @observable private _marqueeX: number = 0; + @observable private _marqueeY: number = 0; + @observable private _marqueeWidth: number = 0; + @observable private _marqueeHeight: number = 0; + @observable private _marqueeing: boolean = false; + @observable private _showWaiting = true; + @observable private _showCover = false; + @observable private _zoomed = 1; + + public pdfViewer: any; + private _isChildActive = false; + private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _annotationLayer: React.RefObject = React.createRef(); private _reactionDisposer?: IReactionDisposer; + private _selectionReactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; private _filterReactionDisposer?: IReactionDisposer; private _viewer: React.RefObject = React.createRef(); private _mainCont: React.RefObject = React.createRef(); - public _pdfViewer: any; - private _pdfFindController: any; - private _searchString: string = ""; + private _marquee: React.RefObject = React.createRef(); private _selectionText: string = ""; - - @computed get panY(): number { return this.props.panY; } - - // startIndex: where to start rendering pages - @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.panY) - this._pageBuffer); } - - // endIndex: where to end rendering pages - @computed get endIndex(): number { - return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer); - } + private _startX: number = 0; + private _startY: number = 0; + private _downX: number = 0; + private _downY: number = 0; + private _coverPath: any; @computed get allAnnotations() { return DocListCast(this.props.fieldExtensionDoc.annotations).filter( @@ -87,42 +101,32 @@ export class PDFViewer extends React.Component { return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result); } - componentDidUpdate = (prevProps: IViewerProps) => this.panY !== prevProps.panY && this.renderPages(); - componentDidMount = async () => { - await this.initialLoad(); - + // change the address to be the file address of the PNG version of each page + // file address of the pdf + this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`))); + runInAction(() => this._showWaiting = this._showCover = true); + this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.setupPdfJsViewer()); this._reactionDisposer = reaction( - () => [this.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0], - () => this.renderPages(), - { fireImmediately: true }); - - this._annotationReactionDisposer = reaction( - () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations), - annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), - { fireImmediately: true }); - - this._filterReactionDisposer = reaction( - () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }), - action(({ scriptField, annos }: { scriptField: FieldResult, annos: Doc[] }) => { - let oldScript = this._script.originalScript; - this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; - if (this._script.originalScript !== oldScript) { - this.Index = -1; + () => this.props.Document.scrollY, + (scrollY) => { + if (scrollY !== undefined) { + if (this._showCover || this._showWaiting) { + this.setupPdfJsViewer(); + } + this._mainCont.current && smoothScroll(1000, this._mainCont.current, NumCast(this.props.Document.scrollY) || 0); + this.props.Document.scrollY = undefined; } - annos.forEach(d => d.opacity = this._script.run({ this: d }, console.log, 1).result ? 1 : 0); - }), + }, { fireImmediately: true } ); - - document.removeEventListener("copy", this.copy); - document.addEventListener("copy", this.copy); } componentWillUnmount = () => { this._reactionDisposer && this._reactionDisposer(); this._annotationReactionDisposer && this._annotationReactionDisposer(); this._filterReactionDisposer && this._filterReactionDisposer(); + this._selectionReactionDisposer && this._selectionReactionDisposer(); document.removeEventListener("copy", this.copy); } @@ -143,41 +147,74 @@ export class PDFViewer extends React.Component { } } - searchStringChanged = (e: React.ChangeEvent) => this._searchString = e.currentTarget.value; - - pageLoaded = (page: Pdfjs.PDFPageViewport): void => this.props.loaded(page.width, page.height, this.props.pdf.numPages); - setSelectionText = (text: string) => this._selectionText = text; - getIndex = () => this.Index; - @action initialLoad = async () => { if (this._pageSizes.length === 0) { - this._isPage = Array(this.props.pdf.numPages); this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); - this._visibleElements = Array(this.props.pdf.numPages); await Promise.all(this._pageSizes.map>((val, i) => this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => { this._pageSizes.splice(i, 1, { - width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale, - height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale + width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), + height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) }); - this._visibleElements.splice(i, 1, -
- "PAGE IS LOADING... " -
); - this.getPlaceholderPage(i); + i === this.props.pdf.numPages - 1 && this.props.loaded((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), + (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i); })))); - this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); - - let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.panY)); - this.props.setPanY && this.props.setPanY(startY); - this.props.scrollTo(startY); + Doc.GetProto(this.props.Document).scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; } } + @action + setupPdfJsViewer = async () => { + this._selectionReactionDisposer && this._selectionReactionDisposer(); + this._selectionReactionDisposer = undefined; + this._showWaiting = true; + this.props.setPdfViewer(this); + await this.initialLoad(); + + this._annotationReactionDisposer = reaction( + () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations), + annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), + { fireImmediately: true }); + + this._filterReactionDisposer = reaction( + () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }), + action(({ scriptField, annos }: { scriptField: FieldResult, annos: Doc[] }) => { + let oldScript = this._script.originalScript; + this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; + if (this._script.originalScript !== oldScript) { + this.Index = -1; + } + annos.forEach(d => d.opacity = this._script.run({ this: d }, console.log, 1).result ? 1 : 0); + }), + { fireImmediately: true } + ); + + document.removeEventListener("copy", this.copy); + document.addEventListener("copy", this.copy); + document.addEventListener("pagesinit", action(() => { + this.pdfViewer.currentScaleValue = this._zoomed = 1; + this.gotoPage(NumCast(this.props.Document.curPage, 1)); + })); + document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); + var pdfLinkService = new PDFJSViewer.PDFLinkService(); + let pdfFindController = new PDFJSViewer.PDFFindController({ + linkService: pdfLinkService, + }); + this.pdfViewer = new PDFJSViewer.PDFViewer({ + container: this._mainCont.current, + viewer: this._viewer.current, + linkService: pdfLinkService, + findController: pdfFindController, + renderer: "canvas", + }); + pdfLinkService.setViewer(this.pdfViewer); + pdfLinkService.setDocument(this.props.pdf, null); + this.pdfViewer.setDocument(this.props.pdf); + } + @action makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => { let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); @@ -198,9 +235,9 @@ export class PDFViewer extends React.Component { annoDocs.push(annoDoc); annoDoc.isButton = true; anno.remove(); - this.props.addDocument && this.props.addDocument(annoDoc, false); mainAnnoDoc = annoDoc; - mainAnnoDocProto = Doc.GetProto(annoDoc); + mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); + mainAnnoDocProto.y = annoDoc.y; } else { this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { let annoDoc = new Doc(); @@ -230,61 +267,6 @@ export class PDFViewer extends React.Component { return mainAnnoDoc; } - @action - getPlaceholderPage = (page: number) => { - if (this._isPage[page] !== "none") { - this._isPage[page] = "none"; - this._visibleElements[page] = ( -
- "PAGE IS LOADING... " -
); - } - } - - @action - getRenderedPage = (page: number) => { - if (this._isPage[page] !== "page") { - this._isPage[page] = "page"; - this._visibleElements[page] = (); - } - } - - // change the address to be the file address of the PNG version of each page - // file address of the pdf - @action - getPageImage = async (page: number) => { - if (this._isPage[page] !== "image") { - this._isPage[page] = "image"; - try { - let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${page + 1}.PNG`))); - runInAction(() => this._visibleElements[page] = - this.getRenderedPage(page)} - style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />); - } catch (e) { - console.log(e); - } - } - } - - renderPages = () => { - numberRange(this.props.pdf.numPages).filter(p => this._isPage[p] !== undefined).map(i => - (i < this.startIndex || i > this.endIndex) ? this.getPlaceholderPage(i) : // pages outside of the pdf use empty stand-in divs - this.props.active() ? this.getRenderedPage(i) : this.getPageImage(i)); - } - @action renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => { if (removeOldAnnotations) { @@ -297,23 +279,30 @@ export class PDFViewer extends React.Component { } @action - prevAnnotation = (e: React.MouseEvent) => { - e.stopPropagation(); + prevAnnotation = () => { this.Index = Math.max(this.Index - 1, 0); - let scrollToAnnotation = this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]; - this.allAnnotations.forEach(d => Doc.UnBrushDoc(d)); - Doc.BrushDoc(scrollToAnnotation); - this.props.scrollTo(NumCast(scrollToAnnotation.y)); + this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]); } @action - nextAnnotation = (e: React.MouseEvent) => { - e.stopPropagation(); + nextAnnotation = () => { this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1); - let scrollToAnnotation = this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]; + this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]); + } + + @action + gotoPage = (p: number) => { + this.pdfViewer && this.pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); + } + + @action + scrollToAnnotation = (scrollToAnnotation: Doc) => { this.allAnnotations.forEach(d => Doc.UnBrushDoc(d)); + let windowHgt = this.props.PanelHeight() / this.props.ContentScaling(); + let scrollRange = this._mainCont.current!.scrollHeight - windowHgt; + let pgScroll = scrollRange / this._pageSizes.length; + this._mainCont.current!.scrollTo(0, NumCast(scrollToAnnotation.y) - pgScroll / 2); Doc.BrushDoc(scrollToAnnotation); - this.props.scrollTo(NumCast(scrollToAnnotation.y)); } sendAnnotations = (page: number) => { @@ -330,6 +319,11 @@ export class PDFViewer extends React.Component { } } + @action + onScroll = (e: React.UIEvent) => { + this.pdfViewer && (this.props.Document.curPage = this.pdfViewer.currentPageNumber); + } + // get the page index that the vertical offset passed in is on getPageFromScroll = (vOffset: number) => { let index = 0; @@ -340,15 +334,11 @@ export class PDFViewer extends React.Component { return index; } - getScrollFromPage = (index: number): number => { - return numberRange(Math.min(this.props.pdf.numPages, index)).reduce((counter, i) => counter + this._pageSizes[i].height, 0); - } - @action createAnnotation = (div: HTMLDivElement, page: number) => { if (this._annotationLayer.current) { if (div.style.top) { - div.style.top = (parseInt(div.style.top) + this.getScrollFromPage(page)).toString(); + div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString(); } this._annotationLayer.current.append(div); let savedPage = this._savedAnnotations.getValue(page); @@ -363,11 +353,14 @@ export class PDFViewer extends React.Component { } @action - search = (searchString: string) => { - if (this._pdfViewer._pageViewsReady) { - this._pdfFindController.executeCommand('findagain', { + search = (searchString: string, fwd: boolean) => { + if (!searchString) { + fwd ? this.nextAnnotation() : this.prevAnnotation(); + } + else if (this.pdfViewer._pageViewsReady) { + this.pdfViewer.findController.executeCommand('findagain', { caseSensitive: false, - findPrevious: undefined, + findPrevious: !fwd, highlightAll: true, phraseSearch: true, query: searchString @@ -375,122 +368,354 @@ export class PDFViewer extends React.Component { } else if (this._mainCont.current) { let executeFind = () => { - this._pdfFindController.executeCommand('find', { + this.pdfViewer.findController.executeCommand('find', { caseSensitive: false, - findPrevious: undefined, + findPrevious: !fwd, highlightAll: true, phraseSearch: true, query: searchString }); - } + }; this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); } } - @action - toggleSearch = (e: React.MouseEvent) => { - e.stopPropagation(); - this._searching = !this._searching; - - if (this._searching) { - if (!this._pdfFindController && this._mainCont.current && this._viewer.current && !this._pdfViewer) { - let simpleLinkService = new SimpleLinkService(this); - this._pdfViewer = new PDFJSViewer.PDFViewer({ - container: this._mainCont.current, - viewer: this._viewer.current, - linkService: simpleLinkService - }) - simpleLinkService.setPDFJSview(this._pdfViewer); - this._mainCont.current.addEventListener("pagesinit", () => this._pdfViewer.currentScaleValue = 1); - this._mainCont.current.addEventListener("pagerendered", () => console.log("rendered")); - this._pdfViewer.setDocument(this.props.pdf); - this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer); - this._pdfViewer.findController = this._pdfFindController; + onPointerDown = (e: React.PointerEvent): void => { + // if alt+left click, drag and annotate + this._downX = e.clientX; + this._downY = e.clientY; + if (NumCast(this.props.Document.scale, 1) !== 1) return; + if ((e.button !== 0 || e.altKey) && this.active()) { + this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true); + } + this._marqueeing = false; + if (!e.altKey && e.button === 0 && this.active()) { + PDFMenu.Instance.StartDrag = this.startDrag; + PDFMenu.Instance.Highlight = this.highlight; + PDFMenu.Instance.Snippet = this.createSnippet; + PDFMenu.Instance.Status = "pdf"; + PDFMenu.Instance.fadeOut(true); + if (e.target && (e.target as any).parentElement.className === "textLayer") { + if (!e.ctrlKey) { + this.receiveAnnotations([], -1); + } + } + else { + // set marquee x and y positions to the spatially transformed position + if (this._mainCont.current) { + let boundingRect = this._mainCont.current.getBoundingClientRect(); + this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width); + this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop; + } + this._marqueeing = true; + let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox"); + if (marquees && marquees.length) { // make a copy of the marquee + let marquee = marquees[0] as HTMLDivElement; + marquee.style.opacity = "0.2"; + } + this.receiveAnnotations([], -1); } + document.removeEventListener("pointermove", this.onSelectMove); + document.addEventListener("pointermove", this.onSelectMove); + document.removeEventListener("pointerup", this.onSelectEnd); + document.addEventListener("pointerup", this.onSelectEnd); } } - @computed get visibleElementWrapper() { - trace(); - return this._visibleElements; + + @action + onSelectMove = (e: PointerEvent): void => { + if (this._marqueeing && this._mainCont.current) { + // transform positions and find the width and height to set the marquee to + let boundingRect = this._mainCont.current.getBoundingClientRect(); + this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width)) - this._startX; + this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height)) - this._startY + this._mainCont.current.scrollTop; + this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); + this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight); + this._marqueeWidth = Math.abs(this._marqueeWidth); + this._marqueeHeight = Math.abs(this._marqueeHeight); + e.stopPropagation(); + e.preventDefault(); + } + else if (e.target && (e.target as any).parentElement === this._mainCont.current) { + e.stopPropagation(); + } } - render() { - return (
-
- {this.visibleElementWrapper} -
-
-
- {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => - )} -
-
e.stopPropagation()} - style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}> - -
- - - -
); + @action + createTextAnnotation = (sel: Selection, selRange: Range) => { + if (this._mainCont.current) { + let boundingRect = this._mainCont.current.getBoundingClientRect(); + let clientRects = selRange.getClientRects(); + for (let i = 0; i < clientRects.length; i++) { + let rect = clientRects.item(i); + if (rect/* && rect.width !== this._mainCont.current.getBoundingClientRect().width && rect.height !== this._mainCont.current.getBoundingClientRect().height / this.props.pdf.numPages*/) { + let scaleY = this._mainCont.current.offsetHeight / boundingRect.height; + let scaleX = this._mainCont.current.offsetWidth / boundingRect.width; + if (rect.width !== this._mainCont.current.clientWidth) { + let annoBox = document.createElement("div"); + annoBox.className = "pdfPage-annotationBox"; + // transforms the positions from screen onto the pdf div + annoBox.style.top = ((rect.top - boundingRect.top) * scaleY + this._mainCont.current.scrollTop).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * scaleX).toString(); + annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width).toString(); + annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height).toString(); + this.createAnnotation(annoBox, this.getPageFromScroll(rect.top)); + } + } + } + } + let text = selRange.cloneContents().textContent; + text && this.setSelectionText(text); + + // clear selection + if (sel.empty) { // Chrome + sel.empty(); + } else if (sel.removeAllRanges) { // Firefox + sel.removeAllRanges(); + } } -} -export enum AnnotationTypes { Region } + @action + onSelectEnd = (e: PointerEvent): void => { + if (this._marqueeing) { + if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { + let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox"); + if (marquees && marquees.length) { // make a copy of the marquee + let copy = document.createElement("div"); + let marquee = marquees[0] as HTMLDivElement; + let style = marquee.style; + copy.style.left = style.left; + copy.style.top = style.top; + copy.style.width = style.width; + copy.style.height = style.height; + copy.style.border = style.border; + copy.style.opacity = style.opacity; + copy.className = "pdfPage-annotationBox"; + this.createAnnotation(copy, this.getPageFromScroll(this._marqueeY)); + marquee.style.opacity = "0"; + } + + if (!e.ctrlKey) { + PDFMenu.Instance.Status = "snippet"; + PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight }; + } + PDFMenu.Instance.jumpTo(e.clientX, e.clientY); + } + + this._marqueeHeight = this._marqueeWidth = 0; + } + else { + let sel = window.getSelection(); + if (sel && sel.type === "Range") { + let selRange = sel.getRangeAt(0); + this.createTextAnnotation(sel, selRange); + PDFMenu.Instance.jumpTo(e.clientX, e.clientY); + } + } -class SimpleLinkService { - _viewer: PDFViewer; - _pdfjsView: any; + if (PDFMenu.Instance.Highlighting) { + this.highlight(undefined, "goldenrod"); + } + else { + PDFMenu.Instance.StartDrag = this.startDrag; + PDFMenu.Instance.Highlight = this.highlight; + } + document.removeEventListener("pointermove", this.onSelectMove); + document.removeEventListener("pointerup", this.onSelectEnd); + } - constructor(viewer: PDFViewer) { - this._viewer = viewer; + @action + highlight = (targetDoc: Doc | undefined, color: string) => { + // creates annotation documents for current highlights + let annotationDoc = this.makeAnnotationDocument(targetDoc, color, false); + Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); + return annotationDoc; } - setPDFJSview(v: any) { this._pdfjsView = v; } - navigateTo() { } + /** + * This is temporary for creating annotations from highlights. It will + * start a drag event and create or put the necessary info into the drag event. + */ + @action + startDrag = (e: PointerEvent, ele: HTMLElement): void => { + e.preventDefault(); + e.stopPropagation(); + let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); + targetDoc.targetPage = this.getPageFromScroll(this._marqueeY); + let annotationDoc = this.highlight(undefined, "red"); + annotationDoc.linkedToDoc = false; + let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); + DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { + handlers: { + dragComplete: () => { + if (!annotationDoc.linkedToDoc) { + let annotations = DocListCast(annotationDoc.annotations); + annotations && annotations.forEach(anno => anno.target = targetDoc); + DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`); + } + } + }, + hideSource: false + }); + } - getDestinationHash() { return "#"; } + createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => { + let view = Doc.MakeAlias(this.props.Document); + let data = Doc.MakeDelegate(Doc.GetProto(this.props.Document)); + data.title = StrCast(data.title) + "_snippet"; + view.proto = data; + view.nativeHeight = marquee.height; + view.height = (this.props.Document[WidthSym]() / NumCast(this.props.Document.nativeWidth)) * marquee.height; + view.nativeWidth = this.props.Document.nativeWidth; + view.startY = marquee.top; + view.width = this.props.Document[WidthSym](); + DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0); + } - getAnchorUrl() { return "#"; } + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. + @action.bound + moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { + return true; + } + return this.removeDocument(doc) ? addDocument(doc) : false; + } - setHash() { } - isPageVisible(page: number) { return true; } + @action.bound + removeDocument(doc: Doc): boolean { + //TODO This won't create the field if it doesn't already exist + let targetDataDoc = this.props.fieldExtensionDoc; + let targetField = this.props.fieldExt; + let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + index !== -1 && value.splice(index, 1); + return true; + } + scrollXf = () => { + return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._mainCont.current.scrollTop) : this.props.ScreenToLocalTransform(); + } + setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => { + this._setPreviewCursor = func; + } + onClick = (e: React.MouseEvent) => { + this._setPreviewCursor && + e.button === 0 && + Math.abs(e.clientX - this._downX) < 3 && + Math.abs(e.clientY - this._downY) < 3 && + this._setPreviewCursor(e.clientX, e.clientY, false); + } + whenActiveChanged = (isActive: boolean) => { + this._isChildActive = isActive; + this.props.whenActiveChanged(isActive); + } + active = () => { + return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; + } - executeNamedAction() { } + getCoverImage = () => { + if (!this.props.Document[HeightSym]()) { + setTimeout(() => { + this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; + this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width; + }, 0); + } + let nativeWidth = NumCast(this.props.Document.nativeWidth); + let nativeHeight = NumCast(this.props.Document.nativeHeight); + return this._showWaiting = false)} + style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />; + } - cachePageRef() { } - get pagesCount() { return this._viewer._pdfViewer.pagesCount; } + @action + onZoomWheel = (e: React.WheelEvent) => { + e.stopPropagation(); + if (e.ctrlKey) { + let curScale = Number(this.pdfViewer.currentScaleValue); + this.pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000)); + this._zoomed = Number(this.pdfViewer.currentScaleValue); + } + } - get page() { return NumCast(this._viewer.props.Document.curPage); } - set page(p: number) { - this._pdfjsView._ensurePdfPageLoaded(this._pdfjsView._pages[p - 1]).then(() => { - this._pdfjsView.renderingQueue.renderView(this._pdfjsView._pages[p - 1]); - if (this._viewer.props.GoToPage) - this._viewer.props.GoToPage(p); - }); + @computed get annotationLayer() { + return
+ {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => + )} +
; + } + @computed get pdfViewerDiv() { + return
; } + @computed get standinViews() { + return <> + {this._showCover ? this.getCoverImage() : (null)} + {this._showWaiting ? : (null)} + ; + } + marqueeWidth = () => this._marqueeWidth; + marqueeHeight = () => this._marqueeHeight; + marqueeX = () => this._marqueeX; + marqueeY = () => this._marqueeY; + marqueeing = () => this._marqueeing; + render() { + trace(); + return (
+ {this.pdfViewerDiv} + +
+ {this.annotationLayer} + NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))} + PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.props.Document.nativeWidth)} + focus={emptyFunction} + isSelected={this.props.isSelected} + select={emptyFunction} + active={this.active} + ContentScaling={returnOne} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }} + CollectionView={this.props.ContainingCollectionView} + ScreenToLocalTransform={this.scrollXf} + ruleProvider={undefined} + renderDepth={this.props.renderDepth + 1} + ContainingCollectionDoc={this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document} + chromeCollapsed={true}> + +
+ {this.standinViews} +
); + } +} + +interface PdfViewerMarqueeProps { + isMarqueeing: () => boolean; + width: () => number; + height: () => number; + x: () => number; + y: () => number; +} + +@observer +class PdfViewerMarquee extends React.Component { + render() { + return !this.props.isMarqueeing() ? (null) :
+
; + } +} - get rotation() { return 0; } - set rotation(value: any) { } -} \ No newline at end of file +export enum AnnotationTypes { Region } diff --git a/src/client/views/pdf/Page.scss b/src/client/views/pdf/Page.scss deleted file mode 100644 index d8034b4b4..000000000 --- a/src/client/views/pdf/Page.scss +++ /dev/null @@ -1,36 +0,0 @@ - -.pdfViewer-text { - .page { - position: relative; - } -} -.pdfPage-cont { - position: relative; - - .pdfPage-canvasContainer { - position: absolute; - } - - .pdfPage-dragAnnotationBox { - position: absolute; - background-color: transparent; - opacity: 0.1; - } - - .pdfPage-textLayer { - position: absolute; - width: 100%; - height: 100%; - div { - user-select: text; - } - span { - color: transparent; - position: absolute; - white-space: pre; - cursor: text; - -webkit-transform-origin: 0% 0%; - transform-origin: 0% 0%; - } - } -} \ No newline at end of file diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx deleted file mode 100644 index 533247170..000000000 --- a/src/client/views/pdf/Page.tsx +++ /dev/null @@ -1,293 +0,0 @@ -import { action, IReactionDisposer, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as Pdfjs from "pdfjs-dist"; -import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Docs, DocUtils } from "../../documents/Documents"; -import { DragManager } from "../../util/DragManager"; -import PDFMenu from "./PDFMenu"; -import { scale } from "./PDFViewer"; -import "./Page.scss"; -import React = require("react"); - - -interface IPageProps { - size: { width: number, height: number }; - pdf: Pdfjs.PDFDocumentProxy; - name: string; - numPages: number; - page: number; - pageLoaded: (page: Pdfjs.PDFPageViewport) => void; - fieldExtensionDoc: Doc; - Document: Doc; - renderAnnotations: (annotations: Doc[], removeOld: boolean) => void; - sendAnnotations: (annotations: HTMLDivElement[], page: number) => void; - createAnnotation: (div: HTMLDivElement, page: number) => void; - makeAnnotationDocuments: (doc: Doc | undefined, color: string, linkTo: boolean) => Doc; - getScrollFromPage: (page: number) => number; - setSelectionText: (text: string) => void; -} - -@observer -export default class Page extends React.Component { - @observable private _state: "N/A" | "rendering" = "N/A"; - @observable private _width: number = this.props.size.width; - @observable private _height: number = this.props.size.height; - @observable private _page: Opt; - @observable private _currPage: number = this.props.page + 1; - @observable private _marqueeX: number = 0; - @observable private _marqueeY: number = 0; - @observable private _marqueeWidth: number = 0; - @observable private _marqueeHeight: number = 0; - - private _canvas: React.RefObject = React.createRef(); - private _textLayer: React.RefObject = React.createRef(); - private _marquee: React.RefObject = React.createRef(); - private _marqueeing: boolean = false; - private _reactionDisposer?: IReactionDisposer; - private _startY: number = 0; - private _startX: number = 0; - - componentDidMount = (): void => this.loadPage(this.props.pdf); - - componentDidUpdate = (): void => this.loadPage(this.props.pdf); - - componentWillUnmount = (): void => this._reactionDisposer && this._reactionDisposer(); - - loadPage = (pdf: Pdfjs.PDFDocumentProxy): void => { - pdf.getPage(this._currPage).then(page => this.renderPage(page)); - } - - @action - renderPage = (page: Pdfjs.PDFPageProxy): void => { - // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes - if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) { - this._state = "rendering"; - let viewport = page.getViewport(scale); - this._canvas.current.width = this._width = viewport.width; - this._canvas.current.height = this._height = viewport.height; - this.props.pageLoaded(viewport); - let ctx = this._canvas.current.getContext("2d"); - if (ctx) { - //@ts-ignore - page.render({ canvasContext: ctx, viewport: viewport, enableWebGL: true }); // renders the page onto the canvas context - page.getTextContent().then(res => // renders text onto the text container - //@ts-ignore - Pdfjs.renderTextLayer({ - textContent: res, - container: this._textLayer.current, - viewport: viewport - })); - - this._page = page; - } - } - } - - @action - highlight = (targetDoc: Doc | undefined, color: string) => { - // creates annotation documents for current highlights - let annotationDoc = this.props.makeAnnotationDocuments(targetDoc, color, false); - Doc.AddDocToList(this.props.fieldExtensionDoc, "annotations", annotationDoc); - return annotationDoc; - } - - /** - * This is temporary for creating annotations from highlights. It will - * start a drag event and create or put the necessary info into the drag event. - */ - @action - startDrag = (e: PointerEvent, ele: HTMLElement): void => { - e.preventDefault(); - e.stopPropagation(); - if (this._textLayer.current) { - let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" }); - targetDoc.targetPage = this.props.page; - let annotationDoc = this.highlight(undefined, "red"); - annotationDoc.linkedToDoc = false; - let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); - DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { - handlers: { - dragComplete: async () => { - if (!BoolCast(annotationDoc.linkedToDoc)) { - let annotations = await DocListCastAsync(annotationDoc.annotations); - annotations && annotations.forEach(anno => anno.target = targetDoc); - DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`); - } - } - }, - hideSource: false - }); - } - } - - // cleans up events and boolean - endDrag = (e: PointerEvent): void => { - e.stopPropagation(); - } - - createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => { - let view = Doc.MakeAlias(this.props.Document); - let data = Doc.MakeDelegate(Doc.GetProto(this.props.Document)); - data.title = StrCast(data.title) + "_snippet"; - view.proto = data; - view.nativeHeight = marquee.height; - view.height = (this.props.Document[WidthSym]() / NumCast(this.props.Document.nativeWidth)) * marquee.height; - view.nativeWidth = this.props.Document.nativeWidth; - view.startY = marquee.top + this.props.getScrollFromPage(this.props.page); - view.width = this.props.Document[WidthSym](); - DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0); - } - - @action - onPointerDown = (e: React.PointerEvent): void => { - // if alt+left click, drag and annotate - if (NumCast(this.props.Document.scale, 1) !== 1) return; - if (!e.altKey && e.button === 0) { - PDFMenu.Instance.StartDrag = this.startDrag; - PDFMenu.Instance.Highlight = this.highlight; - PDFMenu.Instance.Snippet = this.createSnippet; - PDFMenu.Instance.Status = "pdf"; - PDFMenu.Instance.fadeOut(true); - if (e.target && (e.target as any).parentElement === this._textLayer.current) { - e.stopPropagation(); - if (!e.ctrlKey) { - this.props.sendAnnotations([], -1); - } - } - else { - // set marquee x and y positions to the spatially transformed position - if (this._textLayer.current) { - let boundingRect = this._textLayer.current.getBoundingClientRect(); - this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width); - this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height); - } - this._marqueeing = true; - this._marquee.current && (this._marquee.current.style.opacity = "0.2"); - this.props.sendAnnotations([], -1); - } - document.removeEventListener("pointermove", this.onSelectStart); - document.addEventListener("pointermove", this.onSelectStart); - document.removeEventListener("pointerup", this.onSelectEnd); - document.addEventListener("pointerup", this.onSelectEnd); - } - } - - @action - onSelectStart = (e: PointerEvent): void => { - if (this._marqueeing && this._textLayer.current) { - // transform positions and find the width and height to set the marquee to - let boundingRect = this._textLayer.current.getBoundingClientRect(); - this._marqueeWidth = ((e.clientX - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)) - this._startX; - this._marqueeHeight = ((e.clientY - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)) - this._startY; - this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth); - this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight); - this._marqueeWidth = Math.abs(this._marqueeWidth); - e.stopPropagation(); - e.preventDefault(); - } - else if (e.target && (e.target as any).parentElement === this._textLayer.current) { - e.stopPropagation(); - } - } - - @action - onSelectEnd = (e: PointerEvent): void => { - if (this._marqueeing) { - this._marqueeing = false; - if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { - if (this._marquee.current) { // make a copy of the marquee - let copy = document.createElement("div"); - let style = this._marquee.current.style; - copy.style.left = style.left; - copy.style.top = style.top; - copy.style.width = style.width; - copy.style.height = style.height; - copy.style.border = style.border; - copy.style.opacity = style.opacity; - copy.className = "pdfPage-annotationBox"; - this.props.createAnnotation(copy, this.props.page); - this._marquee.current.style.opacity = "0"; - } - - if (!e.ctrlKey) { - PDFMenu.Instance.Status = "snippet"; - PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight }; - } - PDFMenu.Instance.jumpTo(e.clientX, e.clientY); - } - - this._marqueeHeight = this._marqueeWidth = 0; - } - else { - let sel = window.getSelection(); - if (sel && sel.type === "Range") { - let selRange = sel.getRangeAt(0); - this.createTextAnnotation(sel, selRange); - PDFMenu.Instance.jumpTo(e.clientX, e.clientY); - } - } - - if (PDFMenu.Instance.Highlighting) { - this.highlight(undefined, "goldenrod"); - } - else { - PDFMenu.Instance.StartDrag = this.startDrag; - PDFMenu.Instance.Highlight = this.highlight; - } - document.removeEventListener("pointermove", this.onSelectStart); - document.removeEventListener("pointerup", this.onSelectEnd); - } - - @action - createTextAnnotation = (sel: Selection, selRange: Range) => { - if (this._textLayer.current) { - let boundingRect = this._textLayer.current.getBoundingClientRect(); - let clientRects = selRange.getClientRects(); - for (let i = 0; i < clientRects.length; i++) { - let rect = clientRects.item(i); - if (rect && rect.width !== this._textLayer.current.getBoundingClientRect().width && rect.height !== this._textLayer.current.getBoundingClientRect().height) { - let annoBox = document.createElement("div"); - annoBox.className = "pdfPage-annotationBox"; - // transforms the positions from screen onto the pdf div - annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString(); - annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString(); - annoBox.style.width = (rect.width * this._textLayer.current.offsetWidth / boundingRect.width).toString(); - annoBox.style.height = (rect.height * this._textLayer.current.offsetHeight / boundingRect.height).toString(); - this.props.createAnnotation(annoBox, this.props.page); - } - } - } - let text = selRange.cloneContents().textContent; - text && this.props.setSelectionText(text); - - // clear selection - if (sel.empty) { // Chrome - sel.empty(); - } else if (sel.removeAllRanges) { // Firefox - sel.removeAllRanges(); - } - } - - doubleClick = (e: React.MouseEvent) => { - if (e.target && (e.target as any).parentElement === this._textLayer.current) { - // do something to select the paragraph ideally - } - } - - render() { - return ( -
- -
-
-
-
); - } -} \ No newline at end of file diff --git a/src/client/views/presentationview/PresentationModeMenu.tsx b/src/client/views/presentationview/PresentationModeMenu.tsx index 4de8da587..0dd2b7731 100644 --- a/src/client/views/presentationview/PresentationModeMenu.tsx +++ b/src/client/views/presentationview/PresentationModeMenu.tsx @@ -21,10 +21,12 @@ export interface PresModeMenuProps { export default class PresModeMenu extends React.Component { @observable private _top: number = 20; - @observable private _right: number = 0; + @observable private _left: number = window.innerWidth - 160; @observable private _opacity: number = 1; @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; + private _offsetY: number = 0; + private _offsetX: number = 0; private _mainCont: React.RefObject = React.createRef(); @@ -35,8 +37,8 @@ export default class PresModeMenu extends React.Component { */ @action dragging = (e: PointerEvent) => { - this._right -= e.movementX; - this._top += e.movementY; + this._left = e.pageX - this._offsetX; + this._top = e.pageY - this._offsetY; e.stopPropagation(); e.preventDefault(); @@ -63,6 +65,9 @@ export default class PresModeMenu extends React.Component { document.removeEventListener("pointerup", this.dragEnd); document.addEventListener("pointerup", this.dragEnd); + this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX; + this._offsetY = e.nativeEvent.offsetY; + e.stopPropagation(); e.preventDefault(); } @@ -82,7 +87,7 @@ export default class PresModeMenu extends React.Component { render() { return (
+ style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}> {this.renderPlayPauseButton()} diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index 79f87f4ac..3baedce4b 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -2,39 +2,12 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { DocServer } from '../client/DocServer'; import { Doc } from '../new_fields/Doc'; +import * as Pdfjs from "pdfjs-dist"; +import "pdfjs-dist/web/pdf_viewer.css"; +import { Utils } from '../Utils'; +const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const protoId = "protoDoc"; const delegateId = "delegateDoc"; class Test extends React.Component { - onCreateClick = () => { - const proto = new Doc(protoId, true); - const delegate = Doc.MakeDelegate(proto, delegateId); - } - - onReadClick = async () => { - console.log("reading"); - const docs = await DocServer.GetRefFields([delegateId, protoId]); - console.log("done"); - console.log(docs); - } - - onDeleteClick = () => { - DocServer.DeleteDocuments([protoId, delegateId]); - } - - render() { - return ( -
- - - -
- ); - } } - -DocServer.init(window.location.protocol, window.location.hostname, 4321, "test"); -ReactDOM.render( - , - document.getElementById('root') -); \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index fef3b8cc5..605877efa 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -13,6 +13,7 @@ import { listSpec } from "./Schema"; import { ComputedField } from "./ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types"; import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; +import { intersectRect } from "../Utils"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -343,7 +344,7 @@ export namespace Doc { let list = Cast(target[key], listSpec(Doc)); if (list) { if (allowDuplicates !== true) { - let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1); + let pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); if (pind !== -1) { list.splice(pind, 1); } @@ -614,6 +615,18 @@ export namespace Doc { }), 0); } + export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) { + var x2 = NumCast(doc2.x) - clusterDistance; + var y2 = NumCast(doc2.y) - clusterDistance; + var w2 = NumCast(doc2.width) + clusterDistance; + var h2 = NumCast(doc2.height) + clusterDistance; + var x = NumCast(doc.x) - clusterDistance; + var y = NumCast(doc.y) - clusterDistance; + var w = NumCast(doc.width) + clusterDistance; + var h = NumCast(doc.height) + clusterDistance; + return doc.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); + } + export function isBrushedHighlightedDegree(doc: Doc) { if (Doc.IsHighlighted(doc)) { return 3; -- cgit v1.2.3-70-g09d2 From 58ba643f2642432ca6041f5d348aadf005323b73 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Wed, 22 Apr 2020 20:36:06 -0700 Subject: snapping! --- src/client/util/DragManager.ts | 10 ++++++---- src/client/views/MainView.tsx | 10 ++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 22 +++++++--------------- 3 files changed, 23 insertions(+), 19 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index b0cabfaad..02d9f7200 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -19,6 +19,7 @@ import { DateField } from "../../new_fields/DateField"; import { DocumentView } from "../views/nodes/DocumentView"; import { UndoManager } from "./UndoManager"; import { PointData } from "../../new_fields/InkField"; +import { MainView } from "../views/MainView"; export type dropActionType = "place" | "alias" | "copy" | undefined; export function SetupDrag( @@ -74,8 +75,8 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; - let horizSnapLines: number[]; - let vertSnapLines: number[]; + export let horizSnapLines: number[]; + export let vertSnapLines: number[]; export function Root() { const root = document.getElementById("root"); @@ -284,9 +285,12 @@ export namespace DragManager { StartDrag([ele], {}, downX, downY); } + @action export function SetSnapLines(horizLines: number[], vertLines: number[]) { horizSnapLines = horizLines; vertSnapLines = vertLines; + MainView.Instance._hLines = horizLines; + MainView.Instance._vLines = vertLines; } function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { @@ -373,8 +377,6 @@ export namespace DragManager { const yFromTop = downY - elesCont.top; const xFromRight = elesCont.right - downX; const yFromBottom = elesCont.bottom - downY; - console.log(elesCont); - console.log(xFromLeft, yFromTop); const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 40cabcf83..1f88410b8 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -42,6 +42,7 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { ScriptField } from '../../new_fields/ScriptField'; +import { DragManager } from '../util/DragManager'; @observer export class MainView extends React.Component { @@ -563,6 +564,9 @@ export class MainView extends React.Component { return this._mainViewRef; } + @observable public _hLines: any; + @observable public _vLines: any; + render() { return (
@@ -580,6 +584,12 @@ export class MainView extends React.Component { +
+ + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} + +
); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c4c37141b..ef49970e2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1099,7 +1099,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u if (SelectionManager.GetIsDragging()) { const size = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; - console.log(selRect); const selection: Doc[] = []; this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { const layoutDoc = Doc.Layout(doc); @@ -1140,17 +1139,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u const vertLines: number[] = []; selection.forEach(doc => { const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x) - selRect.left; - const y = NumCast(doc.y) - selRect.top; + const x = NumCast(doc.x); + const y = NumCast(doc.y); const w = NumCast(layoutDoc._width); const h = NumCast(layoutDoc._height); - // const s = this._mainCont!.getBoundingClientRect().width / selRect.width; - // const tLFromCorner = [s * x, s * y]; - const topLeft = this.getLocalTransform().inverse().transformDirection(x, y); - console.log(topLeft); - const topLeftInScreen = [this._mainCont!.getBoundingClientRect().left + topLeft[0], this._mainCont!.getBoundingClientRect().top + topLeft[1]]; - const docSize = this.getLocalTransform().inverse().transformDirection(w, h); - console.log(topLeftInScreen); + const topLeftInScreen = this.getTransform().inverse().transformPoint(x, y); + const docSize = this.getTransform().inverse().transformDirection(w, h); horizLines.push(topLeftInScreen[1]); // top line horizLines.push(topLeftInScreen[1] + docSize[1]); // bottom line horizLines.push(topLeftInScreen[1] + docSize[1] / 2); // horiz center line @@ -1158,11 +1152,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u vertLines.push(topLeftInScreen[0] + docSize[0]);// right line vertLines.push(topLeftInScreen[0] + docSize[0] / 2);// vert center line }); - // console.log(horizLines, vertLines); - // this._hLines = horizLines; - // this._vLines = vertLines; DragManager.SetSnapLines(horizLines, vertLines); } + e.stopPropagation(); } @observable private _hLines: number[] | undefined; @@ -1254,12 +1246,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u }}>
-
+ {/*
{this._hLines?.map(l => )} {this._vLines?.map(l => )} -
+
*/}
; } } -- cgit v1.2.3-70-g09d2 From 69de8c235a6580ac222ef3f5b31746f6bc144659 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 28 Apr 2020 10:48:16 -0400 Subject: rearranged text files --- .../apis/google_docs/GooglePhotosClientUtils.ts | 2 +- src/client/documents/Documents.ts | 2 +- src/client/util/DashDocCommentView.tsx | 95 -- src/client/util/DashDocView.tsx | 269 ---- src/client/util/DashFieldView.scss | 36 - src/client/util/DashFieldView.tsx | 211 ---- src/client/util/FootnoteView.tsx | 162 --- src/client/util/ImageResizeView.tsx | 138 --- src/client/util/ParagraphNodeSpec.ts | 143 --- src/client/util/ProsemirrorExampleTransfer.ts | 241 ---- src/client/util/RichTextMenu.scss | 121 -- src/client/util/RichTextMenu.tsx | 875 ------------- src/client/util/RichTextRules.ts | 4 +- src/client/util/RichTextSchema.tsx | 718 ----------- src/client/util/SummaryView.tsx | 81 -- src/client/util/TooltipTextMenu.scss | 372 ------ src/client/util/marks_rts.ts | 296 ----- src/client/util/nodes_rts.ts | 264 ---- src/client/util/prosemirrorPatches.js | 139 --- src/client/util/schema_rts.ts | 26 - src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/MainView.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.scss | 265 ---- src/client/views/nodes/FormattedTextBox.tsx | 1306 -------------------- .../views/nodes/FormattedTextBoxComment.scss | 33 - src/client/views/nodes/FormattedTextBoxComment.tsx | 236 ---- .../nodes/formattedText/DashDocCommentView.tsx | 95 ++ .../views/nodes/formattedText/DashDocView.tsx | 269 ++++ .../views/nodes/formattedText/DashFieldView.scss | 36 + .../views/nodes/formattedText/DashFieldView.tsx | 211 ++++ .../views/nodes/formattedText/FootnoteView.tsx | 162 +++ .../nodes/formattedText/FormattedTextBox.scss | 265 ++++ .../views/nodes/formattedText/FormattedTextBox.tsx | 1303 +++++++++++++++++++ .../formattedText/FormattedTextBoxComment.scss | 33 + .../formattedText/FormattedTextBoxComment.tsx | 236 ++++ .../views/nodes/formattedText/ImageResizeView.tsx | 138 +++ .../views/nodes/formattedText/ParagraphNodeSpec.ts | 143 +++ .../formattedText/ProsemirrorExampleTransfer.ts | 241 ++++ .../views/nodes/formattedText/RichTextMenu.scss | 121 ++ .../views/nodes/formattedText/RichTextMenu.tsx | 875 +++++++++++++ .../views/nodes/formattedText/RichTextRules.ts | 319 +++++ .../views/nodes/formattedText/RichTextSchema.tsx | 718 +++++++++++ .../views/nodes/formattedText/SummaryView.tsx | 81 ++ .../views/nodes/formattedText/TooltipTextMenu.scss | 372 ++++++ src/client/views/nodes/formattedText/marks_rts.ts | 296 +++++ src/client/views/nodes/formattedText/nodes_rts.ts | 264 ++++ .../nodes/formattedText/prosemirrorPatches.js | 139 +++ src/client/views/nodes/formattedText/schema_rts.ts | 26 + src/mobile/MobileInterface.tsx | 2 +- src/new_fields/RichTextUtils.ts | 4 +- .../authentication/models/current_user_utils.ts | 2 +- 57 files changed, 6359 insertions(+), 6043 deletions(-) delete mode 100644 src/client/util/DashDocCommentView.tsx delete mode 100644 src/client/util/DashDocView.tsx delete mode 100644 src/client/util/DashFieldView.scss delete mode 100644 src/client/util/DashFieldView.tsx delete mode 100644 src/client/util/FootnoteView.tsx delete mode 100644 src/client/util/ImageResizeView.tsx delete mode 100644 src/client/util/ParagraphNodeSpec.ts delete mode 100644 src/client/util/ProsemirrorExampleTransfer.ts delete mode 100644 src/client/util/RichTextMenu.scss delete mode 100644 src/client/util/RichTextMenu.tsx delete mode 100644 src/client/util/RichTextSchema.tsx delete mode 100644 src/client/util/SummaryView.tsx delete mode 100644 src/client/util/TooltipTextMenu.scss delete mode 100644 src/client/util/marks_rts.ts delete mode 100644 src/client/util/nodes_rts.ts delete mode 100644 src/client/util/prosemirrorPatches.js delete mode 100644 src/client/util/schema_rts.ts delete mode 100644 src/client/views/nodes/FormattedTextBox.scss delete mode 100644 src/client/views/nodes/FormattedTextBox.tsx delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.scss delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.tsx create mode 100644 src/client/views/nodes/formattedText/DashDocCommentView.tsx create mode 100644 src/client/views/nodes/formattedText/DashDocView.tsx create mode 100644 src/client/views/nodes/formattedText/DashFieldView.scss create mode 100644 src/client/views/nodes/formattedText/DashFieldView.tsx create mode 100644 src/client/views/nodes/formattedText/FootnoteView.tsx create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.scss create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.tsx create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.scss create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx create mode 100644 src/client/views/nodes/formattedText/ImageResizeView.tsx create mode 100644 src/client/views/nodes/formattedText/ParagraphNodeSpec.ts create mode 100644 src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.scss create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.tsx create mode 100644 src/client/views/nodes/formattedText/RichTextRules.ts create mode 100644 src/client/views/nodes/formattedText/RichTextSchema.tsx create mode 100644 src/client/views/nodes/formattedText/SummaryView.tsx create mode 100644 src/client/views/nodes/formattedText/TooltipTextMenu.scss create mode 100644 src/client/views/nodes/formattedText/marks_rts.ts create mode 100644 src/client/views/nodes/formattedText/nodes_rts.ts create mode 100644 src/client/views/nodes/formattedText/prosemirrorPatches.js create mode 100644 src/client/views/nodes/formattedText/schema_rts.ts (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 8c0149a89..e3f801c46 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -10,7 +10,7 @@ import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/Share import { Utils } from "../../../Utils"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { Networking } from "../../Network"; -import { FormattedTextBox } from "../../views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox"; import GoogleAuthenticationManager from "../GoogleAuthenticationManager"; import Photos = require('googlephotos'); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c64916897..f96e3bcd1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,7 @@ import { CollectionView } from "../views/collections/CollectionView"; import { CollectionViewType } from "../views/collections/CollectionView"; import { AudioBox } from "../views/nodes/AudioBox"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { PDFBox } from "../views/nodes/PDFBox"; diff --git a/src/client/util/DashDocCommentView.tsx b/src/client/util/DashDocCommentView.tsx deleted file mode 100644 index e716fac53..000000000 --- a/src/client/util/DashDocCommentView.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { baseKeymap, toggleMark } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { keymap } from "prosemirror-keymap"; -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; -import { StepMap } from "prosemirror-transform"; -import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { List } from "../../new_fields/List"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils"; -import { DocServer } from "../DocServer"; - -import React = require("react"); - -import { schema } from "./schema_rts"; - -interface IDashDocCommentView { - node: any; - view: any; - getPos: any; -} - -export class DashDocCommentView extends React.Component{ - constructor(props: IDashDocCommentView) { - super(props); - } - - targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor - for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) { - const m = this.props.view.state.doc.nodeAt(i); - if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) { - return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; - } - } - const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" }); - this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc)); - setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0); - return undefined; - } - - onPointerDownCollapse = (e: any) => e.stopPropagation(); - - onPointerUpCollapse = (e: any) => { - const target = this.targetNode(); - if (target) { - const expand = target.hidden; - const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); - this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs - setTimeout(() => { - expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { } - }, 0); - } - e.stopPropagation(); - } - - onPointerEnterCollapse = (e: any) => { - DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); - e.preventDefault(); - e.stopPropagation(); - } - - onPointerLeaveCollapse = (e: any) => { - DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); - e.preventDefault(); - e.stopPropagation(); - } - - render() { - - const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid; - - return ( - - - - ); - } -} \ No newline at end of file diff --git a/src/client/util/DashDocView.tsx b/src/client/util/DashDocView.tsx deleted file mode 100644 index 39809187f..000000000 --- a/src/client/util/DashDocView.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { IReactionDisposer, reaction } from "mobx"; -import { NodeSelection } from "prosemirror-state"; -import { Doc, HeightSym, WidthSym } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs } from "../documents/Documents"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { Transform } from "./Transform"; -import React = require("react"); - -interface IDashDocView { - node: any; - view: any; - getPos: any; - tbox?: FormattedTextBox; - self: any; -} - -export class DashDocView extends React.Component { - - _dashDoc: Doc | undefined; - _reactionDisposer: IReactionDisposer | undefined; - _renderDisposer: IReactionDisposer | undefined; - _textBox: FormattedTextBox; - _finalLayout: any; - _resolvedDataDoc: any; - - - // constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - - constructor(props: IDashDocView) { - super(props); - - const node = this.props.node; - this._textBox = this.props.tbox as FormattedTextBox; - - const alias = node.attrs.alias; - const docid = node.attrs.docid || this._textBox.props.Document[Id]; - - DocServer.GetRefField(docid + alias).then(async dashDoc => { - if (!(dashDoc instanceof Doc)) { - alias && DocServer.GetRefField(docid).then(async dashDocBase => { - if (dashDocBase instanceof Doc) { - const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); - aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); - this._dashDoc = aliasedDoc; - // self.doRender(aliasedDoc, removeDoc, node, view, getPos); - } - }); - } else { - this._dashDoc = dashDoc; - // self.doRender(dashDoc, removeDoc, node, view, getPos); - } - }); - - this.onPointerLeave = this.onPointerLeave.bind(this); - this.onPointerEnter = this.onPointerEnter.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - this.onKeyPress = this.onKeyPress.bind(this); - this.onKeyUp = this.onKeyUp.bind(this); - this.onWheel = this.onWheel.bind(this); - } - /* #region Internal functions */ - - removeDoc = () => { - const view = this.props.view; - const pos = this.props.getPos(); - const ns = new NodeSelection(view.state.doc.resolve(pos)); - view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); - return true; - } - - getDocTransform = () => { - const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement; - const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement); - return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); - } - contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; - - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target - - onKeyPress = (e: any) => { - e.stopPropagation(); - } - onWheel = (e: any) => { - e.preventDefault(); - } - onKeyUp = (e: any) => { - e.stopPropagation(); - } - onKeyDown = (e: any) => { - e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { - e.preventDefault(); - } - } - onPointerLeave = () => { - const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = ""; - } - } - onPointerEnter = () => { - const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = "orange"; - } - } - /*endregion*/ - - componentWillMount = () => { - this._reactionDisposer?.(); - } - - componentDidUpdate = () => { - - this._renderDisposer?.(); - this._renderDisposer = reaction(() => { - - const dashDoc = this._dashDoc as Doc; - const dashLayoutDoc = Doc.Layout(dashDoc); - const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey); - - if (finalLayout) { - if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { - finalLayout.rootDocument = dashDoc.aliasOf; - } - const layoutKey = StrCast(finalLayout.layoutKey); - const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; - if (finalLayout !== dashDoc && finalKey) { - const finalLayoutField = finalLayout[finalKey]; - if (finalLayoutField instanceof ObjectField) { - finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); - } - } - this._finalLayout = finalLayout; - this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null); - return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; - } - }, - (res) => { - - if (res) { - this._finalLayout = res.finalLayout; - this._resolvedDataDoc = res.resolvedDataDoc; - - this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc), - } - }, - { fireImmediately: true }); - - } - - render() { - // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { - - const node = this.props.node; - const view = this.props.view; - const getPos = this.props.getPos; - - const spanStyle = { - width: this.props.node.props.width, - height: this.props.node.props.height, - position: 'absolute' as 'absolute', - display: 'inline-block' - }; - - - const outerStyle = { - position: "relative" as "relative", - textIndent: "0", - border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")), - width: this.props.node.props.width, - height: this.props.node.props.height, - display: this.props.node.props.hidden ? "none" : "inline-block", - float: this.props.node.props.float, - }; - - const dashDoc = this._dashDoc as Doc; - const self = this; - const dashLayoutDoc = Doc.Layout(dashDoc); - const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); - const resolvedDataDoc = this._resolvedDataDoc; //Added this - - if (!finalLayout) { - return
; - // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); - } else { - - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => - ({ - dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], - color: finalLayout.color - }), - ({ dim, color }) => { - spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px"; - spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px"; - outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); - }, { fireImmediately: true }); - - if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); - } catch (e) { - console.log(e); - } - } - - - //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { - // ReactDOM.unmountComponentAtNode(this._dashSpan); - - return ( - -
- - -
-
- ); - - } - } - -} \ No newline at end of file diff --git a/src/client/util/DashFieldView.scss b/src/client/util/DashFieldView.scss deleted file mode 100644 index 35ff9c1e6..000000000 --- a/src/client/util/DashFieldView.scss +++ /dev/null @@ -1,36 +0,0 @@ -.dashFieldView { - position: relative; - display: inline-block; - - .dashFieldView-enumerables { - width: 10px; - height: 10px; - position: relative; - display: inline-block; - background: dimGray; - } - .dashFieldView-fieldCheck { - min-width: 12px; - position: relative; - display: inline-block; - background-color: rgba(155, 155, 155, 0.24); - } - .dashFieldView-labelSpan { - position: relative; - display: inline-block; - font-size: small; - } - .dashFieldView-fieldSpan { - min-width: 20px; - margin-left: 2px; - margin-right: 5px; - position: relative; - display: inline-block; - background-color: rgba(155, 155, 155, 0.24); - span { - min-width: 100%; - display: inline-block; - } - } -} - \ No newline at end of file diff --git a/src/client/util/DashFieldView.tsx b/src/client/util/DashFieldView.tsx deleted file mode 100644 index cf09e0132..000000000 --- a/src/client/util/DashFieldView.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { IReactionDisposer, observable, runInAction, computed, action } from "mobx"; -import { Doc, DocListCast, Field } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../new_fields/Types"; -import { DocServer } from "../DocServer"; -import { CollectionViewType } from "../views/collections/CollectionView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import React = require("react"); -import * as ReactDOM from 'react-dom'; -import "./DashFieldView.scss"; -import { observer } from "mobx-react"; - - -export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; - } - destroy() { - ReactDOM.unmountComponentAtNode(this._fieldWrapper); - } - selectNode() { } - -} -interface IDashFieldViewInternal { - fieldKey: string; - docid: string; - view: any; - getPos: any; - tbox: FormattedTextBox; - width: number; - height: number; -} - -@observer -export class DashFieldViewInternal extends React.Component { - _reactionDisposer: IReactionDisposer | undefined; - _textBoxDoc: Doc; - _fieldKey: string; - _fieldStringRef = React.createRef(); - @observable _showEnumerables: boolean = false; - @observable _dashDoc: Doc | undefined; - - constructor(props: IDashFieldViewInternal) { - super(props); - this._fieldKey = this.props.fieldKey; - this._textBoxDoc = this.props.tbox.props.Document; - - if (this.props.docid) { - DocServer.GetRefField(this.props.docid). - then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); - } else { - this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc; - } - } - componentWillUnmount() { - this._reactionDisposer?.(); - } - - // set the display of the field's value (checkbox for booleans, span of text for strings) - @computed get fieldValueContent() { - if (this._dashDoc) { - const dashVal = this._dashDoc[this._fieldKey]; - const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal; - const boolVal = Cast(fval, "boolean", null); - const strVal = Field.toString(fval as Field) || ""; - - // field value is a boolean, so use a checkbox or similar widget to display it - if (boolVal === true || boolVal === false) { - return this._dashDoc![this._fieldKey] = e.target.checked} - />; - } - else // field value is a string, so display it as an editable span - { - // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't - // use React events. Essentially, React events occur after native events have been processed, so corresponding React events - // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. - return { - r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); - r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); - r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true)); - }}> - {strVal} - - } - } - } - - // we need to handle all key events on the input span or else they will propagate to prosemirror. - @action - fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { - if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. - e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); - this.updateText(span.textContent!, true); - e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view - } - if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(span); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected - } - e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror. - } - - @action - updateText = (nodeText: string, forceMatch: boolean) => { - this._showEnumerables = false; - if (nodeText) { - const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; - - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(this._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); - this._dashDoc![this._fieldKey] = modText; - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (nodeText.startsWith(":=")) { - this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); - } else if (nodeText.startsWith("=:=")) { - Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); - } else { - this._dashDoc![this._fieldKey] = newText; - } - }); - } - } - - // display a collection of all the enumerable values for this field - onPointerDownEnumerables = async (e: any) => { - e.stopPropagation(); - const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); - collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight"); - } - - - // clicking on the label creates a pivot view collection of all documents - // in the same collection. The pivot field is the fieldKey of this label - onPointerDownLabelSpan = (e: any) => { - e.stopPropagation(); - let container = this.props.tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } - if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias.viewType = CollectionViewType.Time; - let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); - if (!list) { - alias.schemaColumns = list = new List(); - } - list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = this._fieldKey; - this.props.tbox.props.addDocTab(alias, "onRight"); - } - } - - render() { - return
- - {this._fieldKey} - - -
- {this.fieldValueContent} -
- - {!this._showEnumerables ? (null) :
} - -
; - } -} \ No newline at end of file diff --git a/src/client/util/FootnoteView.tsx b/src/client/util/FootnoteView.tsx deleted file mode 100644 index ee21fb765..000000000 --- a/src/client/util/FootnoteView.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { EditorView } from "prosemirror-view"; -import { EditorState } from "prosemirror-state"; -import { keymap } from "prosemirror-keymap"; -import { baseKeymap, toggleMark } from "prosemirror-commands"; -import { schema } from "./schema_rts"; -import { redo, undo } from "prosemirror-history"; -import { StepMap } from "prosemirror-transform"; - -import React = require("react"); - -interface IFootnoteView { - innerView: any; - outerView: any; - node: any; - dom: any; - getPos: any; -} - -export class FootnoteView extends React.Component { - _innerView: any; - _node: any; - - constructor(props: IFootnoteView) { - super(props); - const node = this.props.node; - const outerView = this.props.outerView; - const _innerView = this.props.innerView; - const getPos = this.props.getPos; - } - - selectNode() { - const attrs = { ...this.props.node.attrs }; - attrs.visibility = true; - this.dom.classList.add("ProseMirror-selectednode"); - if (!this.props.innerView) this.open(); - } - - deselectNode() { - const attrs = { ...this.props.node.attrs }; - attrs.visibility = false; - this.dom.classList.remove("ProseMirror-selectednode"); - if (this.props.innerView) this.close(); - } - open() { - // Append a tooltip to the outer node - const tooltip = this.dom.appendChild(document.createElement("div")); - tooltip.className = "footnote-tooltip"; - // And put a sub-ProseMirror into that - this.props.innerView.defineProperty(new EditorView(tooltip, { - // You can use any node as an editor document - state: EditorState.create({ - doc: this.props.node, - plugins: [keymap(baseKeymap), - keymap({ - "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch), - "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch), - "Mod-b": toggleMark(schema.marks.strong) - }), - // new Plugin({ - // view(newView) { - // // TODO -- make this work with RichTextMenu - // // return FormattedTextBox.getToolTip(newView); - // } - // }) - ], - - }), - // This is the magic part - dispatchTransaction: this.dispatchInner.bind(this), - handleDOMEvents: { - pointerdown: ((view: any, e: PointerEvent) => { - // Kludge to prevent issues due to the fact that the whole - // footnote is node-selected (and thus DOM-selected) when - // the parent editor is focused. - e.stopPropagation(); - document.addEventListener("pointerup", this.ignore, true); - if (this.props.outerView.hasFocus()) this.props.innerView.focus(); - }) as any - } - })); - setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0); - } - - ignore = (e: PointerEvent) => { - e.stopPropagation(); - document.removeEventListener("pointerup", this.ignore, true); - } - - dispatchInner(tr: any) { - const { state, transactions } = this.props.innerView.state.applyTransaction(tr); - this.props.innerView.updateState(state); - - if (!tr.getMeta("fromOutside")) { - const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1); - for (const transaction of transactions) { - const steps = transaction.steps; - for (const step of steps) { - outerTr.step(step.map(offsetMap)); - } - } - if (outerTr.docChanged) this.props.outerView.dispatch(outerTr); - } - } - update(node: any) { - if (!node.sameMarkup(this.props.node)) return false; - this._node = node; //not sure - if (this.props.innerView) { - const state = this.props.innerView.state; - const start = node.content.findDiffStart(state.doc.content); - if (start !== null) { - let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); - const overlap = start - Math.min(endA, endB); - if (overlap > 0) { endA += overlap; endB += overlap; } - this.props.innerView.dispatch( - state.tr - .replace(start, endB, node.slice(start, endA)) - .setMeta("fromOutside", true)); - } - } - return true; - } - onPointerUp = (e: any) => { - this.toggle(e); - } - - toggle = (e: any) => { - e.preventDefault(); - if (this.props.innerView) this.close(); - else { - this.open(); - } - } - - close() { - this.props.innerView && this.props.innerView.destroy(); - this._innerView = null; - this.dom.textContent = ""; - } - - destroy() { - if (this.props.innerView) this.close(); - } - - stopEvent(event: any) { - return this.props.innerView && this.props.innerView.dom.contains(event.target); - } - - ignoreMutation() { return true; } - - - render() { - return ( -
-
- -
-
- ); - } -} diff --git a/src/client/util/ImageResizeView.tsx b/src/client/util/ImageResizeView.tsx deleted file mode 100644 index 4f66475fb..000000000 --- a/src/client/util/ImageResizeView.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { NodeSelection } from "prosemirror-state"; -import { Doc } from "../../new_fields/Doc"; -import { DocServer } from "../DocServer"; -import { DocumentManager } from "./DocumentManager"; -import React = require("react"); - -import { schema } from "./schema_rts"; - -interface IImageResizeView { - node: any; - view: any; - getPos: any; - addDocTab: any; -} - -export class ImageResizeView extends React.Component { - constructor(props: IImageResizeView) { - super(props); - } - - onClickImg = (e: any) => { - e.stopPropagation(); - e.preventDefault(); - if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) { - this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2)))); - } - } - - onPointerDownImg = (e: any) => { - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc => - (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document, - document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false)); - } - } - - onPointerDownHandle = (e: any) => { - e.preventDefault(); - e.stopPropagation(); - const elementImage = document.getElementById("imageId") as HTMLElement; - const wid = Number(getComputedStyle(elementImage).width.replace(/px/, "")); - const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, "")); - const startX = e.pageX; - const startWidth = parseFloat(this.props.node.attrs.width); - - const onpointermove = (e: any) => { - const elementOuter = document.getElementById("outerId") as HTMLElement; - - const currentX = e.pageX; - const diffInPx = currentX - startX; - elementOuter.style.width = `${startWidth + diffInPx}`; - elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`; - }; - - const onpointerup = () => { - document.removeEventListener("pointermove", onpointermove); - document.removeEventListener("pointerup", onpointerup); - const pos = this.props.view.state.selection.from; - const elementOuter = document.getElementById("outerId") as HTMLElement; - this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height })); - this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos)))); - }; - - document.addEventListener("pointermove", onpointermove); - document.addEventListener("pointerup", onpointerup); - } - - selectNode() { - const elementImage = document.getElementById("imageId") as HTMLElement; - const elementHandle = document.getElementById("handleId") as HTMLElement; - - elementImage.classList.add("ProseMirror-selectednode"); - elementHandle.style.display = ""; - } - - deselectNode() { - const elementImage = document.getElementById("imageId") as HTMLElement; - const elementHandle = document.getElementById("handleId") as HTMLElement; - - elementImage.classList.remove("ProseMirror-selectednode"); - elementHandle.style.display = "none"; - } - - - render() { - - const outerStyle = { - width: this.props.node.attrs.width, - height: this.props.node.attrs.height, - display: "inline-block", - overflow: "hidden", - float: this.props.node.attrs.float - }; - - const imageStyle = { - width: "100%", - }; - - const handleStyle = { - position: "absolute", - width: "20px", - heiht: "20px", - backgroundColor: "blue", - borderRadius: "15px", - display: "none", - bottom: "-10px", - right: "-10px" - - }; - - - - return ( -
- - - - - -
- ); - } -} \ No newline at end of file diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts deleted file mode 100644 index 0a3b68217..000000000 --- a/src/client/util/ParagraphNodeSpec.ts +++ /dev/null @@ -1,143 +0,0 @@ -import clamp from './clamp'; -import convertToCSSPTValue from './convertToCSSPTValue'; -import toCSSLineSpacing from './toCSSLineSpacing'; -import { Node, DOMOutputSpec } from 'prosemirror-model'; - -//import type { NodeSpec } from './Types'; -type NodeSpec = { - attrs?: { [key: string]: any }, - content?: string, - draggable?: boolean, - group?: string, - inline?: boolean, - name?: string, - parseDOM?: Array, - toDOM?: (node: any) => DOMOutputSpec, -}; - -// This assumes that every 36pt maps to one indent level. -export const INDENT_MARGIN_PT_SIZE = 36; -export const MIN_INDENT_LEVEL = 0; -export const MAX_INDENT_LEVEL = 7; -export const ATTRIBUTE_INDENT = 'data-indent'; - -export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']); - -const ALIGN_PATTERN = /(left|right|center|justify)/; - -// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js -// :: NodeSpec A plain paragraph textblock. Represented in the DOM -// as a `

` element. -const ParagraphNodeSpec: NodeSpec = { - attrs: { - align: { default: null }, - color: { default: null }, - id: { default: null }, - indent: { default: null }, - inset: { default: null }, - lineSpacing: { default: null }, - // TODO: Add UI to let user edit / clear padding. - paddingBottom: { default: null }, - // TODO: Add UI to let user edit / clear padding. - paddingTop: { default: null }, - }, - content: 'inline*', - group: 'block', - parseDOM: [{ tag: 'p', getAttrs }], - toDOM, -}; - -function getAttrs(dom: HTMLElement): Object { - const { - lineHeight, - textAlign, - marginLeft, - paddingTop, - paddingBottom, - } = dom.style; - - let align = dom.getAttribute('align') || textAlign || ''; - align = ALIGN_PATTERN.test(align) ? align : ""; - - let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10); - - if (!indent && marginLeft) { - indent = convertMarginLeftToIndentValue(marginLeft); - } - - indent = indent || MIN_INDENT_LEVEL; - - const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null; - - const id = dom.getAttribute('id') || ''; - return { align, indent, lineSpacing, paddingTop, paddingBottom, id }; -} - -function toDOM(node: Node): DOMOutputSpec { - const { - align, - indent, - inset, - lineSpacing, - paddingTop, - paddingBottom, - id, - } = node.attrs; - const attrs: { [key: string]: any } | null = {}; - - let style = ''; - if (align && align !== 'left') { - style += `text-align: ${align};`; - } - - if (lineSpacing) { - const cssLineSpacing = toCSSLineSpacing(lineSpacing); - style += - `line-height: ${cssLineSpacing};` + - // This creates the local css variable `--czi-content-line-height` - // that its children may apply. - `--czi-content-line-height: ${cssLineSpacing}`; - } - - if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) { - style += `padding-top: ${paddingTop};`; - } - - if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) { - style += `padding-bottom: ${paddingBottom};`; - } - - if (indent) { - style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`; - } - - if (inset) { - style += `margin-left: ${inset}; margin-right: ${inset};`; - } - - style && (attrs.style = style); - - if (indent) { - attrs[ATTRIBUTE_INDENT] = String(indent); - } - - if (id) { - attrs.id = id; - } - - return ['p', attrs, 0]; -} - -export const toParagraphDOM = toDOM; -export const getParagraphNodeAttrs = getAttrs; - -export function convertMarginLeftToIndentValue(marginLeft: string): number { - const ptValue = convertToCSSPTValue(marginLeft); - return clamp( - MIN_INDENT_LEVEL, - Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), - MAX_INDENT_LEVEL - ); -} - -export default ParagraphNodeSpec; \ No newline at end of file diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts deleted file mode 100644 index 356f20ce6..000000000 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { undoInputRule } from "prosemirror-inputrules"; -import { Schema } from "prosemirror-model"; -import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; -import { splitListItem, wrapInList, } from "prosemirror-schema-list"; -import { EditorState, Transaction, TextSelection } from "prosemirror-state"; -import { SelectionManager } from "./SelectionManager"; -import { Docs } from "../documents/Documents"; -import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types"; -import { Doc } from "../../new_fields/Doc"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { Id } from "../../new_fields/FieldSymbols"; - -const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; - -export type KeyMap = { [key: string]: any }; - -export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => { - let fontSize: number | undefined = undefined; - tx2.doc.descendants((node: any, offset: any, index: any) => { - if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { - const path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); - if (node.type === schema.nodes.ordered_list) depth++; - fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize; - const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined; - tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); - } - }); - return tx2; -}; -export default function buildKeymap>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { - const keys: { [key: string]: any } = {}; - - function bind(key: string, cmd: any) { - if (mapKeys) { - const mapped = mapKeys[key]; - if (mapped === false) return; - if (mapped) key = mapped; - } - keys[key] = cmd; - } - - bind("Mod-z", undo); - bind("Shift-Mod-z", redo); - bind("Backspace", undoInputRule); - - !mac && bind("Mod-y", redo); - - bind("Alt-ArrowUp", joinUp); - bind("Alt-ArrowDown", joinDown); - bind("Mod-BracketLeft", lift); - bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { - dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur?.(); - SelectionManager.DeselectAll(); - }); - - bind("Mod-b", toggleMark(schema.marks.strong)); - bind("Mod-B", toggleMark(schema.marks.strong)); - - bind("Mod-e", toggleMark(schema.marks.em)); - bind("Mod-E", toggleMark(schema.marks.em)); - - bind("Mod-u", toggleMark(schema.marks.underline)); - bind("Mod-U", toggleMark(schema.marks.underline)); - - bind("Mod-`", toggleMark(schema.marks.code)); - - bind("Ctrl-.", wrapInList(schema.nodes.bullet_list)); - - bind("Ctrl-n", wrapInList(schema.nodes.ordered_list)); - - bind("Ctrl->", wrapIn(schema.nodes.blockquote)); - - // bind("^", (state: EditorState, dispatch: (tx: Transaction) => void) => { - // let newNode = schema.nodes.footnote.create({}); - // if (dispatch && state.selection.from === state.selection.to) { - // let tr = state.tr; - // tr.replaceSelectionWith(newNode); // replace insertion with a footnote. - // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display - // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) - // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)))); - // return true; - // } - // return false; - // }); - - - const cmd = chainCommands(exitCode, (state, dispatch) => { - if (dispatch) { - dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView()); - return true; - } - return false; - }); - bind("Mod-Enter", cmd); - bind("Shift-Enter", cmd); - mac && bind("Ctrl-Enter", cmd); - - - bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph)); - - bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block)); - - for (let i = 1; i <= 6; i++) { - bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i })); - } - - const hr = schema.nodes.horizontal_rule; - bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => { - dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); - return true; - }); - - bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const ref = state.selection; - const range = ref.$from.blockRange(ref.$to); - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { // couldn't sink into an existing list, so wrap in a new one - const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); - if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - // when promoting to a list, assume list will format things so don't copy the stored marks. - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { - console.log("bullet promote fail"); - } - } - }); - - bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - - if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { - console.log("bullet demote fail"); - } - }); - bind("Ctrl-Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const layoutDoc = props.Document; - const originalDoc = layoutDoc.rootDocument || layoutDoc; - if (originalDoc instanceof Doc) { - const layoutKey = StrCast(originalDoc.layoutKey); - const newDoc = Docs.Create.TextDocument("", { - layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, - layoutKey, - _singleLine: BoolCast(originalDoc._singleLine), - x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) - }); - if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { - newDoc[layoutKey] = originalDoc[layoutKey]; - } - FormattedTextBox.SelectOnLoad = newDoc[Id]; - props.addDocument(newDoc); - } - }); - - const splitMetadata = (marks: any, tx: Transaction) => { - marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); - marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); - return tx; - }; - const addTextOnRight = (force: boolean) => { - const layoutDoc = props.Document; - const originalDoc = layoutDoc.rootDocument || layoutDoc; - if (force || props.Document._singleLine) { - const layoutKey = StrCast(originalDoc.layoutKey); - const newDoc = Docs.Create.TextDocument("", { - layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, - layoutKey, - _singleLine: BoolCast(originalDoc._singleLine), - x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) - }); - if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { - newDoc[layoutKey] = originalDoc[layoutKey]; - } - FormattedTextBox.SelectOnLoad = newDoc[Id]; - props.addDocument(newDoc); - return true; - } - return false; - }; - bind("Alt-Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - return addTextOnRight(true); - }); - bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - if (addTextOnRight(false)) return true; - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!splitListItem(schema.nodes.list_item)(state, dispatch)) { - if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - splitMetadata(marks, tx3); - if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { - dispatch(tx3); - } - })) { - return false; - } - } - return true; - }); - bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - dispatch(splitMetadata(marks, state.tr)); - return false; - }); - bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { - return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); - }); - const path = (state.doc.resolve(state.selection.from - 1) as any).path; - const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; - const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator; - if (anchor >= 0) { - const textsel = TextSelection.create(state.doc, anchor, range!.end); - const text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; - let whitespace = text.length - 1; - for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } - if (text.endsWith(":")) { - dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). - addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); - } - } - return false; - }); - - - return keys; -} diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss deleted file mode 100644 index 43cc23ecd..000000000 --- a/src/client/util/RichTextMenu.scss +++ /dev/null @@ -1,121 +0,0 @@ -@import "../views/globalCssVariables"; - -.button-dropdown-wrapper { - position: relative; - - .dropdown-button { - width: 15px; - padding-left: 5px; - padding-right: 5px; - } - - .dropdown-button-combined { - width: 50px; - display: flex; - justify-content: space-between; - - svg:nth-child(2) { - margin-top: 2px; - } - } - - .dropdown { - position: absolute; - top: 35px; - left: 0; - background-color: #323232; - color: $light-color-secondary; - border: 1px solid #4d4d4d; - border-radius: 0 6px 6px 6px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - min-width: 150px; - padding: 5px; - font-size: 12px; - z-index: 10001; - - button { - background-color: #323232; - border: 1px solid black; - border-radius: 1px; - padding: 6px; - margin: 5px 0; - font-size: 10px; - - &:hover { - background-color: black; - } - - &:last-child { - margin-bottom: 0; - } - } - } - - input { - color: black; - } -} - -.link-menu { - .divider { - background-color: white; - height: 1px; - width: 100%; - } -} - -.color-preview-button { - .color-preview { - width: 100%; - height: 3px; - margin-top: 3px; - } -} - -.color-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - - button.color-button { - width: 20px; - height: 20px; - border-radius: 15px !important; - margin: 3px; - border: 2px solid transparent !important; - padding: 3px; - - &.active { - border: 2px solid white !important; - } - } -} - -select { - background-color: #323232; - color: white; - border: 1px solid black; - // border-top: none; - // border-bottom: none; - font-size: 12px; - height: 100%; - margin-right: 3px; - - &:focus, - &:hover { - background-color: black; - } - - &::-ms-expand { - color: white; - } -} - -.row-2 { - display: flex; - justify-content: space-between; - - >div { - display: flex; - } -} \ No newline at end of file diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx deleted file mode 100644 index 140635a1d..000000000 --- a/src/client/util/RichTextMenu.tsx +++ /dev/null @@ -1,875 +0,0 @@ -import React = require("react"); -import AntimodeMenu from "../views/AntimodeMenu"; -import { observable, action, } from "mobx"; -import { observer } from "mobx-react"; -import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; -import { schema } from "./schema_rts"; -import { EditorView } from "prosemirror-view"; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons"; -import { updateBullets } from "./ProsemirrorExampleTransfer"; -import { FieldViewProps } from "../views/nodes/FieldView"; -import { Cast, StrCast } from "../../new_fields/Types"; -import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; -import { unimplementedFunction, Utils } from "../../Utils"; -import { wrapInList } from "prosemirror-schema-list"; -import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; -import "./RichTextMenu.scss"; -import { DocServer } from "../DocServer"; -import { Doc } from "../../new_fields/Doc"; -import { SelectionManager } from "./SelectionManager"; -import { LinkManager } from "./LinkManager"; -const { toggleMark, setBlockType } = require("prosemirror-commands"); - -library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); - -@observer -export default class RichTextMenu extends AntimodeMenu { - static Instance: RichTextMenu; - public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable - - private view?: EditorView; - public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - - public _brushMap: Map> = new Map(); - private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; - private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; - private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[]; - private fontColors: (string | undefined)[]; - private highlightColors: (string | undefined)[]; - - @observable private collapsed: boolean = false; - @observable private boldActive: boolean = false; - @observable private italicsActive: boolean = false; - @observable private underlineActive: boolean = false; - @observable private strikethroughActive: boolean = false; - @observable private subscriptActive: boolean = false; - @observable private superscriptActive: boolean = false; - - @observable private activeFontSize: string = ""; - @observable private activeFontFamily: string = ""; - @observable private activeListType: string = ""; - - @observable private brushIsEmpty: boolean = true; - @observable private brushMarks: Set = new Set(); - @observable private showBrushDropdown: boolean = false; - - @observable private activeFontColor: string = "black"; - @observable private showColorDropdown: boolean = false; - - @observable private activeHighlightColor: string = "transparent"; - @observable private showHighlightDropdown: boolean = false; - - @observable private currentLink: string | undefined = ""; - @observable private showLinkDropdown: boolean = false; - - constructor(props: Readonly<{}>) { - super(props); - RichTextMenu.Instance = this; - this._canFade = false; - - this.fontSizeOptions = [ - { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize }, - { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, - { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option - ]; - - this.fontFamilyOptions = [ - { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } }, - { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } }, - { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } }, - { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } }, - { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } }, - { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } }, - { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } }, - { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, - // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true }, - ]; - - this.listTypeOptions = [ - { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, - { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, - { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType }, - { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, - ]; - - this.fontColors = [ - DarkPastelSchemaPalette.get("pink2"), - DarkPastelSchemaPalette.get("purple4"), - DarkPastelSchemaPalette.get("bluegreen1"), - DarkPastelSchemaPalette.get("yellow4"), - DarkPastelSchemaPalette.get("red2"), - DarkPastelSchemaPalette.get("bluegreen7"), - DarkPastelSchemaPalette.get("bluegreen5"), - DarkPastelSchemaPalette.get("orange1"), - "#757472", - "#000" - ]; - - this.highlightColors = [ - PastelSchemaPalette.get("pink2"), - PastelSchemaPalette.get("purple4"), - PastelSchemaPalette.get("bluegreen1"), - PastelSchemaPalette.get("yellow4"), - PastelSchemaPalette.get("red2"), - PastelSchemaPalette.get("bluegreen7"), - PastelSchemaPalette.get("bluegreen5"), - PastelSchemaPalette.get("orange1"), - "white", - "transparent" - ]; - } - - @action - changeView(view: EditorView) { - this.view = view; - } - - update(view: EditorView, lastState: EditorState | undefined) { - this.updateFromDash(view, lastState, this.editorProps); - } - - - public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { - if (this.view) { - const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). - addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - return this.view.state.selection.$from.nodeAfter?.text || ""; - } - return ""; - } - - @action - public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { - if (!view) { - console.log("no editor? why?"); - return; - } - this.view = view; - const state = view.state; - props && (this.editorProps = props); - - // Don't do anything if the document/selection didn't change - if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; - - // update active marks - const activeMarks = this.getActiveMarksOnSelection(); - this.setActiveMarkButtons(activeMarks); - - // update active font family and size - const active = this.getActiveFontStylesOnSelection(); - const activeFamilies = active && active.get("families"); - const activeSizes = active && active.get("sizes"); - - this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various"; - - // update link in current selection - const targetTitle = await this.getTextLinkTargetTitle(); - this.setCurrentLink(targetTitle); - } - - setMark = (mark: Mark, state: EditorState, dispatch: any) => { - if (mark) { - const node = (state.selection as NodeSelection).node; - if (node?.type === schema.nodes.ordered_list) { - let attrs = node.attrs; - if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; - if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; - if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; - const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); - dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); - } else { - toggleMark(mark.type, mark.attrs)(state, (tx: any) => { - const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { - toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); - } else dispatch(tx); - }); - } - } - } - - // finds font sizes and families in selection - getActiveFontStylesOnSelection() { - if (!this.view) return; - - const activeFamilies: string[] = []; - const activeSizes: string[] = []; - const state = this.view.state; - const pos = this.view.state.selection.$from; - const ref_node = this.reference_node(pos); - if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - ref_node.marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); - }); - } - - const styles = new Map(); - styles.set("families", activeFamilies); - styles.set("sizes", activeSizes); - return styles; - } - - getMarksInSelection(state: EditorState) { - const found = new Set(); - const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); - return found; - } - - //finds all active marks on selection in given group - getActiveMarksOnSelection() { - if (!this.view) return; - - const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; - if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); - //current selection - const { empty, ranges, $to } = this.view.state.selection as TextSelection; - const state = this.view.state; - let activeMarks: MarkType[] = []; - if (!empty) { - activeMarks = markGroup.filter(mark => { - const has = false; - for (let i = 0; !has && i < ranges.length; i++) { - return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark); - } - return false; - }); - } - else { - const pos = this.view.state.selection.$from; - const ref_node: ProsNode | null = this.reference_node(pos); - if (ref_node !== null && ref_node !== this.view.state.doc) { - if (ref_node.isText) { - } - else { - return []; - } - activeMarks = markGroup.filter(mark_type => { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } - const mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); - }); - } - } - return activeMarks; - } - - destroy() { - this.fadeOut(true); - } - - @action - setActiveMarkButtons(activeMarks: MarkType[] | undefined) { - if (!activeMarks) return; - - this.boldActive = false; - this.italicsActive = false; - this.underlineActive = false; - this.strikethroughActive = false; - this.subscriptActive = false; - this.superscriptActive = false; - - activeMarks.forEach(mark => { - switch (mark.name) { - case "strong": this.boldActive = true; break; - case "em": this.italicsActive = true; break; - case "underline": this.underlineActive = true; break; - case "strikethrough": this.strikethroughActive = true; break; - case "subscript": this.subscriptActive = true; break; - case "superscript": this.superscriptActive = true; break; - } - }); - } - - createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) { - const self = this; - function onClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && command && command(self.view.state, self.view.dispatch, self.view); - self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); - self.setActiveMarkButtons(self.getActiveMarksOnSelection()); - } - - return ( - - ); - } - - createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { - const items = options.map(({ title, label, hidden, style }) => { - if (hidden) { - return label === activeOption ? - : - ; - } - return label === activeOption ? - : - ; - }); - - const self = this; - function onChange(e: React.ChangeEvent) { - e.stopPropagation(); - e.preventDefault(); - options.forEach(({ label, mark, command }) => { - if (e.target.value === label) { - self.view && mark && command(mark, self.view); - } - }); - } - return ; - } - - createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { - const items = options.map(({ title, label, hidden, style }) => { - if (hidden) { - return label === activeOption ? - : - ; - } - return label === activeOption ? - : - ; - }); - - const self = this; - function onChange(val: string) { - options.forEach(({ label, node, command }) => { - if (val === label) { - self.view && node && command(node); - } - }); - } - return ; - } - - changeFontSize = (mark: Mark, view: EditorView) => { - this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch); - } - - changeFontFamily = (mark: Mark, view: EditorView) => { - this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch); - } - - // TODO: remove doesn't work - //remove all node type and apply the passed-in one to the selected text - changeListType = (nodeType: NodeType | undefined) => { - if (!this.view) return; - - if (nodeType === schema.nodes.bullet_list) { - wrapInList(nodeType)(this.view.state, this.view.dispatch); - } else { - const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - this.view!.dispatch(tx2); - })) { - const tx2 = this.view.state.tr; - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - this.view.dispatch(tx3); - } - } - } - - insertSummarizer(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - const mark = state.schema.marks.summarize.create(); - const tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - const content = tr.selection.content(); - const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); - dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); - return true; - } - - @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } - - // todo: add brushes to brushMap to save with a style name - onBrushNameKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); - this._brushNameRef.current!.style.background = "lightGray"; - } - } - _brushNameRef = React.createRef(); - - createBrushButton() { - const self = this; - function onBrushClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && self.fillBrush(self.view.state, self.view.dispatch); - } - - let label = "Stored marks: "; - if (this.brushMarks && this.brushMarks.size > 0) { - this.brushMarks.forEach((mark: Mark) => { - const markType = mark.type; - label += markType.name; - label += ", "; - }); - label = label.substring(0, label.length - 2); - } else { - label = "No marks are currently stored"; - } - - const button = - ; - - const dropdownContent = -

-

{label}

- - -
; - - return ( - - ); - } - - @action - clearBrush() { - RichTextMenu.Instance.brushIsEmpty = true; - RichTextMenu.Instance.brushMarks = new Set(); - } - - @action - fillBrush(state: EditorState, dispatch: any) { - if (!this.view) return; - - if (this.brushIsEmpty) { - const selected_marks = this.getMarksInSelection(this.view.state); - if (selected_marks.size >= 0) { - this.brushMarks = selected_marks; - this.brushIsEmpty = !this.brushIsEmpty; - } - } - else { - const { from, to, $from } = this.view.state.selection; - if (!this.view.state.selection.empty && $from && $from.nodeAfter) { - if (this.brushMarks && to - from > 0) { - this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { - this.setMark(mark, this.view!.state, this.view!.dispatch); - }); - } - } - else { - this.brushIsEmpty = !this.brushIsEmpty; - } - } - } - - @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; } - @action setActiveColor(color: string) { this.activeFontColor = color; } - - createColorButton() { - const self = this; - function onColorClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); - } - function changeColor(e: React.PointerEvent, color: string) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.setActiveColor(color); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); - } - - const button = - ; - - const dropdownContent = -
-

Change font color:

-
- {this.fontColors.map(color => { - if (color) { - return this.activeFontColor === color ? - : - ; - } - })} -
-
; - - return ( - - ); - } - - public insertColor(color: String, state: EditorState, dispatch: any) { - const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color }); - if (state.selection.empty) { - dispatch(state.tr.addStoredMark(colorMark)); - return false; - } - this.setMark(colorMark, state, dispatch); - } - - @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; } - @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } - - createHighlighterButton() { - const self = this; - function onHighlightClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); - } - function changeHighlight(e: React.PointerEvent, color: string) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.setActiveHighlight(color); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); - } - - const button = - ; - - const dropdownContent = -
-

Change highlight color:

-
- {this.highlightColors.map(color => { - if (color) { - return this.activeHighlightColor === color ? - : - ; - } - })} -
-
; - - return ( - - ); - } - - insertHighlight(color: String, state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch); - } - - @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; } - @action setCurrentLink(link: string) { this.currentLink = link; } - - createLinkButton() { - const self = this; - - function onLinkChange(e: React.ChangeEvent) { - self.setCurrentLink(e.target.value); - } - - const link = this.currentLink ? this.currentLink : ""; - - const button = ; - - const dropdownContent = -
-

Linked to:

- - -
- -
; - - return ( - - ); - } - - async getTextLinkTargetTitle() { - if (!this.view) return; - - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - const href = link.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - const linkDoc = await DocServer.GetRefField(linkclicked); - if (linkDoc instanceof Doc) { - const anchor1 = await Cast(linkDoc.anchor1, Doc); - const anchor2 = await Cast(linkDoc.anchor2, Doc); - const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - if (currentDoc && anchor1 && anchor2) { - if (Doc.AreProtosEqual(currentDoc, anchor1)) { - return StrCast(anchor2.title); - } - if (Doc.AreProtosEqual(currentDoc, anchor2)) { - return StrCast(anchor1.title); - } - } - } - } - } else { - return href; - } - } else { - return link.attrs.title; - } - } - } - - // TODO: should check for valid URL - makeLinkToURL = (target: String, lcoation: string) => { - if (!this.view) return; - - let node = this.view.state.selection.$from.nodeAfter; - let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); - this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - node = this.view.state.selection.$from.nodeAfter; - link = node && node.marks.find(m => m.type.name === "link"); - } - - deleteLink = () => { - if (!this.view) return; - - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link); - const href = link!.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - DocServer.GetRefField(linkclicked).then(async linkDoc => { - if (linkDoc instanceof Doc) { - LinkManager.Instance.deleteLink(linkDoc); - this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link)); - } - }); - } - } else { - if (node) { - const { tr, schema, selection } = this.view.state; - const extension = this.linkExtend(selection.$anchor, href); - this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link)); - } - } - } - } - - linkExtend($start: ResolvedPos, href: string) { - const mark = this.view!.state.schema.marks.link; - - let startIndex = $start.index(); - let endIndex = $start.indexAfter(); - - while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--; - while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++; - - let startPos = $start.start(); - let endPos = startPos; - for (let i = 0; i < endIndex; i++) { - const size = $start.parent.child(i).nodeSize; - if (i < startIndex) startPos += size; - endPos += size; - } - return { from: startPos, to: endPos }; - } - - reference_node(pos: ResolvedPos): ProsNode | null { - if (!this.view) return null; - - let ref_node: ProsNode = this.view.state.doc; - if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { - ref_node = pos.nodeBefore; - } - else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { - ref_node = pos.nodeAfter; - } - else if (pos.pos > 0) { - let skip = false; - for (let i: number = pos.pos - 1; i > 0; i--) { - this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => { - if (node.isLeaf && !skip) { - ref_node = node; - skip = true; - } - - }); - } - } - if (!ref_node.isLeaf && ref_node.childCount > 0) { - ref_node = ref_node.child(0); - } - return ref_node; - } - - @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; } - @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } - - @action - toggleMenuPin = (e: React.MouseEvent) => { - this.Pinned = !this.Pinned; - if (!this.Pinned) { - this.fadeOut(true); - } - } - - @action - protected toggleCollapse = (e: React.MouseEvent) => { - this.collapsed = !this.collapsed; - setTimeout(() => { - const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width); - RichTextMenu.Instance.jumpTo(x, this._top); - }, 0); - } - - render() { - - const row1 =
{[ - this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), - this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), - this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), - this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), - this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), - this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), - this.createColorButton(), - this.createHighlighterButton(), - this.createLinkButton(), - this.createBrushButton(), - this.createButton("indent", "Summarize", undefined, this.insertSummarizer), - ]}
; - - const row2 =
-
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), - this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), - this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]} -
-
-
- -
- - {this.getDragger()} -
-
; - - return ( -
- {this.getElementWithRows([row1, row2], 2, false)} -
- ); - } -} - -interface ButtonDropdownProps { - view?: EditorView; - button: JSX.Element; - dropdownContent: JSX.Element; - openDropdownOnButton?: boolean; -} - -@observer -class ButtonDropdown extends React.Component { - - @observable private showDropdown: boolean = false; - private ref: HTMLDivElement | null = null; - - componentDidMount() { - document.addEventListener("pointerdown", this.onBlur); - } - - componentWillUnmount() { - document.removeEventListener("pointerdown", this.onBlur); - } - - @action - setShowDropdown(show: boolean) { - this.showDropdown = show; - } - @action - toggleDropdown() { - this.showDropdown = !this.showDropdown; - } - - onDropdownClick = (e: React.PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - this.props.view && this.props.view.focus(); - this.toggleDropdown(); - } - - onBlur = (e: PointerEvent) => { - setTimeout(() => { - if (this.ref !== null && !this.ref.contains(e.target as Node)) { - this.setShowDropdown(false); - } - }, 0); - } - - render() { - return ( -
this.ref = node}> - {this.props.openDropdownOnButton ? - : - <> - {this.props.button} - - } - - {this.showDropdown ? this.props.dropdownContent : (null)} -
- ); - } -} \ No newline at end of file diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 63a3815ea..7ce6bbf85 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -7,9 +7,9 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { returnFalse, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocUtils } from "../documents/Documents"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { wrappingInputRule } from "./prosemirrorPatches"; -import RichTextMenu from "./RichTextMenu"; +import RichTextMenu from "../views/nodes/formattedText/RichTextMenu"; import { schema } from "./schema_rts"; export class RichTextRules { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx deleted file mode 100644 index 3e45d5de5..000000000 --- a/src/client/util/RichTextSchema.tsx +++ /dev/null @@ -1,718 +0,0 @@ -import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { baseKeymap, toggleMark } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { keymap } from "prosemirror-keymap"; -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; -import { StepMap } from "prosemirror-transform"; -import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { List } from "../../new_fields/List"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs } from "../documents/Documents"; -import { CollectionViewType } from "../views/collections/CollectionView"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { DocumentManager } from "./DocumentManager"; -import { Transform } from "./Transform"; -import React = require("react"); - -import { schema } from "./schema_rts"; - -export class OrderedListView { - update(node: any) { - return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update - } -} - -export class ImageResizeView { - _handle: HTMLElement; - _img: HTMLElement; - _outer: HTMLElement; - constructor(node: any, view: any, getPos: any, addDocTab: any) { - //moved - this._handle = document.createElement("span"); - this._img = document.createElement("img"); - this._outer = document.createElement("span"); - this._outer.style.position = "relative"; - this._outer.style.width = node.attrs.width; - this._outer.style.height = node.attrs.height; - this._outer.style.display = "inline-block"; - this._outer.style.overflow = "hidden"; - (this._outer.style as any).float = node.attrs.float; - //moved - this._img.setAttribute("src", node.attrs.src); - this._img.style.width = "100%"; - this._handle.style.position = "absolute"; - this._handle.style.width = "20px"; - this._handle.style.height = "20px"; - this._handle.style.backgroundColor = "blue"; - this._handle.style.borderRadius = "15px"; - this._handle.style.display = "none"; - this._handle.style.bottom = "-10px"; - this._handle.style.right = "-10px"; - const self = this; - //moved - this._img.onclick = function (e: any) { - e.stopPropagation(); - e.preventDefault(); - if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) { - view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); - } - }; - //moved - this._img.onpointerdown = function (e: any) { - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - DocServer.GetRefField(node.attrs.docid).then(async linkDoc => - (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document, - document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false)); - } - }; - //moved - this._handle.onpointerdown = function (e: any) { - e.preventDefault(); - e.stopPropagation(); - const wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); - const hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); - const startX = e.pageX; - const startWidth = parseFloat(node.attrs.width); - const onpointermove = (e: any) => { - const currentX = e.pageX; - const diffInPx = currentX - startX; - self._outer.style.width = `${startWidth + diffInPx}`; - self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`; - }; - - const onpointerup = () => { - document.removeEventListener("pointermove", onpointermove); - document.removeEventListener("pointerup", onpointerup); - const pos = view.state.selection.from; - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height })); - view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos)))); - }; - - document.addEventListener("pointermove", onpointermove); - document.addEventListener("pointerup", onpointerup); - }; - //Moved - this._outer.appendChild(this._img); - this._outer.appendChild(this._handle); - (this as any).dom = this._outer; - } - - selectNode() { - this._img.classList.add("ProseMirror-selectednode"); - - this._handle.style.display = ""; - } - - deselectNode() { - this._img.classList.remove("ProseMirror-selectednode"); - - this._handle.style.display = "none"; - } -} - -export class DashDocCommentView { - _collapsed: HTMLElement; - _view: any; - constructor(node: any, view: any, getPos: any) { - //moved - this._collapsed = document.createElement("span"); - this._collapsed.className = "formattedTextBox-inlineComment"; - this._collapsed.id = "DashDocCommentView-" + node.attrs.docid; - this._view = view; - //moved - const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor - for (let i = getPos() + 1; i < view.state.doc.content.size; i++) { - const m = view.state.doc.nodeAt(i); - if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) { - return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; - } - } - const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" }); - view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc)); - setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0); - return undefined; - }; - //moved - this._collapsed.onpointerdown = (e: any) => { - e.stopPropagation(); - }; - //moved - this._collapsed.onpointerup = (e: any) => { - const target = targetNode(); - if (target) { - const expand = target.hidden; - const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); - view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs - setTimeout(() => { - expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { } - }, 0); - } - e.stopPropagation(); - }; - //moved - this._collapsed.onpointerenter = (e: any) => { - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); - e.preventDefault(); - e.stopPropagation(); - }; - //moved - this._collapsed.onpointerleave = (e: any) => { - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); - e.preventDefault(); - e.stopPropagation(); - }; - - (this as any).dom = this._collapsed; - } - //moved - selectNode() { } -} - -export class DashDocView { - _dashSpan: HTMLDivElement; - _outer: HTMLElement; - _dashDoc: Doc | undefined; - _reactionDisposer: IReactionDisposer | undefined; - _renderDisposer: IReactionDisposer | undefined; - _textBox: FormattedTextBox; - - getDocTransform = () => { - const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); - return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); - } - contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; - - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._textBox = tbox; - this._dashSpan = document.createElement("div"); - this._outer = document.createElement("span"); - this._outer.style.position = "relative"; - this._outer.style.textIndent = "0"; - this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); - this._outer.style.width = node.attrs.width; - this._outer.style.height = node.attrs.height; - this._outer.style.display = node.attrs.hidden ? "none" : "inline-block"; - // this._outer.style.overflow = "hidden"; // bcz: not sure if this is needed. if it's used, then the doc doesn't highlight when you hover over a docComment - (this._outer.style as any).float = node.attrs.float; - - this._dashSpan.style.width = node.attrs.width; - this._dashSpan.style.height = node.attrs.height; - this._dashSpan.style.position = "absolute"; - this._dashSpan.style.display = "inline-block"; - this._dashSpan.onpointerleave = () => { - const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = ""; - } - }; - this._dashSpan.onpointerenter = () => { - const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = "orange"; - } - }; - const removeDoc = () => { - const pos = getPos(); - const ns = new NodeSelection(view.state.doc.resolve(pos)); - view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); - return true; - }; - const alias = node.attrs.alias; - - const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; - DocServer.GetRefField(docid + alias).then(async dashDoc => { - if (!(dashDoc instanceof Doc)) { - alias && DocServer.GetRefField(docid).then(async dashDocBase => { - if (dashDocBase instanceof Doc) { - const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); - aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); - self.doRender(aliasedDoc, removeDoc, node, view, getPos); - } - }); - } else { - self.doRender(dashDoc, removeDoc, node, view, getPos); - } - }); - const self = this; - this._dashSpan.onkeydown = function (e: any) { - e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { - e.preventDefault(); - } - }; - this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; - this._dashSpan.onwheel = function (e: any) { e.preventDefault(); }; - this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; - this._outer.appendChild(this._dashSpan); - (this as any).dom = this._outer; - } - - doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { - this._dashDoc = dashDoc; - const self = this; - const dashLayoutDoc = Doc.Layout(dashDoc); - const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); - - if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); - else { - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => { - this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; - this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; - this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); - }, { fireImmediately: true }); - const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { - ReactDOM.unmountComponentAtNode(this._dashSpan); - - ReactDOM.render(, this._dashSpan); - if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); - } catch (e) { - console.log(e); - } - } - }; - this._renderDisposer?.(); - this._renderDisposer = reaction(() => { - // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { - // finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here? - // } - const layoutKey = StrCast(finalLayout.layoutKey); - const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; - if (finalLayout !== dashDoc && finalKey) { - const finalLayoutField = finalLayout[finalKey]; - if (finalLayoutField instanceof ObjectField) { - finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); - } - } - return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; - }, - (res) => doReactRender(res.finalLayout, res.resolvedDataDoc), - { fireImmediately: true }); - } - } - destroy() { - ReactDOM.unmountComponentAtNode(this._dashSpan); - this._reactionDisposer?.(); - } -} - -export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value - _labelSpan: HTMLSpanElement; // field label - _fieldSpan: HTMLSpanElement; // field value - _fieldCheck: HTMLInputElement; - _enumerables: HTMLDivElement; // field value - _reactionDisposer: IReactionDisposer | undefined; - _textBoxDoc: Doc; - @observable _dashDoc: Doc | undefined; - _fieldKey: string; - _options: Doc[] = []; - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldKey = node.attrs.fieldKey; - this._textBoxDoc = tbox.props.Document; - this._fieldWrapper = document.createElement("p"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - - const self = this; - - this._enumerables = document.createElement("div"); - this._enumerables.style.width = "10px"; - this._enumerables.style.height = "10px"; - this._enumerables.style.position = "relative"; - this._enumerables.style.display = "none"; - - //Moved - this._enumerables.onpointerdown = async (e) => { - e.stopPropagation(); - const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); - collview instanceof Doc && tbox.props.addDocTab(collview, "onRight"); - }; - //Moved - const updateText = (forceMatch: boolean) => { - self._enumerables.style.display = "none"; - const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText; - - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(self._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText; - Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []); - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (self._fieldSpan.innerText.startsWith(":=")) { - self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); - } else if (self._fieldSpan.innerText.startsWith("=:=")) { - Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3)); - } else { - self._dashDoc![self._fieldKey] = newText; - } - }); - }; - - //Moved - this._fieldCheck = document.createElement("input"); - this._fieldCheck.id = Utils.GenerateGuid(); - this._fieldCheck.type = "checkbox"; - this._fieldCheck.style.position = "relative"; - this._fieldCheck.style.display = "none"; - this._fieldCheck.style.minWidth = "12px"; - this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; - this._fieldCheck.onchange = function (e: any) { - self._dashDoc![self._fieldKey] = e.target.checked; - }; - - this._fieldSpan = document.createElement("span"); - this._fieldSpan.id = Utils.GenerateGuid(); - this._fieldSpan.contentEditable = "true"; - this._fieldSpan.style.position = "relative"; - this._fieldSpan.style.display = "none"; - this._fieldSpan.style.minWidth = "12px"; - this._fieldSpan.style.fontSize = "large"; - this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; }; - this._fieldSpan.onblur = function (e: any) { updateText(false); }; - - // MOVED - const setDashDoc = (doc: Doc) => { - self._dashDoc = doc; - if (self._options?.length && !self._dashDoc[self._fieldKey]) { - self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); - } - this._labelSpan.innerHTML = `${self._fieldKey}: `; - const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); - this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; - this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none"; - }; - - //Moved - this._fieldSpan.onkeydown = function (e: any) { - e.stopPropagation(); - if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(self._fieldSpan); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); - } - if (e.key === "Enter") { - e.preventDefault(); - e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); - updateText(true); - } - }; - - this._labelSpan = document.createElement("span"); - this._labelSpan.style.position = "relative"; - this._labelSpan.style.fontSize = "small"; - this._labelSpan.title = "click to see related tags"; - this._labelSpan.style.fontSize = "x-small"; - this._labelSpan.onpointerdown = function (e: any) { - e.stopPropagation(); - let container = tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } - if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias.viewType = CollectionViewType.Time; - let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); - if (!list) { - alias.schemaColumns = list = new List(); - } - list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = self._fieldKey; - tbox.props.addDocTab(alias, "onRight"); - } - }; - this._labelSpan.innerHTML = `${self._fieldKey}: `; - //MOVED - if (node.attrs.docid) { - DocServer.GetRefField(node.attrs.docid). - then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc))); - } else { - setDashDoc(tbox.props.DataDoc || tbox.dataDoc); - } - - //Moved - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes - const dashVal = this._dashDoc?.[self._fieldKey]; - return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal; - }, fval => { - const boolVal = Cast(fval, "boolean", null); - if (boolVal === true || boolVal === false) { - this._fieldCheck.checked = boolVal; - } else { - this._fieldSpan.innerHTML = Field.toString(fval as Field) || ""; - } - this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none"; - this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none"; - }, { fireImmediately: true }); - - //MOVED IN ORDER - this._fieldWrapper.appendChild(this._labelSpan); - this._fieldWrapper.appendChild(this._fieldCheck); - this._fieldWrapper.appendChild(this._fieldSpan); - this._fieldWrapper.appendChild(this._enumerables); - (this as any).dom = this._fieldWrapper; - //updateText(false); - } - //MOVED - destroy() { - this._reactionDisposer?.(); - } - //moved - selectNode() { } -} - -export class FootnoteView { - innerView: any; - outerView: any; - node: any; - dom: any; - getPos: any; - - constructor(node: any, view: any, getPos: any) { - // We'll need these later - this.node = node; - this.outerView = view; - this.getPos = getPos; - - // The node's representation in the editor (empty, for now) - this.dom = document.createElement("footnote"); - this.dom.addEventListener("pointerup", this.toggle, true); - // These are used when the footnote is selected - this.innerView = null; - } - selectNode() { - const attrs = { ...this.node.attrs }; - attrs.visibility = true; - this.dom.classList.add("ProseMirror-selectednode"); - if (!this.innerView) this.open(); - } - - deselectNode() { - const attrs = { ...this.node.attrs }; - attrs.visibility = false; - this.dom.classList.remove("ProseMirror-selectednode"); - if (this.innerView) this.close(); - } - open() { - // Append a tooltip to the outer node - const tooltip = this.dom.appendChild(document.createElement("div")); - tooltip.className = "footnote-tooltip"; - // And put a sub-ProseMirror into that - this.innerView = new EditorView(tooltip, { - // You can use any node as an editor document - state: EditorState.create({ - doc: this.node, - plugins: [keymap(baseKeymap), - keymap({ - "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch), - "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), - "Mod-b": toggleMark(schema.marks.strong) - }), - // new Plugin({ - // view(newView) { - // // TODO -- make this work with RichTextMenu - // // return FormattedTextBox.getToolTip(newView); - // } - // }) - ], - - }), - // This is the magic part - dispatchTransaction: this.dispatchInner.bind(this), - handleDOMEvents: { - pointerdown: ((view: any, e: PointerEvent) => { - // Kludge to prevent issues due to the fact that the whole - // footnote is node-selected (and thus DOM-selected) when - // the parent editor is focused. - e.stopPropagation(); - document.addEventListener("pointerup", this.ignore, true); - if (this.outerView.hasFocus()) this.innerView.focus(); - }) as any - } - - }); - setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0); - } - - ignore = (e: PointerEvent) => { - e.stopPropagation(); - document.removeEventListener("pointerup", this.ignore, true); - } - - toggle = () => { - if (this.innerView) this.close(); - else { - this.open(); - } - } - close() { - this.innerView && this.innerView.destroy(); - this.innerView = null; - this.dom.textContent = ""; - } - - dispatchInner(tr: any) { - const { state, transactions } = this.innerView.state.applyTransaction(tr); - this.innerView.updateState(state); - - if (!tr.getMeta("fromOutside")) { - const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); - for (const transaction of transactions) { - const steps = transaction.steps; - for (const step of steps) { - outerTr.step(step.map(offsetMap)); - } - } - if (outerTr.docChanged) this.outerView.dispatch(outerTr); - } - } - update(node: any) { - if (!node.sameMarkup(this.node)) return false; - this.node = node; - if (this.innerView) { - const state = this.innerView.state; - const start = node.content.findDiffStart(state.doc.content); - if (start !== null) { - let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); - const overlap = start - Math.min(endA, endB); - if (overlap > 0) { endA += overlap; endB += overlap; } - this.innerView.dispatch( - state.tr - .replace(start, endB, node.slice(start, endA)) - .setMeta("fromOutside", true)); - } - } - return true; - } - - destroy() { - if (this.innerView) this.close(); - } - - stopEvent(event: any) { - return this.innerView && this.innerView.dom.contains(event.target); - } - - ignoreMutation() { return true; } -} - -export class SummaryView { - _collapsed: HTMLElement; - _view: any; - constructor(node: any, view: any, getPos: any) { - this._collapsed = document.createElement("span"); - this._collapsed.className = this.className(node.attrs.visibility); - this._view = view; - const js = node.toJSON; - node.toJSON = function () { - return js.apply(this, arguments); - }; - - this._collapsed.onpointerdown = (e: any) => { - const visible = !node.attrs.visibility; - const attrs = { ...node.attrs, visibility: visible }; - let textSelection = TextSelection.create(view.state.doc, getPos() + 1); - if (!visible) { // update summarized text and save in attrs - textSelection = this.updateSummarizedText(getPos() + 1); - attrs.text = textSelection.content(); - attrs.textslice = attrs.text.toJSON(); - } - view.dispatch(view.state.tr. - setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) - replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it - setNodeMarkup(getPos(), undefined, attrs)); // update the attrs - e.preventDefault(); - e.stopPropagation(); - this._collapsed.className = this.className(visible); - }; - (this as any).dom = this._collapsed; - } - selectNode() { } - - deselectNode() { } - - className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); - - updateSummarizedText(start?: any) { - const mtype = this._view.state.schema.marks.summarize; - const mtypeInc = this._view.state.schema.marks.summarizeInclusive; - let endPos = start; - - const visited = new Set(); - for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) { - let skip = false; - this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { - if (node.isLeaf && !visited.has(node) && !skip) { - if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { - visited.add(node); - endPos = i + node.nodeSize - 1; - } - else skip = true; - } - }); - } - return TextSelection.create(this._view.state.doc, start, endPos); - } -} \ No newline at end of file diff --git a/src/client/util/SummaryView.tsx b/src/client/util/SummaryView.tsx deleted file mode 100644 index 89908d8ee..000000000 --- a/src/client/util/SummaryView.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { TextSelection } from "prosemirror-state"; -import { Fragment, Node, Slice } from "prosemirror-model"; - -import React = require("react"); - -interface ISummaryView { - node: any; - view: any; - getPos: any; - self: any; -} -export class SummaryView extends React.Component { - - onPointerDown = (e: any) => { - const visible = !this.props.node.attrs.visibility; - const attrs = { ...this.props.node.attrs, visibility: visible }; - let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1); - if (!visible) { // update summarized text and save in attrs - textSelection = this.updateSummarizedText(this.props.getPos() + 1); - attrs.text = textSelection.content(); - attrs.textslice = attrs.text.toJSON(); - } - this.props.view.dispatch(this.props.view.state.tr. - setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) - replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it - setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs - e.preventDefault(); - e.stopPropagation(); - const _collapsed = document.getElementById('collapse') as HTMLElement; - _collapsed.className = this.className(visible); - } - - updateSummarizedText(start?: any) { - const mtype = this.props.view.state.schema.marks.summarize; - const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive; - let endPos = start; - - const visited = new Set(); - for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) { - let skip = false; - this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { - if (this.props.node.isLeaf && !visited.has(node) && !skip) { - if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { - visited.add(node); - endPos = i + this.props.node.nodeSize - 1; - } - else skip = true; - } - }); - } - return TextSelection.create(this.props.view.state.doc, start, endPos); - } - - className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); - - selectNode() { } - - deselectNode() { } - - render() { - const _view = this.props.node.view; - const js = this.props.node.toJSon; - - this.props.node.toJSON = function () { - return js.apply(this, arguments); - }; - - const spanCollapsedClassName = this.className(this.props.node.attrs.visibility); - - return ( - - - - ); - - } -} \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss deleted file mode 100644 index e2149e9c1..000000000 --- a/src/client/util/TooltipTextMenu.scss +++ /dev/null @@ -1,372 +0,0 @@ -@import "../views/globalCssVariables"; -.ProseMirror-menu-dropdown-wrap { - display: inline-block; - position: relative; -} - -.ProseMirror-menu-dropdown { - vertical-align: 1px; - cursor: pointer; - position: relative; - padding: 0 15px 0 4px; - background: white; - border-radius: 2px; - text-align: left; - font-size: 12px; - white-space: nowrap; - margin-right: 4px; - - &:after { - content: ""; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 2px); - } -} - -.ProseMirror-menu-submenu-wrap { - position: relative; - margin-right: -4px; -} - -.ProseMirror-menu-dropdown-menu, -.ProseMirror-menu-submenu { - font-size: 12px; - background: white; - border: 1px solid rgb(223, 223, 223); - min-width: 40px; - z-index: 50000; - position: absolute; - box-sizing: content-box; - - .ProseMirror-menu-dropdown-item { - cursor: pointer; - z-index: 100000; - text-align: left; - padding: 3px; - - &:hover { - background-color: $light-color-secondary; - } - } -} - - -.ProseMirror-menu-submenu-label:after { - content: ""; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 4px); -} - - .ProseMirror-icon { - display: inline-block; - // line-height: .8; - // vertical-align: -2px; /* Compensate for padding */ - // padding: 2px 8px; - cursor: pointer; - - &.ProseMirror-menu-disabled { - cursor: default; - } - - svg { - fill:white; - height: 1em; - } - - span { - vertical-align: text-top; - } - } - -.wrapper { - position: absolute; - pointer-events: all; - display: flex; - align-items: center; - transform: translateY(-85px); - - height: 35px; - background: #323232; - border-radius: 6px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - -} - -.tooltipMenu, .basic-tools { - z-index: 20000; - pointer-events: all; - padding: 3px; - padding-bottom: 5px; - display: flex; - align-items: center; - - .ProseMirror-example-setup-style hr { - padding: 2px 10px; - border: none; - margin: 1em 0; - } - - .ProseMirror-example-setup-style hr:after { - content: ""; - display: block; - height: 1px; - background-color: silver; - line-height: 2px; - } -} - -.menuicon { - width: 25px; - height: 25px; - cursor: pointer; - text-align: center; - line-height: 25px; - margin: 0 2px; - border-radius: 3px; - - &:hover { - background-color: black; - - #link-drag { - background-color: black; - } - } - - &> * { - margin-top: 50%; - margin-left: 50%; - transform: translate(-50%, -50%); - } - - svg { - fill: white; - width: 18px; - height: 18px; - } -} - -.menuicon-active { - width: 25px; - height: 25px; - cursor: pointer; - text-align: center; - line-height: 25px; - margin: 0 2px; - border-radius: 3px; - - &:hover { - background-color: black; - } - - &> * { - margin-top: 50%; - margin-left: 50%; - transform: translate(-50%, -50%); - } - - svg { - fill: greenyellow; - width: 18px; - height: 18px; - } -} - -#colorPicker { - position: relative; - - svg { - width: 18px; - height: 18px; - // margin-top: 11px; - } - - .buttonColor { - position: absolute; - top: 24px; - left: 1px; - width: 24px; - height: 4px; - margin-top: 0; - } -} - -#link-drag { - background-color: #323232; -} - -.underline svg { - margin-top: 13px; -} - - .font-size-indicator { - font-size: 12px; - padding-right: 0px; - } - .summarize{ - color: white; - height: 20px; - text-align: center; - } - - -.brush{ - display: inline-block; - width: 1em; - height: 1em; - stroke-width: 0; - stroke: currentColor; - fill: currentColor; - margin-right: 15px; -} - -.brush-active{ - display: inline-block; - width: 1em; - height: 1em; - stroke-width: 3; - fill: greenyellow; - margin-right: 15px; -} - -.dragger-wrapper { - color: #eee; - height: 22px; - padding: 0 5px; - box-sizing: content-box; - cursor: grab; - - .dragger { - width: 18px; - height: 100%; - display: flex; - justify-content: space-evenly; - } - - .dragger-line { - width: 2px; - height: 100%; - background-color: black; - } -} - -.button-dropdown-wrapper { - display: flex; - align-content: center; - - &:hover { - background-color: black; - } -} - -.buttonSettings-dropdown { - - &.ProseMirror-menu-dropdown { - width: 10px; - height: 25px; - margin: 0; - padding: 0 2px; - background-color: #323232; - text-align: center; - - &:after { - border-top: 4px solid white; - right: 2px; - } - - &:hover { - background-color: black; - } - } - - &.ProseMirror-menu-dropdown-menu { - min-width: 150px; - left: -27px; - top: 31px; - background-color: #323232; - border: 1px solid #4d4d4d; - color: $light-color-secondary; - // border: none; - // border: 1px solid $light-color-secondary; - border-radius: 0 6px 6px 6px; - padding: 3px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - - .ProseMirror-menu-dropdown-item{ - cursor: default; - - &:last-child { - border-bottom: none; - } - - &:hover { - background-color: #323232; - } - - .button-setting, .button-setting-disabled { - padding: 2px; - border-radius: 2px; - } - - .button-setting:hover { - cursor: pointer; - background-color: black; - } - - .separated-button { - border-top: 1px solid $light-color-secondary; - padding-top: 6px; - } - - input { - color: black; - border: none; - border-radius: 1px; - padding: 3px; - } - - button { - padding: 6px; - background-color: #323232; - border: 1px solid black; - border-radius: 1px; - - &:hover { - background-color: black; - } - } - } - - - } -} - -.colorPicker-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - margin-top: 3px; - margin-left: -3px; - width: calc(100% + 6px); -} - -button.colorPicker { - width: 20px; - height: 20px; - border-radius: 15px !important; - margin: 3px; - border: none !important; - - &.active { - border: 2px solid white !important; - } -} diff --git a/src/client/util/marks_rts.ts b/src/client/util/marks_rts.ts deleted file mode 100644 index 75d7109e4..000000000 --- a/src/client/util/marks_rts.ts +++ /dev/null @@ -1,296 +0,0 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { Doc } from "../../new_fields/Doc"; - - -const emDOM: DOMOutputSpecArray = ["em", 0]; -const strongDOM: DOMOutputSpecArray = ["strong", 0]; -const codeDOM: DOMOutputSpecArray = ["code", 0]; - -// :: Object [Specs](#model.MarkSpec) for the marks in the schema. -export const marks: { [index: string]: MarkSpec } = { - // :: MarkSpec A link. Has `href` and `title` attributes. `title` - // defaults to the empty string. Rendered and parsed as an `` - // element. - link: { - attrs: { - href: {}, - targetId: { default: "" }, - linkId: { default: "" }, - showPreview: { default: true }, - location: { default: null }, - title: { default: null }, - docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text - }, - inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") }; - } - }], - toDOM(node: any) { - return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : - ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0]; - } - }, - - - // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. - pFontColor: { - attrs: { - color: { default: "#000" } - }, - inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { color: dom.getAttribute("color") }; - } - }], - toDOM(node: any) { - return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; - } - }, - - marker: { - attrs: { - highlight: { default: "transparent" } - }, - inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { highlight: dom.getAttribute("backgroundColor") }; - } - }], - toDOM(node: any) { - return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; - } - }, - - // :: MarkSpec An emphasis mark. Rendered as an `` element. - // Has parse rules that also match `` and `font-style: italic`. - em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }], - toDOM() { return emDOM; } - }, - - // :: MarkSpec A strong mark. Rendered as ``, parse rules - // also match `` and `font-weight: bold`. - strong: { - parseDOM: [{ tag: "strong" }, - { tag: "b" }, - { style: "font-weight" }], - toDOM() { return strongDOM; } - }, - - strikethrough: { - parseDOM: [ - { tag: 'strike' }, - { style: 'text-decoration=line-through' }, - { style: 'text-decoration-line=line-through' } - ], - toDOM: () => ['span', { - style: 'text-decoration-line:line-through' - }] - }, - - subscript: { - excludes: 'superscript', - parseDOM: [ - { tag: 'sub' }, - { style: 'vertical-align=sub' } - ], - toDOM: () => ['sub'] - }, - - superscript: { - excludes: 'subscript', - parseDOM: [ - { tag: 'sup' }, - { style: 'vertical-align=super' } - ], - toDOM: () => ['sup'] - }, - - mbulletType: { - attrs: { - bulletType: { default: "decimal" } - }, - toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` - }]; - } - }, - - metadata: { - toDOM() { - return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; - } - }, - metadataKey: { - toDOM() { - return ['span', { style: 'font-style:italic; ' }]; - } - }, - metadataVal: { - toDOM() { - return ['span']; - } - }, - - summarizeInclusive: { - parseDOM: [ - { - tag: "span", - getAttrs: (p: any) => { - if (typeof (p) !== "string") { - const style = getComputedStyle(p); - if (style.textDecoration === "underline") return null; - if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { - return null; - } - } - return false; - } - }, - ], - inclusive: true, - toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } - }, - - summarize: { - inclusive: false, - parseDOM: [ - { - tag: "span", - getAttrs: (p: any) => { - if (typeof (p) !== "string") { - const style = getComputedStyle(p); - if (style.textDecoration === "underline") return null; - if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { - return null; - } - } - return false; - } - }, - ], - toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } - }, - - underline: { - parseDOM: [ - { - tag: "span", - getAttrs: (p: any) => { - if (typeof (p) !== "string") { - const style = getComputedStyle(p); - if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { - return null; - } - } - return false; - } - } - // { style: "text-decoration=underline" } - ], - toDOM: () => ['span', { - style: 'text-decoration:underline;text-decoration-style:line' - }] - }, - - search_highlight: { - attrs: { - selected: { default: false } - }, - parseDOM: [{ style: 'background: yellow' }], - toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.selected ? "orange" : "yellow"}` - }]; - } - }, - - // the id of the user who entered the text - user_mark: { - attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 - }, - group: "inline", - toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); - const min = Math.round(node.attrs.modified / 12); - const hr = Math.round(min / 60); - const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; - return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; - } - }, - // the id of the user who entered the text - user_tag: { - attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 - tag: { default: "" } - }, - group: "inline", - inclusive: false, - toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; - } - }, - - - // :: MarkSpec Code font mark. Represented as a `` element. - code: { - parseDOM: [{ tag: "code" }], - toDOM() { return codeDOM; } - }, - - /* FONTS */ - pFontFamily: { - attrs: { - family: { default: "Crimson Text" }, - }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - const cstyle = getComputedStyle(dom); - if (cstyle.font) { - if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; - if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; - if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; - if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; - if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; - if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; - } - } - }], - toDOM: (node) => ['span', { - style: `font-family: "${node.attrs.family}";` - }] - }, - - /** FONT SIZES */ - pFontSize: { - attrs: { - fontSize: { default: 10 } - }, - parseDOM: [{ style: 'font-size: 10px;' }], - toDOM: (node) => ['span', { - style: `font-size: ${node.attrs.fontSize}px;` - }] - }, -}; diff --git a/src/client/util/nodes_rts.ts b/src/client/util/nodes_rts.ts deleted file mode 100644 index e7bcf444a..000000000 --- a/src/client/util/nodes_rts.ts +++ /dev/null @@ -1,264 +0,0 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import ParagraphNodeSpec from "./ParagraphNodeSpec"; - -const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; - -// :: Object -// [Specs](#model.NodeSpec) for the nodes defined in this schema. -export const nodes: { [index: string]: NodeSpec } = { - // :: NodeSpec The top level document node. - doc: { - content: "block+" - }, - - footnote: { - group: "inline", - content: "inline*", - inline: true, - attrs: { - visibility: { default: false } - }, - // This makes the view treat the node as a leaf, even though it - // technically has content - atom: true, - toDOM: () => ["footnote", 0], - parseDOM: [{ tag: "footnote" }] - }, - - paragraph: ParagraphNodeSpec, - - // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. - blockquote: { - content: "block+", - group: "block", - defining: true, - parseDOM: [{ tag: "blockquote" }], - toDOM() { return blockquoteDOM; } - }, - - // :: NodeSpec A horizontal rule (`
`). - horizontal_rule: { - group: "block", - parseDOM: [{ tag: "hr" }], - toDOM() { return hrDOM; } - }, - - // :: NodeSpec A heading textblock, with a `level` attribute that - // should hold the number 1 to 6. Parsed and serialized as `

` to - // `

` elements. - heading: { - attrs: { level: { default: 1 } }, - content: "inline*", - group: "block", - defining: true, - parseDOM: [{ tag: "h1", attrs: { level: 1 } }, - { tag: "h2", attrs: { level: 2 } }, - { tag: "h3", attrs: { level: 3 } }, - { tag: "h4", attrs: { level: 4 } }, - { tag: "h5", attrs: { level: 5 } }, - { tag: "h6", attrs: { level: 6 } }], - toDOM(node: any) { return ["h" + node.attrs.level, 0]; } - }, - - // :: NodeSpec A code listing. Disallows marks or non-text inline - // nodes by default. Represented as a `
` element with a
-    // `` element inside of it.
-    code_block: {
-        content: "text*",
-        marks: "",
-        group: "block",
-        code: true,
-        defining: true,
-        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-        toDOM() { return preDOM; }
-    },
-
-    // :: NodeSpec The text node.
-    text: {
-        group: "inline"
-    },
-
-    dashComment: {
-        attrs: {
-            docid: { default: "" },
-        },
-        inline: true,
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }, "←"];
-        },
-    },
-
-    summary: {
-        inline: true,
-        attrs: {
-            visibility: { default: false },
-            text: { default: undefined },
-            textslice: { default: undefined },
-        },
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }];
-        },
-    },
-
-    // :: NodeSpec An inline image (``) node. Supports `src`,
-    // `alt`, and `href` attributes. The latter two default to the empty
-    // string.
-    image: {
-        inline: true,
-        attrs: {
-            src: {},
-            agnostic: { default: null },
-            width: { default: 100 },
-            alt: { default: null },
-            title: { default: null },
-            float: { default: "left" },
-            location: { default: "onRight" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "img[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        // TODO if we don't define toDom, dragging the image crashes. Why?
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["img", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashDoc: {
-        inline: true,
-        attrs: {
-            width: { default: 200 },
-            height: { default: 100 },
-            title: { default: null },
-            float: { default: "right" },
-            location: { default: "onRight" },
-            hidden: { default: false },
-            fieldKey: { default: "" },
-            docid: { default: "" },
-            alias: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashField: {
-        inline: true,
-        attrs: {
-            fieldKey: { default: "" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    video: {
-        inline: true,
-        attrs: {
-            src: {},
-            width: { default: "100px" },
-            alt: { default: null },
-            title: { default: null }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "video[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["video", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    // :: NodeSpec A hard line break, represented in the DOM as `
`. - hard_break: { - inline: true, - group: "inline", - selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { return brDOM; } - }, - - ordered_list: { - ...orderedList, - content: 'list_item+', - group: 'block', - attrs: { - bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, - setFontSize: { default: undefined }, - setFontFamily: { default: "inherit" }, - setFontColor: { default: "inherit" }, - inheritedFontSize: { default: undefined }, - visibility: { default: true }, - indent: { default: undefined } - }, - toDOM(node: Node) { - if (node.attrs.mapStyle === "bullet") return ['ul', 0]; - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; - const ffam = node.attrs.setFontFamily; - const color = node.attrs.setFontColor; - return node.attrs.visibility ? - ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; - } - }, - - bullet_list: { - ...bulletList, - content: 'list_item+', - group: 'block', - // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], - toDOM(node: Node) { - return ['ul', 0]; - } - }, - - list_item: { - attrs: { - bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, - visibility: { default: true } - }, - ...listItem, - content: 'paragraph block*', - toDOM(node: any) { - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; - //return ["li", { class: `${map}` }, 0]; - } - }, -}; \ No newline at end of file diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js deleted file mode 100644 index 269423482..000000000 --- a/src/client/util/prosemirrorPatches.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -var prosemirrorInputRules = require('prosemirror-inputrules'); -var prosemirrorTransform = require('prosemirror-transform'); -var prosemirrorModel = require('prosemirror-model'); - -exports.liftListItem = liftListItem; -exports.sinkListItem = sinkListItem; -exports.wrappingInputRule = wrappingInputRule; -// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool -// Create a command to lift the list item around the selection up into -// a wrapping list. -function liftListItem(itemType) { - return function (tx, dispatch) { - var ref = tx.selection; - var $from = ref.$from; - var $to = ref.$to; - var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); - if (!range) { return false } - if (!dispatch) { return true } - if ($from.node(range.depth - 1).type == itemType) // Inside a parent list - { return liftToOuterList(tx, dispatch, itemType, range) } - else // Outer list node - { return liftOutOfList(tx, dispatch, range) } - } -} - -function liftToOuterList(tr, dispatch, itemType, range) { - var end = range.end, endOfList = range.$to.end(range.depth); - if (end < endOfList) { - // There are siblings after the lifted items, which must become - // children of the last item - tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList, - new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true)); - range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth); - } - dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView()); - return true -} - -function liftOutOfList(tr, dispatch, range) { - var list = range.parent; - // Merge the list items into a single big item - for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) { - pos -= list.child(i).nodeSize; - tr.delete(pos - 1, pos + 1); - } - var $start = tr.doc.resolve(range.start), item = $start.nodeAfter; - var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount; - var parent = $start.node(-1), indexBefore = $start.index(-1); - if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, - item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false } - var start = $start.pos, end = start + item.nodeSize; - // Strip off the surrounding list. At the sides where we're not at - // the end of the list, the existing list is closed. At sides where - // this is the end, it is overwritten to its end. - tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, - new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))) - .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))), - atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1)); - dispatch(tr.scrollIntoView()); - return true -} - -// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool -// Create a command to sink the list item around the selection down -// into an inner list. -function sinkListItem(itemType) { - return function (state, dispatch) { - var ref = state.selection; - var $from = ref.$from; - var $to = ref.$to; - var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); - if (!range) { return false } - var startIndex = range.startIndex; - if (startIndex == 0) { return false } - var parent = range.parent, nodeBefore = parent.child(startIndex - 1); - if (nodeBefore.type != itemType) { return false; } - - if (dispatch) { - var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type; - var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null); - let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))), - nestedBefore ? 3 : 1, 0); - var before = range.start, after = range.end; - dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, - before, after, slice, 1, true)) - .scrollIntoView()); - } - return true - } -} - -function findWrappingOutside(range, type) { - var parent = range.parent; - var startIndex = range.startIndex; - var endIndex = range.endIndex; - var around = parent.contentMatchAt(startIndex).findWrapping(type); - if (!around) { return null } - var outer = around.length ? around[0] : type; - return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null -} - -function findWrappingInside(range, type) { - var parent = range.parent; - var startIndex = range.startIndex; - var endIndex = range.endIndex; - var inner = parent.child(startIndex); - var inside = type.contentMatch.findWrapping(inner.type); - if (!inside) { return null } - var lastType = inside.length ? inside[inside.length - 1] : type; - var innerMatch = lastType.contentMatch; - for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); } - if (!innerMatch || !innerMatch.validEnd) { return null } - return inside -} -function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) { - if (innerRange === void 0) innerRange = range; - let withAttrs = (type) => ({ type: type, attrs: null }); - var around = findWrappingOutside(range, nodeType); - var inner = around && findWrappingInside(innerRange, nodeType); - if (!inner) { return null } - return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs)) -} -function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) { - return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) { - var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; - var tr = state.tr.delete(start, end); - var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs); - if (!wrapping) { return null } - tr.wrap(range, wrapping); - var before = tr.doc.resolve(start - 1).nodeBefore; - if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) && - (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } - return tr - }) -} \ No newline at end of file diff --git a/src/client/util/schema_rts.ts b/src/client/util/schema_rts.ts deleted file mode 100644 index 83561073c..000000000 --- a/src/client/util/schema_rts.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Schema, Slice } from "prosemirror-model"; - -import { nodes } from "./nodes_rts"; -import { marks } from "./marks_rts"; - - -// :: Schema -// This schema rougly corresponds to the document schema used by -// [CommonMark](http://commonmark.org/), minus the list elements, -// which are defined in the [`prosemirror-schema-list`](#schema-list) -// module. -// -// To reuse elements from this schema, extend or read from its -// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). - -export const schema = new Schema({ nodes, marks }); - -const fromJson = schema.nodeFromJSON; - -schema.nodeFromJSON = (json: any) => { - const node = fromJson(json); - if (json.type === schema.nodes.summary.name) { - node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); - } - return node; -}; \ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c02f79187..3624cdb6d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -15,7 +15,7 @@ import './collections/ParentDocumentSelector.scss'; import './DocumentButtonBar.scss'; import { LinkMenu } from "./linking/LinkMenu"; import { DocumentView } from './nodes/DocumentView'; -import { GoogleRef } from "./nodes/FormattedTextBox"; +import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; import { TemplateMenu } from "./TemplateMenu"; import { Template, Templates } from "./Templates"; import React = require("react"); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 172c1864a..70ea955e1 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -8,7 +8,7 @@ import { Scripting } from "../util/Scripting"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import GestureOverlay from "./GestureOverlay"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; +import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; export class InkingControl { @observable static Instance: InkingControl; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8fb67c435..20238985d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -19,7 +19,7 @@ import { DocServer } from '../DocServer'; import { Docs, DocumentOptions } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { HistoryUtil } from '../util/History'; -import RichTextMenu from '../util/RichTextMenu'; +import RichTextMenu from './nodes/formattedText/RichTextMenu'; import { Scripting } from '../util/Scripting'; import SettingsManager from '../util/SettingsManager'; import SharingManager from '../util/SharingManager'; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f4250e96d..eda8e5684 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -11,7 +11,7 @@ import "./CollectionCarouselView.scss"; import { CollectionSubView } from './CollectionSubView'; import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { Doc } from '../../../new_fields/Doc'; -import { FormattedTextBox } from '../nodes/FormattedTextBox'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { ContextMenu } from '../ContextMenu'; import { ObjectField } from '../../../new_fields/ObjectField'; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 49abc6ee6..fb7535d9f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -15,7 +15,7 @@ import { DragManager, dropActionType } from "../../util/DragManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox"; +import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; import { CollectionView } from "./CollectionView"; import React = require("react"); import { basename } from 'path'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 28b461313..92b27a0c6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -32,7 +32,7 @@ import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingControl } from "../../InkingControl"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; import { CollectionDockingView } from "../CollectionDockingView"; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index cd8166309..2d3bb6f3c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,7 +19,7 @@ import React = require("react"); import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { RichTextField } from "../../../../new_fields/RichTextField"; import { CollectionView } from "../CollectionView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { ScriptField } from "../../../../new_fields/ScriptField"; interface MarqueeViewProps { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index cd78ac7b3..4d20d3e2c 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -19,7 +19,7 @@ import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; import { FieldView, FieldViewProps } from "./FieldView"; -import { FormattedTextBox } from "./FormattedTextBox"; +import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss deleted file mode 100644 index 3bedb7127..000000000 --- a/src/client/views/nodes/FormattedTextBox.scss +++ /dev/null @@ -1,265 +0,0 @@ -@import "../globalCssVariables"; - -.ProseMirror { - width: 100%; - height: 100%; - min-height: 100%; -} - -.ProseMirror:focus { - outline: none !important; -} - -.formattedTextBox-cont { - touch-action: none; - cursor: text; - background: inherit; - padding: 0; - border-width: 0px; - border-radius: inherit; - border-color: $intermediate-color; - box-sizing: border-box; - background-color: inherit; - border-style: solid; - overflow-y: auto; - overflow-x: hidden; - color: initial; - max-height: 100%; - display: flex; - flex-direction: row; - transition: opacity 1s; - - .formattedTextBox-dictation { - height: 12px; - width: 10px; - top: 0px; - left: 0px; - position: absolute; - } -} -.formattedTextBox-outer { - position: relative; - overflow: auto; - display: inline-block; - width: 100%; - height: 100%; -} - -.formattedTextBox-sidebar-handle { - position: absolute; - top: calc(50% - 17.5px); - width: 10px; - height: 35px; - background: lightgray; - border-radius: 20px; - cursor:grabbing; -} - -.formattedTextBox-cont>.formattedTextBox-sidebar-handle { - right: 0; - left: unset; -} - -.formattedTextBox-sidebar, -.formattedTextBox-sidebar-inking { - border-left: dashed 1px black; - height: 100%; - display: inline-block; - position: absolute; - right: 0; - - .collectionfreeformview-container { - position: relative; - } - - >.formattedTextBox-sidebar-handle { - right: unset; - left: -5; - } -} - -.formattedTextBox-sidebar-inking { - pointer-events: all; -} - -.formattedTextBox-inner-rounded { - height: 70%; - width: 85%; - position: absolute; - overflow: auto; - top: 15%; - left: 10%; -} - -.formattedTextBox-inner-rounded, -.formattedTextBox-inner { - height: 100%; - white-space: pre-wrap; -} - -// .menuicon { -// display: inline-block; -// border-right: 1px solid rgba(0, 0, 0, 0.2); -// color: #888; -// line-height: 1; -// padding: 0 7px; -// margin: 1px; -// cursor: pointer; -// text-align: center; -// min-width: 1.4em; -// } - -.strong, -.heading { - font-weight: bold; -} - -.em { - font-style: italic; -} - -.userMarkOpen { - background: rgba(255, 255, 0, 0.267); - display: inline; -} - -.userMark { - background: rgba(255, 255, 0, 0.267); - font-size: 2px; - display: inline-grid; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 10px; - min-height: 10px; - text-align: center; - align-content: center; -} - -footnote { - display: inline-block; - position: relative; - cursor: pointer; - - div { - padding: 0 !important; - } -} - -footnote::after { - content: counter(prosemirror-footnote); - vertical-align: super; - font-size: 75%; - counter-increment: prosemirror-footnote; -} - -.ProseMirror { - counter-reset: prosemirror-footnote; -} - -.footnote-tooltip { - cursor: auto; - font-size: 75%; - position: absolute; - left: -30px; - top: calc(100% + 10px); - background: silver; - padding: 3px; - border-radius: 2px; - max-width: 100px; - min-width: 50px; - width: max-content; -} - -.prosemirror-attribution { - font-size: 8px; -} - -.footnote-tooltip::before { - border: 5px solid silver; - border-top-width: 0px; - border-left-color: transparent; - border-right-color: transparent; - position: absolute; - top: -5px; - left: 27px; - content: " "; - height: 0; - width: 0; -} - - -.formattedTextBox-inlineComment { - position: relative; - width: 40px; - height: 20px; - &::before { - content: "→"; - } - &:hover { - background: orange; - } -} - -.formattedTextBox-summarizer { - opacity: 0.5; - position: relative; - width: 40px; - height: 20px; - &::after { - content: "←"; - } -} - -.formattedTextBox-summarizer-collapsed { - opacity: 0.5; - position: relative; - width: 40px; - height: 20px; - &::after { - content: "..."; - } -} - -.ProseMirror { - touch-action: none; - span { - font-family: inherit; - } - - ol, ul { - counter-reset: deci1 0 multi1 0; - padding-left: 1em; - font-family: inherit; - } - ol { - margin-left: 1em; - font-family: inherit; - } - - .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } - .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} - .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} - .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} - .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } - .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } - .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } - - .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } - .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} - .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} - .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} - - .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } - .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } - .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } - .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } - .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } - .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } - .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } - - .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } - .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } - .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } - .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } -} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx deleted file mode 100644 index e65453aa0..000000000 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ /dev/null @@ -1,1306 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { isEqual } from "lodash"; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { baseKeymap } from "prosemirror-commands"; -import { history } from "prosemirror-history"; -import { inputRules } from 'prosemirror-inputrules'; -import { keymap } from "prosemirror-keymap"; -import { Fragment, Mark, Node, Slice } from "prosemirror-model"; -import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; -import { ReplaceStep } from 'prosemirror-transform'; -import { EditorView } from "prosemirror-view"; -import { DateField } from '../../../new_fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { InkTool } from '../../../new_fields/InkField'; -import { PrefetchProxy } from '../../../new_fields/Proxy'; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { RichTextUtils } from '../../../new_fields/RichTextUtils'; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { TraceMobx } from '../../../new_fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../Utils'; -import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; -import { DocServer } from "../../DocServer"; -import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { DictationManager } from '../../util/DictationManager'; -import { DragManager } from "../../util/DragManager"; -import { makeTemplate } from '../../util/DropConverter'; -import buildKeymap from "../../util/ProsemirrorExampleTransfer"; -import RichTextMenu from '../../util/RichTextMenu'; -import { RichTextRules } from "../../util/RichTextRules"; -// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "../../util/RichTextSchema"; -// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "../../util/RichTextSchema"; -import { OrderedListView } from "../../util/RichTextSchema"; -import { ImageResizeView } from "../../util/ImageResizeView"; - -import { DashDocCommentView } from "../../util/DashDocCommentView"; -import { DashFieldView } from "../../util/DashFieldView"; -import { FootnoteView } from "../../util/FootnoteView"; -import { SummaryView } from "../../util/SummaryView"; -import { DashDocView } from "../../util/DashDocView"; - -import { schema } from "../../util/schema_rts"; -import { SelectionManager } from "../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; -import { DocumentButtonBar } from '../DocumentButtonBar'; -import { InkingControl } from "../InkingControl"; -import { AudioBox } from './AudioBox'; -import { FieldView, FieldViewProps } from "./FieldView"; -import "./FormattedTextBox.scss"; -import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; -import React = require("react"); - -library.add(faEdit); -library.add(faSmile, faTextHeight, faUpload); - -export interface FormattedTextBoxProps { - hideOnLeave?: boolean; - makeLink?: () => Opt; - xMargin?: number; - yMargin?: number; -} - -const richTextSchema = createSchema({ - documentText: "string" -}); - -export const GoogleRef = "googleDocId"; - -type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; -const RichTextDocument = makeInterface(richTextSchema, documentSchema); - -type PullHandler = (exportState: Opt, dataDoc: Doc) => void; - -@observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } - public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); - public static Instance: FormattedTextBox; - public ProseRef?: HTMLDivElement; - private _ref: React.RefObject = React.createRef(); - private _scrollRef: React.RefObject = React.createRef(); - private _editorView: Opt; - private _applyingChange: boolean = false; - private _searchIndex = 0; - private _sidebarMovement = 0; - private _lastX = 0; - private _lastY = 0; - private _undoTyping?: UndoManager.Batch; - private _disposers: { [name: string]: IReactionDisposer } = {}; - private dropDisposer?: DragManager.DragDropDisposer; - - @computed get _recording() { return this.dataDoc.audioState === "recording"; } - set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } - - @observable private _entered = false; - - public static FocusedBox: FormattedTextBox | undefined; - public static SelectOnLoad = ""; - public static SelectOnLoadChar = ""; - public static IsFragment(html: string) { - return html.indexOf("data-pm-slice") !== -1; - } - public static GetHref(html: string): string { - const parser = new DOMParser(); - const parsedHtml = parser.parseFromString(html, 'text/html'); - if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && - (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { - return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; - } - return ""; - } - public static GetDocFromUrl(url: string) { - if (url.startsWith(document.location.origin)) { - const split = new URL(url).pathname.split("doc/"); - const docid = split[split.length - 1]; - return docid; - } - return ""; - } - - @undoBatch - public setFontColor(color: string) { - const view = this._editorView!; - if (view.state.selection.from === view.state.selection.to) return false; - if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.layoutDoc.color = color; - } - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); - view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); - return true; - } - - constructor(props: any) { - super(props); - FormattedTextBox.Instance = this; - this.updateHighlights(); - } - - public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - - linkOnDeselect: Map = new Map(); - - doLinkOnDeselect() { - Array.from(this.linkOnDeselect.entries()).map(entry => { - const key = entry[0]; - const value = entry[1]; - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); - DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); - if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); - }); - }); - }); - this.linkOnDeselect.clear(); - } - - dispatchTransaction = (tx: Transaction) => { - if (this._editorView) { - const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); - if (metadata) { - const range = tx.selection.$from.blockRange(tx.selection.$to); - let text = range ? tx.doc.textBetween(range.start, range.end) : ""; - let textEndSelection = tx.selection.to; - for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } - text = text.substr(0, textEndSelection - range!.start); - text = text.split(" ")[text.split(" ").length - 1]; - const split = text.split("::"); - if (split.length > 1 && split[1]) { - const key = split[0]; - const value = split[split.length - 1]; - this.linkOnDeselect.set(key, value); - - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value }); - const mval = this._editorView.state.schema.marks.metadataVal.create(); - const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); - tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); - this.dataDoc[key] = value; - } - } - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); - (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks); - - const tsel = this._editorView.state.selection.$from; - tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); - const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); - if (!this._applyingChange) { - this._applyingChange = true; - this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited - } else { // if we've deleted all the text in a note driven by a template, then restore the template data - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data))); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have - } - this._applyingChange = false; - } - this.updateTitle(); - this.tryUpdateHeight(); - } - } - - updateTitle = () => { - if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { - const str = this._editorView.state.doc.textContent; - const titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); - } - } - - // needs a better API for taking in a set of words with target documents instead of just one target - public hyperlinkTerms = (terms: string[], target: Doc) => { - if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - const 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); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!; - const link = this._editorView.state.schema.marks.link.create({ - href: Utils.prepend("/doc/" + alink[Id]), - title: "a link", location: location, linkId: alink[Id], targetId: target[Id] - }); - this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); - } - } - public highlightSearchTerms = (terms: string[]) => { - 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)); - 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()); - } - } - - public unhighlightSearchTerms = () => { - if (this._editorView && (this._editorView as any).docView) { - 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 end = this._editorView.state.doc.nodeSize - 2; - this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); - } - } - adoptAnnotation = (start: number, end: number, mark: Mark) => { - const view = this._editorView!; - const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); - view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); - } - protected createDropTarget = (ele: HTMLDivElement) => { - this.ProseRef = ele; - this.dropDisposer?.(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); - } - - @undoBatch - @action - drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0]; - // replace text contents whend dragging with Alt - if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { - if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); - e.stopPropagation(); - } - // embed document when dragging with a userDropAction or an embedDoc flag set - } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) { - const target = de.complete.docDragData.droppedDocuments[0]; - // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - // if (link) { - target._fitToBox = true; - const node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], - float: "right" - }); - const view = this._editorView!; - view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); - this.tryUpdateHeight(); - e.stopPropagation(); - // } - } // otherwise, fall through to outer collection to handle drop - } else if (de.complete.linkDragData) { - de.complete.linkDragData.linkDropCallback = this.linkDrop; - } - } - linkDrop = (data: DragManager.LinkDragData) => { - const linkDoc = data.linkDocument!; - const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; - const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; - this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id); - } - - getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { - let offset = 0; - - if (context === node) return { from: offset, to: offset + node.nodeSize }; - - if (node.isBlock) { - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < (context.content as any).content.length; i++) { - const result = this.getNodeEndpoints((context.content as any).content[i], node); - if (result) { - return { - from: result.from + offset + (context.type.name === "doc" ? 0 : 1), - to: result.to + offset + (context.type.name === "doc" ? 0 : 1) - }; - } - offset += (context.content as any).content[i].nodeSize; - } - return null; - } else { - return null; - } - } - - - //Recursively finds matches within a given node - findInNode(pm: EditorView, node: Node, find: string) { - let ret: TextSelection[] = []; - - if (node.isTextblock) { - let index = 0, foundAt; - const ep = this.getNodeEndpoints(pm.state.doc, node); - while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { - const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); - ret.push(sel); - index = index + foundAt + find.length; - } - } else { - node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); - } - return ret; - } - static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; - - updateHighlights = () => { - clearStyleSheetRules(FormattedTextBox._userStyleSheet); - if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); - } - if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); - } - if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); - } - if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); - } - if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); - } - if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); - } - if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); - const min = Math.round(Date.now() / 1000 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); - setTimeout(() => this.updateHighlights()); - } - if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); - const hr = Math.round(Date.now() / 1000 / 60 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); - } - } - - sidebarDown = (e: React.PointerEvent) => { - this._lastX = e.clientX; - this._lastY = e.clientY; - this._sidebarMovement = 0; - document.addEventListener("pointermove", this.sidebarMove); - document.addEventListener("pointerup", this.sidebarUp); - e.stopPropagation(); - e.preventDefault(); // prevents text from being selected during drag - } - sidebarMove = (e: PointerEvent) => { - const bounds = this.CurrentDiv.getBoundingClientRect(); - this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY)); - this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; - } - sidebarUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.sidebarMove); - document.removeEventListener("pointerup", this.sidebarUp); - } - - toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"); - - public static get DefaultLayout(): Doc | string | undefined { - return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); - } - specificContextMenu = (e: React.MouseEvent): void => { - const cm = ContextMenu.Instance; - - const funcs: ContextMenuProps[] = []; - this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); - funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - !this.props.Document.rootDocument && funcs.push({ - description: "Make Template", event: () => { - this.props.Document.isTemplateDoc = makeTemplate(this.props.Document); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); - }, icon: "eye" - }); - funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); - - const highlighting: ContextMenuProps[] = []; - ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => - highlighting.push({ - description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { - e.stopPropagation(); - if (FormattedTextBox._highlights.indexOf(option) === -1) { - FormattedTextBox._highlights.push(option); - } else { - FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); - } - this.updateHighlights(); - }, icon: "expand-arrows-alt" - })); - funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); - - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - - const change = cm.findByDescription("Change Perspective..."); - const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : []; - - const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); - DocListCast(noteTypesDoc?.data).forEach(note => { - changeItems.push({ - description: StrCast(note.title), event: undoBatch(() => { - Doc.setNativeView(this.props.Document); - Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); - }), icon: "eye" - }); - }); - changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" }); - !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); - - const open = cm.findByDescription("Add a Perspective..."); - const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; - - openItems.push({ - description: "FreeForm", event: undoBatch(() => { - const alias = Doc.MakeAlias(this.rootDoc); - Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform"); - this.props.addDocTab(alias, "onRight"); - }), icon: "eye" - }); - !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" }); - - } - - recordDictation = () => { - DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous: { indefinite: false }, - }).then(results => { - if (results && [DictationManager.Controls.Infringed].includes(results)) { - DictationManager.Controls.stop(); - } - //this._editorView!.focus(); - }); - } - stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; - - @action - toggleMenubar = () => { - this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; - } - - recordBullet = async () => { - const completedCue = "end session"; - const results = await DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous: { indefinite: false }, - terminators: [completedCue, "bullet", "next"] - }); - if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { - DictationManager.Controls.stop(); - return; - } - this.nextBullet(this._editorView!.state.selection.to); - setTimeout(this.recordBullet, 2000); - } - - setCurrentBulletContent = (value: string) => { - if (this._editorView) { - const state = this._editorView.state; - const now = Date.now(); - let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); - if (!this._break && state.selection.to !== state.selection.from) { - for (let i = state.selection.from; i <= state.selection.to; i++) { - const pos = state.doc.resolve(i); - const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark); - if (um) { - mark = um; - break; - } - } - } - const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime(); - this._break = false; - value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; - const from = state.selection.from; - const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); - this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1))); - } - } - - nextBullet = (pos: number) => { - if (this._editorView) { - const frag = Fragment.fromArray(this.newListItems(2)); - if (this._editorView.state.doc.resolve(pos).depth >= 2) { - const slice = new Slice(frag, 2, 2); - let state = this._editorView.state; - this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); - pos += 4; - state = this._editorView.state; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); - } - } - } - - private newListItems = (count: number) => { - return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); - } - - _keymap: any = undefined; - _rules: RichTextRules | undefined; - @computed get config() { - this._keymap = buildKeymap(schema, this.props); - this._rules = new RichTextRules(this.props.Document, this); - return { - schema, - plugins: [ - inputRules(this._rules.inpRules), - this.richTextMenuPlugin(), - history(), - keymap(this._keymap), - keymap(baseKeymap), - new Plugin({ - props: { - attributes: { class: "ProseMirror-example-setup-style" } - } - }), - formattedTextBoxCommentPlugin - ] - }; - } - - makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) { - if (this._editorView) { - const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); - this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link). - addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link)); - } - } - componentDidMount() { - this._disposers.buttonBar = reaction( - () => DocumentButtonBar.Instance, - instance => { - if (instance) { - this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); - } - } - ); - this._disposers.linkMaker = reaction( - () => this.props.makeLink?.(), - (linkDoc: Opt) => { - if (linkDoc) { - const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; - const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; - this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id); - } - }, - { fireImmediately: true } - ); - this._disposers.editorState = reaction( - () => { - if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { - return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; - } - return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; - }, - incomingValue => { - if (incomingValue !== undefined && this._editorView && !this._applyingChange) { - const updatedState = JSON.parse(incomingValue); - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); - } - } - ); - this._disposers.pullDoc = reaction( - () => this.props.Document[Pulls], - () => { - if (!DocumentButtonBar.hasPulledHack) { - DocumentButtonBar.hasPulledHack = true; - const unchanged = this.dataDoc.unchanged; - this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); - } - } - ); - this._disposers.pushDoc = reaction( - () => this.props.Document[Pushes], - () => { - if (!DocumentButtonBar.hasPushedHack) { - DocumentButtonBar.hasPushedHack = true; - this.pushToGoogleDoc(); - } - } - ); - this._disposers.height = reaction( - () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], - () => this.tryUpdateHeight() - ); - - this.setupEditor(this.config, this.props.fieldKey); - - this._disposers.search = reaction(() => this.rootDoc.searchMatch, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), - { fireImmediately: true }); - - this._disposers.record = reaction(() => this._recording, - () => { - if (this._recording) { - setTimeout(action(() => { - this.stopDictation(true); - setTimeout(() => this.recordDictation(), 500); - }), 500); - } else setTimeout(() => this.stopDictation(true), 0); - } - ); - this._disposers.scrollToRegion = reaction( - () => StrCast(this.layoutDoc.scrollToLinkID), - async (scrollToLinkID) => { - const findLinkFrag = (frag: Fragment, editor: EditorView) => { - const nodes: Node[] = []; - frag.forEach((node, index) => { - const examinedNode = findLinkNode(node, editor); - if (examinedNode && examinedNode.textContent) { - nodes.push(examinedNode); - start += index; - } - }); - return { frag: Fragment.fromArray(nodes), start: start }; - }; - const findLinkNode = (node: Node, editor: EditorView) => { - if (!node.isText) { - const content = findLinkFrag(node.content, editor); - return node.copy(content.frag); - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); - return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; - }; - - let start = -1; - if (this._editorView && scrollToLinkID) { - const editor = this._editorView; - const ret = findLinkFrag(editor.state.doc.content, editor); - - if (ret.frag.size > 2 && ret.start >= 0) { - let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start - if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected - } - editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); - setTimeout(() => this.unhighlightSearchTerms(), 2000); - } - Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); - } - - }, - { fireImmediately: true } - ); - this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), - pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } - ); - - setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0))); - } - - pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { - const modes = GoogleApiClientUtils.Docs.WriteMode; - let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); - if (!reference) { - mode = modes.Insert; - reference = { title: StrCast(this.dataDoc.title) }; - } - const redo = async () => { - if (this._editorView && reference) { - const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); - const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); - response && (this.dataDoc[GoogleRef] = response.documentId); - const pushSuccess = response !== undefined && !("errors" in response); - dataDoc.unchanged = pushSuccess; - DocumentButtonBar.Instance.startPushOutcome(pushSuccess); - } - }; - const undo = () => { - if (!exportState) { - return; - } - const content: GoogleApiClientUtils.Docs.Content = { - text: exportState.text, - requests: [] - }; - if (reference && content) { - GoogleApiClientUtils.Docs.write({ reference, content, mode }); - } - }; - UndoManager.AddEvent({ undo, redo }); - redo(); - }); - } - - pullFromGoogleDoc = async (handler: PullHandler) => { - const dataDoc = this.dataDoc; - const documentId = StrCast(dataDoc[GoogleRef]); - let exportState: Opt; - if (documentId) { - exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); - } - UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); - } - - updateState = (exportState: Opt, dataDoc: Doc) => { - let pullSuccess = false; - if (exportState !== undefined) { - pullSuccess = true; - dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); - setTimeout(() => { - if (this._editorView) { - const state = this._editorView.state; - const end = state.doc.content.size - 1; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); - } - }, 0); - dataDoc.title = exportState.title; - this.rootDoc.customTitle = true; - dataDoc.unchanged = true; - } else { - delete dataDoc[GoogleRef]; - } - DocumentButtonBar.Instance.startPullOutcome(pullSuccess); - } - - checkState = (exportState: Opt, dataDoc: Doc) => { - if (exportState && this._editorView) { - const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); - const equalTitles = dataDoc.title === exportState.title; - const unchanged = equalContent && equalTitles; - dataDoc.unchanged = unchanged; - DocumentButtonBar.Instance.setPullState(unchanged); - } - } - - clipboardTextSerializer = (slice: Slice): string => { - let text = "", separated = true; - const from = 0, to = slice.content.size; - slice.content.nodesBetween(from, to, (node, pos) => { - if (node.isText) { - text += node.text!.slice(Math.max(from, pos) - pos, to - pos); - separated = false; - } else if (!separated && node.isBlock) { - text += "\n"; - separated = true; - } else if (node.type.name === "hard_break") { - text += "\n"; - } - }, 0); - return text; - } - - sliceSingleNode(slice: Slice) { - return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null; - } - - handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { - const cbe = event as ClipboardEvent; - const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); - const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); - if (pdfDocId && pdfRegionId) { - DocServer.GetRefField(pdfDocId).then(pdfDoc => { - DocServer.GetRefField(pdfRegionId).then(pdfRegion => { - if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { - setTimeout(async () => { - const targetField = Doc.LayoutFieldKey(pdfDoc); - const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations - targetAnnotations?.push(pdfRegion); - }); - - const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); - if (link) { - cbe.clipboardData!.setData("dash/linkDoc", link[Id]); - const linkId = link[Id]; - const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); - slice = new Slice(frag, slice.openStart, slice.openEnd); - const tr = view.state.tr.replaceSelection(slice); - view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); - } - } - }); - }); - return true; - } - return false; - - - function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { - const nodes: Node[] = []; - frag.forEach(node => nodes.push(marker(node))); - return Fragment.fromArray(nodes); - } - function addLinkMark(node: Node, title: string, linkId: string) { - if (!node.isText) { - const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); - return node.copy(content); - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type.name === "link"); - const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true }); - marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); - return node.mark(marks); - } - } - - private setupEditor(config: any, fieldKey: string) { - const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); - const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"]; - const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); - if (this.ProseRef) { - const self = this; - this._editorView?.destroy(); - this._editorView = new EditorView(this.ProseRef, { - state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), - handleScrollToSelection: (editorView) => { - const docPos = editorView.coordsAtPos(editorView.state.selection.from); - const viewRect = self._ref.current!.getBoundingClientRect(); - if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) { - docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale); - } - return true; - }, - dispatchTransaction: this.dispatchTransaction, - nodeViews: { - //dashComment(node, view, getPos) { return new DashDocCommentView({ node, view, getPos }); }, - dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, - //dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, - // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); }, - - // image(node, view, getPos) { - // //const addDocTab = this.props.addDocTab; - // return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab }); - // }, - - - // // WAS : - // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); }, - - // summary(node, view, getPos) { return new SummaryView({ node, view, getPos }); }, - // ordered_list(node, view, getPos) { return new OrderedListView(); }, - // footnote(node, view, getPos) { return new FootnoteView({ node, outerView, getPos }); } - }, - clipboardTextSerializer: this.clipboardTextSerializer, - handlePaste: this.handlePaste, - }); - const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); - if (startupText) { - const { state: { tr }, dispatch } = this._editorView; - dispatch(tr.insertText(startupText)); - } - } - - const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad; - if (selectOnLoad && !this.props.dontRegisterView) { - FormattedTextBox.SelectOnLoad = ""; - this.props.select(false); - FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar)); - FormattedTextBox.SelectOnLoadChar = ""; - - } - (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); - // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. - this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; - } - getFont(font: string) { - switch (font) { - case "Arial": return schema.marks.arial.create(); - case "Times New Roman": return schema.marks.timesNewRoman.create(); - case "Georgia": return schema.marks.georgia.create(); - case "Comic Sans MS": return schema.marks.comicSans.create(); - case "Tahoma": return schema.marks.tahoma.create(); - case "Impact": return schema.marks.impact.create(); - case "ACrimson Textrial": return schema.marks.crimson.create(); - } - return schema.marks.arial.create(); - } - - componentWillUnmount() { - Object.values(this._disposers).forEach(disposer => disposer?.()); - this._editorView?.destroy(); - } - - static _downEvent: any; - _downX = 0; - _downY = 0; - _break = false; - onPointerDown = (e: React.PointerEvent): void => { - if (this._recording && !e.ctrlKey && e.button === 0) { - this.stopDictation(true); - this._break = true; - const state = this._editorView!.state; - const to = state.selection.to; - const updated = TextSelection.create(state.doc, to, to); - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to)); - e.preventDefault(); - e.stopPropagation(); - if (this._recording) setTimeout(() => this.recordDictation(), 500); - } - this._downX = e.clientX; - this._downY = e.clientY; - this.doLinkOnDeselect(); - FormattedTextBox._downEvent = true; - FormattedTextBoxComment.textBox = this; - if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { - e.preventDefault(); - } - if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar - e.stopPropagation(); - } - } - if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - e.preventDefault(); - } - } - - onPointerUp = (e: React.PointerEvent): void => { - if (!FormattedTextBox._downEvent) return; - FormattedTextBox._downEvent = false; - if (!(e.nativeEvent as any).formattedHandled) { - FormattedTextBoxComment.textBox = this; - FormattedTextBoxComment.update(this._editorView!); - } - (e.nativeEvent as any).formattedHandled = true; - - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { - e.stopPropagation(); - } - this._downX = this._downY = Number.NaN; - } - - @action - onFocused = (e: React.FocusEvent): void => { - FormattedTextBox.FocusedBox = this; - this.tryUpdateHeight(); - - // see if we need to preserve the insertion point - const prosediv = this.ProseRef?.children?.[0] as any; - const keeplocation = prosediv?.keeplocation; - prosediv && (prosediv.keeplocation = undefined); - const pos = this._editorView?.state.selection.$from.pos || 1; - keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); - const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos); - - // jump rich text menu to this textbox - const bounds = this._ref.current?.getBoundingClientRect(); - if (bounds && this.props.Document._chromeStatus !== "disabled") { - const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); - let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); - if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { - y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); - } - RichTextMenu.Instance.jumpTo(x, y); - } - } - onPointerWheel = (e: React.WheelEvent): void => { - // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time - if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - e.stopPropagation(); - } - } - - static _bulletStyleSheet: any = addStyleSheet(); - static _userStyleSheet: any = addStyleSheet(); - - onClick = (e: React.MouseEvent): void => { - if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. - const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) - if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); - e.preventDefault(); - } - if (!node && this.ProseRef) { - const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div - if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); - } - } - } - if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } - (e.nativeEvent as any).formattedHandled = true; - // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) { - // let href = (e.target as any).href; - // let location: string; - // if ((e.target as any).attributes.location) { - // location = (e.target as any).attributes.location.value; - // } - // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); - // if (node) { - // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). - // href = link && link.attrs.href; - // location = link && link.attrs.location; - // } - // } - // if (href) { - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - // if (linkClicked) { - // DocServer.GetRefField(linkClicked).then(async linkDoc => { - // (linkDoc instanceof Doc) && - // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false); - // }); - // } - // } else { - // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); - // this.props.addDocument && this.props.addDocument(webDoc); - // } - // e.stopPropagation(); - // e.preventDefault(); - // } - // } - - if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) { - this.props.select(e.ctrlKey); - this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); - } - } - - // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. - hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) { - clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); - const pos = this._editorView!.posAtCoords({ left: x, top: y }); - if (pos && this.props.isSelected(true)) { - // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined; - //const node = this._editorView!.state.doc.nodeAt(pos.pos); - const $pos = this._editorView!.state.doc.resolve(pos.pos); - let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined; - if ($pos.node().type === schema.nodes.ordered_list) { - for (let off = 1; off < 100; off++) { - const pos = this._editorView!.posAtCoords({ left: x + off, top: y }); - const node = pos && this._editorView!.state.doc.nodeAt(pos.pos); - if (node?.type === schema.nodes.list_item) { - list_node = node; - break; - } - } - } - if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { - if (select) { - const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); - if (!highlightOnly) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos))); - } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); - } else if (Math.abs(pos.pos - pos.inside) < 2) { - if (!highlightOnly) { - const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0; - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility })); - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset))); - } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); - } - } - } - } - onMouseUp = (e: React.MouseEvent): void => { - e.stopPropagation(); - - const view = this._editorView as any; - // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there - // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. - if (view.mouseDown) { - const originalUpHandler = view.mouseDown.up; - view.root.removeEventListener("mouseup", originalUpHandler); - view.mouseDown.up = (e: MouseEvent) => { - !(e as any).formattedHandled && originalUpHandler(e); - // e.stopPropagation(); - (e as any).formattedHandled = true; - }; - view.root.addEventListener("mouseup", view.mouseDown.up); - } - } - - richTextMenuPlugin() { - return new Plugin({ - view(newView) { - RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); - return RichTextMenu.Instance; - } - }); - } - - public static HadSelection: boolean = false; - onBlur = (e: any) => { - FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; - //DictationManager.Controls.stop(false); - if (this._undoTyping) { - this._undoTyping.end(); - this._undoTyping = undefined; - } - this.doLinkOnDeselect(); - - // move the richtextmenu offscreen - if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300); - } - - _lastTimedMark: Mark | undefined = undefined; - onKeyPress = (e: React.KeyboardEvent) => { - if (e.altKey) { - e.preventDefault(); - return; - } - const state = this._editorView!.state; - if (!state.selection.empty && e.key === "%") { - this._rules!.EnteringStyle = true; - e.preventDefault(); - e.stopPropagation(); - return; - } - - if (state.selection.empty || !this._rules!.EnteringStyle) { - this._rules!.EnteringStyle = false; - } - if (e.key === "Escape") { - this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur?.(); - SelectionManager.DeselectAll(); - } - e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { - e.preventDefault(); - } - const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); - this._lastTimedMark = mark; - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); - - if (!this._undoTyping) { - this._undoTyping = UndoManager.StartBatch("undoTyping"); - } - } - - onscrolled = (ev: React.UIEvent) => { - this.props.Document.scrollPos = this._scrollRef.current!.scrollTop; - } - @action - tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this._ref.current?.scrollHeight; - if (this.layoutDoc._autoHeight && scrollHeight && - getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - if (limitHeight && scrollHeight > limitHeight) { - scrollHeight = limitHeight; - this.layoutDoc.limitHeight = undefined; - this.layoutDoc._autoHeight = false; - } - const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); - const dh = NumCast(this.layoutDoc._height, 0); - const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); - if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle - this.layoutDoc._height = newHeight; - this.dataDoc._nativeHeight = nh ? scrollHeight : undefined; - } - } - } - - @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0); - @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } - render() { - TraceMobx(); - const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; - if (this.props.isSelected()) { - this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props); - } else if (FormattedTextBoxComment.textBox === this) { - FormattedTextBoxComment.Hide(); - } - return ( - -
this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)} - onBlur={this.onBlur} - onPointerUp={this.onPointerUp} - onPointerDown={this.onPointerDown} - onMouseUp={this.onMouseUp} - onWheel={this.onPointerWheel} - onPointerEnter={action(() => this._entered = true)} - onPointerLeave={action((e: React.PointerEvent) => { - this._entered = false; - const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); - for (let child: any = target; child; child = child?.parentElement) { - if (child === this._ref.current!) { - this._entered = true; - } - } - })} - > -
-
-
- {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? -
this.toggleSidebar()} /> : -
- - -
this.toggleSidebar()} /> -
} - {!this.props.Document._showAudio ? (null) : -
{ - runInAction(() => this._recording = !this._recording); - setTimeout(() => this._editorView!.focus(), 500); - e.stopPropagation(); - }} > - -
} -
- ); - } -} diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss deleted file mode 100644 index 2dd63ec21..000000000 --- a/src/client/views/nodes/FormattedTextBoxComment.scss +++ /dev/null @@ -1,33 +0,0 @@ -.FormattedTextBox-tooltip { - position: absolute; - pointer-events: none; - z-index: 20; - background: white; - border: 1px solid silver; - border-radius: 2px; - margin-bottom: 7px; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); - } - .FormattedTextBox-tooltip:before { - content: ""; - height: 0; width: 0; - position: absolute; - left: 50%; - margin-left: -5px; - bottom: -6px; - border: 5px solid transparent; - border-bottom-width: 0; - border-top-color: silver; - } - .FormattedTextBox-tooltip:after { - content: ""; - height: 0; width: 0; - position: absolute; - left: 50%; - margin-left: -5px; - bottom: -4.5px; - border: 5px solid transparent; - border-bottom-width: 0; - border-top-color: white; - } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx deleted file mode 100644 index dfea0f6bb..000000000 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState, Plugin } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import { Doc, DocCastAsync } from "../../../new_fields/Doc"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils"; -import { DocServer } from "../../DocServer"; -import { DocumentManager } from "../../util/DocumentManager"; -import { schema } from "../../util/schema_rts"; -import { Transform } from "../../util/Transform"; -import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; -import { FormattedTextBox } from "./FormattedTextBox"; -import './FormattedTextBoxComment.scss'; -import React = require("react"); -import { Docs } from "../../documents/Documents"; -import wiki from "wikijs"; -import { DocumentType } from "../../documents/DocumentTypes"; - -export let formattedTextBoxCommentPlugin = new Plugin({ - view(editorView) { return new FormattedTextBoxComment(editorView); } -}); -export function findOtherUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); -} -export function findUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid); -} -export function findLinkMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.type === schema.marks.link); -} -export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let before = 0; - let nbef = rpos.nodeBefore; - while (nbef && finder(nbef.marks)) { - before += nbef.nodeSize; - rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); - rpos && (nbef = rpos.nodeBefore); - } - return before; -} -export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let after = 0; - let naft = rpos.nodeAfter; - while (naft && finder(naft.marks)) { - after += naft.nodeSize; - rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); - rpos && (naft = rpos.nodeAfter); - } - return after; -} - - -export class FormattedTextBoxComment { - static tooltip: HTMLElement; - static tooltipText: HTMLElement; - static tooltipInput: HTMLInputElement; - static start: number; - static end: number; - static mark: Mark; - static textBox: FormattedTextBox | undefined; - static linkDoc: Doc | undefined; - constructor(view: any) { - if (!FormattedTextBoxComment.tooltip) { - const root = document.getElementById("root"); - FormattedTextBoxComment.tooltipInput = document.createElement("input"); - FormattedTextBoxComment.tooltipInput.type = "checkbox"; - FormattedTextBoxComment.tooltip = document.createElement("div"); - FormattedTextBoxComment.tooltipText = document.createElement("div"); - FormattedTextBoxComment.tooltipText.style.width = "100%"; - FormattedTextBoxComment.tooltipText.style.height = "100%"; - FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; - FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; - FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; - FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; - FormattedTextBoxComment.tooltip.style.width = "100%"; - FormattedTextBoxComment.tooltip.style.height = "100%"; - FormattedTextBoxComment.tooltip.style.overflow = "hidden"; - FormattedTextBoxComment.tooltip.style.display = "none"; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); - FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { - const keep = e.target && (e.target as any).type === "checkbox" ? true : false; - const textBox = FormattedTextBoxComment.textBox; - if (FormattedTextBoxComment.linkDoc && !keep && textBox) { - if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); - } else { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, - (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); - } - } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight"); - } - keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( - FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); - e.stopPropagation(); - e.preventDefault(); - }; - root && root.appendChild(FormattedTextBoxComment.tooltip); - } - } - - public static Hide() { - FormattedTextBoxComment.textBox = undefined; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } - public static SetState(textBox: any, start: number, end: number, mark: Mark) { - FormattedTextBoxComment.textBox = textBox; - FormattedTextBoxComment.start = start; - FormattedTextBoxComment.end = end; - FormattedTextBoxComment.mark = mark; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); - } - - static update(view: EditorView, lastState?: EditorState) { - const state = view.state; - // Don't do anything if the document/selection didn't change - if (lastState && lastState.doc.eq(state.doc) && - lastState.selection.eq(state.selection)) { - return; - } - FormattedTextBoxComment.linkDoc = undefined; - - const textBox = FormattedTextBoxComment.textBox; - if (!textBox || !textBox.props) { - return; - } - let set = "none"; - let nbef = 0; - FormattedTextBoxComment.tooltipInput.style.display = "none"; - FormattedTextBoxComment.tooltip.style.width = ""; - FormattedTextBoxComment.tooltip.style.height = ""; - (FormattedTextBoxComment.tooltipText as any).href = ""; - FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; - FormattedTextBoxComment.tooltipText.style.overflow = ""; - // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date - if (state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); - const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const noselection = view.state.selection.$from === view.state.selection.$to; - let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - const mark = child && findOtherUserMark(child.marks); - if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); - } - if (mark && child && ((nbef && naft) || !noselection)) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); - set = ""; - FormattedTextBoxComment.tooltipInput.style.display = ""; - } - } - // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. - if (set === "none" && state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findLinkMark); - const naft = findEndOfMark(state.selection.$from, view, findLinkMark); - let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - const mark = child && findLinkMark(child.marks); - if (mark && child && nbef && naft && mark.attrs.showPreview) { - FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; - (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; - if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) { - wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); - } else { - FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; - FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; - } - if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { - FormattedTextBoxComment.tooltipText.textContent = "target not found..."; - (FormattedTextBoxComment.tooltipText as any).href = ""; - const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { } - docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { - if (linkDoc instanceof Doc) { - (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; - FormattedTextBoxComment.linkDoc = linkDoc; - const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); - const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; - if (anchor !== target && anchor && target) { - target.scrollY = NumCast(anchor?.y); - } - if (target) { - ReactDOM.render( Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => Math.min(250, NumCast(target._height, 250))} - focus={emptyFunction} - whenActiveChanged={returnFalse} - />, FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; - FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; - } - // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... - // let text = ext && StrCast(ext.text); - // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); - } - }); - } - set = ""; - } - } - if (set !== "none") { - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - const left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; - } - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); - } - - destroy() { } -} diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx new file mode 100644 index 000000000..d94fe7fc6 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -0,0 +1,95 @@ +import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { keymap } from "prosemirror-keymap"; +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; +import { StepMap } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import * as ReactDOM from 'react-dom'; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { List } from "../../../../new_fields/List"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; + +import React = require("react"); + +import { schema } from "./schema_rts"; + +interface IDashDocCommentView { + node: any; + view: any; + getPos: any; +} + +export class DashDocCommentView extends React.Component{ + constructor(props: IDashDocCommentView) { + super(props); + } + + targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor + for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) { + const m = this.props.view.state.doc.nodeAt(i); + if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) { + return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; + } + } + const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" }); + this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc)); + setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0); + return undefined; + } + + onPointerDownCollapse = (e: any) => e.stopPropagation(); + + onPointerUpCollapse = (e: any) => { + const target = this.targetNode(); + if (target) { + const expand = target.hidden; + const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); + this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs + setTimeout(() => { + expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); + try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { } + }, 0); + } + e.stopPropagation(); + } + + onPointerEnterCollapse = (e: any) => { + DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + e.preventDefault(); + e.stopPropagation(); + } + + onPointerLeaveCollapse = (e: any) => { + DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); + e.preventDefault(); + e.stopPropagation(); + } + + render() { + + const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid; + + return ( + + + + ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx new file mode 100644 index 000000000..9fe8fa320 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -0,0 +1,269 @@ +import { IReactionDisposer, reaction } from "mobx"; +import { NodeSelection } from "prosemirror-state"; +import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { Docs } from "../../../documents/Documents"; +import { DocumentView } from "../DocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { Transform } from "../../../util/Transform"; +import React = require("react"); + +interface IDashDocView { + node: any; + view: any; + getPos: any; + tbox?: FormattedTextBox; + self: any; +} + +export class DashDocView extends React.Component { + + _dashDoc: Doc | undefined; + _reactionDisposer: IReactionDisposer | undefined; + _renderDisposer: IReactionDisposer | undefined; + _textBox: FormattedTextBox; + _finalLayout: any; + _resolvedDataDoc: any; + + + // constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + + constructor(props: IDashDocView) { + super(props); + + const node = this.props.node; + this._textBox = this.props.tbox as FormattedTextBox; + + const alias = node.attrs.alias; + const docid = node.attrs.docid || this._textBox.props.Document[Id]; + + DocServer.GetRefField(docid + alias).then(async dashDoc => { + if (!(dashDoc instanceof Doc)) { + alias && DocServer.GetRefField(docid).then(async dashDocBase => { + if (dashDocBase instanceof Doc) { + const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); + aliasedDoc.layoutKey = "layout"; + node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + this._dashDoc = aliasedDoc; + // self.doRender(aliasedDoc, removeDoc, node, view, getPos); + } + }); + } else { + this._dashDoc = dashDoc; + // self.doRender(dashDoc, removeDoc, node, view, getPos); + } + }); + + this.onPointerLeave = this.onPointerLeave.bind(this); + this.onPointerEnter = this.onPointerEnter.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + this.onKeyPress = this.onKeyPress.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + this.onWheel = this.onWheel.bind(this); + } + /* #region Internal functions */ + + removeDoc = () => { + const view = this.props.view; + const pos = this.props.getPos(); + const ns = new NodeSelection(view.state.doc.resolve(pos)); + view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); + return true; + } + + getDocTransform = () => { + const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement; + const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement); + return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); + } + contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; + + outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target + + onKeyPress = (e: any) => { + e.stopPropagation(); + } + onWheel = (e: any) => { + e.preventDefault(); + } + onKeyUp = (e: any) => { + e.stopPropagation(); + } + onKeyDown = (e: any) => { + e.stopPropagation(); + if (e.key === "Tab" || e.key === "Enter") { + e.preventDefault(); + } + } + onPointerLeave = () => { + const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = ""; + } + } + onPointerEnter = () => { + const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = "orange"; + } + } + /*endregion*/ + + componentWillMount = () => { + this._reactionDisposer?.(); + } + + componentDidUpdate = () => { + + this._renderDisposer?.(); + this._renderDisposer = reaction(() => { + + const dashDoc = this._dashDoc as Doc; + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey); + + if (finalLayout) { + if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { + finalLayout.rootDocument = dashDoc.aliasOf; + } + const layoutKey = StrCast(finalLayout.layoutKey); + const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; + if (finalLayout !== dashDoc && finalKey) { + const finalLayoutField = finalLayout[finalKey]; + if (finalLayoutField instanceof ObjectField) { + finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + this._finalLayout = finalLayout; + this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null); + return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; + } + }, + (res) => { + + if (res) { + this._finalLayout = res.finalLayout; + this._resolvedDataDoc = res.resolvedDataDoc; + + this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc), + } + }, + { fireImmediately: true }); + + } + + render() { + // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { + + const node = this.props.node; + const view = this.props.view; + const getPos = this.props.getPos; + + const spanStyle = { + width: this.props.node.props.width, + height: this.props.node.props.height, + position: 'absolute' as 'absolute', + display: 'inline-block' + }; + + + const outerStyle = { + position: "relative" as "relative", + textIndent: "0", + border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")), + width: this.props.node.props.width, + height: this.props.node.props.height, + display: this.props.node.props.hidden ? "none" : "inline-block", + float: this.props.node.props.float, + }; + + const dashDoc = this._dashDoc as Doc; + const self = this; + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); + const resolvedDataDoc = this._resolvedDataDoc; //Added this + + if (!finalLayout) { + return
; + // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); + } else { + + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => + ({ + dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], + color: finalLayout.color + }), + ({ dim, color }) => { + spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px"; + spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px"; + outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + }, { fireImmediately: true }); + + if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } catch (e) { + console.log(e); + } + } + + + //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { + // ReactDOM.unmountComponentAtNode(this._dashSpan); + + return ( + +
+ + +
+
+ ); + + } + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss new file mode 100644 index 000000000..35ff9c1e6 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -0,0 +1,36 @@ +.dashFieldView { + position: relative; + display: inline-block; + + .dashFieldView-enumerables { + width: 10px; + height: 10px; + position: relative; + display: inline-block; + background: dimGray; + } + .dashFieldView-fieldCheck { + min-width: 12px; + position: relative; + display: inline-block; + background-color: rgba(155, 155, 155, 0.24); + } + .dashFieldView-labelSpan { + position: relative; + display: inline-block; + font-size: small; + } + .dashFieldView-fieldSpan { + min-width: 20px; + margin-left: 2px; + margin-right: 5px; + position: relative; + display: inline-block; + background-color: rgba(155, 155, 155, 0.24); + span { + min-width: 100%; + display: inline-block; + } + } +} + \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx new file mode 100644 index 000000000..82c3185e7 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -0,0 +1,211 @@ +import { IReactionDisposer, observable, runInAction, computed, action } from "mobx"; +import { Doc, DocListCast, Field } from "../../../../new_fields/Doc"; +import { List } from "../../../../new_fields/List"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { Cast, StrCast } from "../../../../new_fields/Types"; +import { DocServer } from "../../../DocServer"; +import { CollectionViewType } from "../../collections/CollectionView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import React = require("react"); +import * as ReactDOM from 'react-dom'; +import "./DashFieldView.scss"; +import { observer } from "mobx-react"; + + +export class DashFieldView { + _fieldWrapper: HTMLDivElement; // container for label and value + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldWrapper = document.createElement("div"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.fontWeight = "bold"; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + + ReactDOM.render(, this._fieldWrapper); + (this as any).dom = this._fieldWrapper; + } + destroy() { + ReactDOM.unmountComponentAtNode(this._fieldWrapper); + } + selectNode() { } + +} +interface IDashFieldViewInternal { + fieldKey: string; + docid: string; + view: any; + getPos: any; + tbox: FormattedTextBox; + width: number; + height: number; +} + +@observer +export class DashFieldViewInternal extends React.Component { + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + _fieldKey: string; + _fieldStringRef = React.createRef(); + @observable _showEnumerables: boolean = false; + @observable _dashDoc: Doc | undefined; + + constructor(props: IDashFieldViewInternal) { + super(props); + this._fieldKey = this.props.fieldKey; + this._textBoxDoc = this.props.tbox.props.Document; + + if (this.props.docid) { + DocServer.GetRefField(this.props.docid). + then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); + } else { + this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc; + } + } + componentWillUnmount() { + this._reactionDisposer?.(); + } + + // set the display of the field's value (checkbox for booleans, span of text for strings) + @computed get fieldValueContent() { + if (this._dashDoc) { + const dashVal = this._dashDoc[this._fieldKey]; + const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal; + const boolVal = Cast(fval, "boolean", null); + const strVal = Field.toString(fval as Field) || ""; + + // field value is a boolean, so use a checkbox or similar widget to display it + if (boolVal === true || boolVal === false) { + return this._dashDoc![this._fieldKey] = e.target.checked} + />; + } + else // field value is a string, so display it as an editable span + { + // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't + // use React events. Essentially, React events occur after native events have been processed, so corresponding React events + // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. + return { + r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); + r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); + r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true)); + }}> + {strVal} + ; + } + } + } + + // we need to handle all key events on the input span or else they will propagate to prosemirror. + @action + fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { + if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. + e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); + this.updateText(span.textContent!, true); + e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view + } + if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span + if (window.getSelection) { + const range = document.createRange(); + range.selectNodeContents(span); + window.getSelection()!.removeAllRanges(); + window.getSelection()!.addRange(range); + } + e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected + } + e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror. + } + + @action + updateText = (nodeText: string, forceMatch: boolean) => { + this._showEnumerables = false; + if (nodeText) { + const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; + + // look for a document whose id === the fieldKey being displayed. If there's a match, then that document + // holds the different enumerated values for the field in the titles of its collected documents. + // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. + DocServer.GetRefField(this._fieldKey).then(options => { + let modText = ""; + (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + if (modText) { + // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; + Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); + this._dashDoc![this._fieldKey] = modText; + } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key + else if (nodeText.startsWith(":=")) { + this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); + } else if (nodeText.startsWith("=:=")) { + Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); + } else { + this._dashDoc![this._fieldKey] = newText; + } + }); + } + } + + // display a collection of all the enumerable values for this field + onPointerDownEnumerables = async (e: any) => { + e.stopPropagation(); + const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); + collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight"); + } + + + // clicking on the label creates a pivot view collection of all documents + // in the same collection. The pivot field is the fieldKey of this label + onPointerDownLabelSpan = (e: any) => { + e.stopPropagation(); + let container = this.props.tbox.props.ContainingCollectionView; + while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { + container = container.props.ContainingCollectionView; + } + if (container) { + const alias = Doc.MakeAlias(container.props.Document); + alias.viewType = CollectionViewType.Time; + let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); + if (!list) { + alias.schemaColumns = list = new List(); + } + list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); + list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); + alias._pivotField = this._fieldKey; + this.props.tbox.props.addDocTab(alias, "onRight"); + } + } + + render() { + return
+ + {this._fieldKey} + + +
+ {this.fieldValueContent} +
+ + {!this._showEnumerables ? (null) :
} + +
; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx new file mode 100644 index 000000000..ee21fb765 --- /dev/null +++ b/src/client/views/nodes/formattedText/FootnoteView.tsx @@ -0,0 +1,162 @@ +import { EditorView } from "prosemirror-view"; +import { EditorState } from "prosemirror-state"; +import { keymap } from "prosemirror-keymap"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { schema } from "./schema_rts"; +import { redo, undo } from "prosemirror-history"; +import { StepMap } from "prosemirror-transform"; + +import React = require("react"); + +interface IFootnoteView { + innerView: any; + outerView: any; + node: any; + dom: any; + getPos: any; +} + +export class FootnoteView extends React.Component { + _innerView: any; + _node: any; + + constructor(props: IFootnoteView) { + super(props); + const node = this.props.node; + const outerView = this.props.outerView; + const _innerView = this.props.innerView; + const getPos = this.props.getPos; + } + + selectNode() { + const attrs = { ...this.props.node.attrs }; + attrs.visibility = true; + this.dom.classList.add("ProseMirror-selectednode"); + if (!this.props.innerView) this.open(); + } + + deselectNode() { + const attrs = { ...this.props.node.attrs }; + attrs.visibility = false; + this.dom.classList.remove("ProseMirror-selectednode"); + if (this.props.innerView) this.close(); + } + open() { + // Append a tooltip to the outer node + const tooltip = this.dom.appendChild(document.createElement("div")); + tooltip.className = "footnote-tooltip"; + // And put a sub-ProseMirror into that + this.props.innerView.defineProperty(new EditorView(tooltip, { + // You can use any node as an editor document + state: EditorState.create({ + doc: this.props.node, + plugins: [keymap(baseKeymap), + keymap({ + "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch), + "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch), + "Mod-b": toggleMark(schema.marks.strong) + }), + // new Plugin({ + // view(newView) { + // // TODO -- make this work with RichTextMenu + // // return FormattedTextBox.getToolTip(newView); + // } + // }) + ], + + }), + // This is the magic part + dispatchTransaction: this.dispatchInner.bind(this), + handleDOMEvents: { + pointerdown: ((view: any, e: PointerEvent) => { + // Kludge to prevent issues due to the fact that the whole + // footnote is node-selected (and thus DOM-selected) when + // the parent editor is focused. + e.stopPropagation(); + document.addEventListener("pointerup", this.ignore, true); + if (this.props.outerView.hasFocus()) this.props.innerView.focus(); + }) as any + } + })); + setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0); + } + + ignore = (e: PointerEvent) => { + e.stopPropagation(); + document.removeEventListener("pointerup", this.ignore, true); + } + + dispatchInner(tr: any) { + const { state, transactions } = this.props.innerView.state.applyTransaction(tr); + this.props.innerView.updateState(state); + + if (!tr.getMeta("fromOutside")) { + const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1); + for (const transaction of transactions) { + const steps = transaction.steps; + for (const step of steps) { + outerTr.step(step.map(offsetMap)); + } + } + if (outerTr.docChanged) this.props.outerView.dispatch(outerTr); + } + } + update(node: any) { + if (!node.sameMarkup(this.props.node)) return false; + this._node = node; //not sure + if (this.props.innerView) { + const state = this.props.innerView.state; + const start = node.content.findDiffStart(state.doc.content); + if (start !== null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); + const overlap = start - Math.min(endA, endB); + if (overlap > 0) { endA += overlap; endB += overlap; } + this.props.innerView.dispatch( + state.tr + .replace(start, endB, node.slice(start, endA)) + .setMeta("fromOutside", true)); + } + } + return true; + } + onPointerUp = (e: any) => { + this.toggle(e); + } + + toggle = (e: any) => { + e.preventDefault(); + if (this.props.innerView) this.close(); + else { + this.open(); + } + } + + close() { + this.props.innerView && this.props.innerView.destroy(); + this._innerView = null; + this.dom.textContent = ""; + } + + destroy() { + if (this.props.innerView) this.close(); + } + + stopEvent(event: any) { + return this.props.innerView && this.props.innerView.dom.contains(event.target); + } + + ignoreMutation() { return true; } + + + render() { + return ( +
+
+ +
+
+ ); + } +} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss new file mode 100644 index 000000000..477a2ca08 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -0,0 +1,265 @@ +@import "../../globalCssVariables"; + +.ProseMirror { + width: 100%; + height: 100%; + min-height: 100%; +} + +.ProseMirror:focus { + outline: none !important; +} + +.formattedTextBox-cont { + touch-action: none; + cursor: text; + background: inherit; + padding: 0; + border-width: 0px; + border-radius: inherit; + border-color: $intermediate-color; + box-sizing: border-box; + background-color: inherit; + border-style: solid; + overflow-y: auto; + overflow-x: hidden; + color: initial; + max-height: 100%; + display: flex; + flex-direction: row; + transition: opacity 1s; + + .formattedTextBox-dictation { + height: 12px; + width: 10px; + top: 0px; + left: 0px; + position: absolute; + } +} +.formattedTextBox-outer { + position: relative; + overflow: auto; + display: inline-block; + width: 100%; + height: 100%; +} + +.formattedTextBox-sidebar-handle { + position: absolute; + top: calc(50% - 17.5px); + width: 10px; + height: 35px; + background: lightgray; + border-radius: 20px; + cursor:grabbing; +} + +.formattedTextBox-cont>.formattedTextBox-sidebar-handle { + right: 0; + left: unset; +} + +.formattedTextBox-sidebar, +.formattedTextBox-sidebar-inking { + border-left: dashed 1px black; + height: 100%; + display: inline-block; + position: absolute; + right: 0; + + .collectionfreeformview-container { + position: relative; + } + + >.formattedTextBox-sidebar-handle { + right: unset; + left: -5; + } +} + +.formattedTextBox-sidebar-inking { + pointer-events: all; +} + +.formattedTextBox-inner-rounded { + height: 70%; + width: 85%; + position: absolute; + overflow: auto; + top: 15%; + left: 10%; +} + +.formattedTextBox-inner-rounded, +.formattedTextBox-inner { + height: 100%; + white-space: pre-wrap; +} + +// .menuicon { +// display: inline-block; +// border-right: 1px solid rgba(0, 0, 0, 0.2); +// color: #888; +// line-height: 1; +// padding: 0 7px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 1.4em; +// } + +.strong, +.heading { + font-weight: bold; +} + +.em { + font-style: italic; +} + +.userMarkOpen { + background: rgba(255, 255, 0, 0.267); + display: inline; +} + +.userMark { + background: rgba(255, 255, 0, 0.267); + font-size: 2px; + display: inline-grid; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 10px; + min-height: 10px; + text-align: center; + align-content: center; +} + +footnote { + display: inline-block; + position: relative; + cursor: pointer; + + div { + padding: 0 !important; + } +} + +footnote::after { + content: counter(prosemirror-footnote); + vertical-align: super; + font-size: 75%; + counter-increment: prosemirror-footnote; +} + +.ProseMirror { + counter-reset: prosemirror-footnote; +} + +.footnote-tooltip { + cursor: auto; + font-size: 75%; + position: absolute; + left: -30px; + top: calc(100% + 10px); + background: silver; + padding: 3px; + border-radius: 2px; + max-width: 100px; + min-width: 50px; + width: max-content; +} + +.prosemirror-attribution { + font-size: 8px; +} + +.footnote-tooltip::before { + border: 5px solid silver; + border-top-width: 0px; + border-left-color: transparent; + border-right-color: transparent; + position: absolute; + top: -5px; + left: 27px; + content: " "; + height: 0; + width: 0; +} + + +.formattedTextBox-inlineComment { + position: relative; + width: 40px; + height: 20px; + &::before { + content: "→"; + } + &:hover { + background: orange; + } +} + +.formattedTextBox-summarizer { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "←"; + } +} + +.formattedTextBox-summarizer-collapsed { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "..."; + } +} + +.ProseMirror { + touch-action: none; + span { + font-family: inherit; + } + + ol, ul { + counter-reset: deci1 0 multi1 0; + padding-left: 1em; + font-family: inherit; + } + ol { + margin-left: 1em; + font-family: inherit; + } + + .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } + .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} + .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} + .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} + .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } + .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } + .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } + + .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } + .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} + .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} + .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} + + .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } + .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } + .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } + .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } + .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } + .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } + .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } + + .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } + .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } + .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } + .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx new file mode 100644 index 000000000..248b4f467 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -0,0 +1,1303 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isEqual } from "lodash"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { baseKeymap } from "prosemirror-commands"; +import { history } from "prosemirror-history"; +import { inputRules } from 'prosemirror-inputrules'; +import { keymap } from "prosemirror-keymap"; +import { Fragment, Mark, Node, Slice } from "prosemirror-model"; +import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; +import { ReplaceStep } from 'prosemirror-transform'; +import { EditorView } from "prosemirror-view"; +import { DateField } from '../../../../new_fields/DateField'; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; +import { documentSchema } from '../../../../new_fields/documentSchemas'; +import { Id } from '../../../../new_fields/FieldSymbols'; +import { InkTool } from '../../../../new_fields/InkField'; +import { PrefetchProxy } from '../../../../new_fields/Proxy'; +import { RichTextField } from "../../../../new_fields/RichTextField"; +import { RichTextUtils } from '../../../../new_fields/RichTextUtils'; +import { createSchema, makeInterface } from "../../../../new_fields/Schema"; +import { Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { TraceMobx } from '../../../../new_fields/util'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../../Utils'; +import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; +import { DocServer } from "../../../DocServer"; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { DictationManager } from '../../../util/DictationManager'; +import { DragManager } from "../../../util/DragManager"; +import { makeTemplate } from '../../../util/DropConverter'; +import buildKeymap from "./ProsemirrorExampleTransfer"; +import RichTextMenu from './RichTextMenu'; +import { RichTextRules } from "./RichTextRules"; +import { DashDocCommentView, DashDocView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "./RichTextSchema"; +// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "./RichTextSchema"; +// import { OrderedListView } from "./RichTextSchema"; +// import { ImageResizeView } from "./ImageResizeView"; +// import { DashDocCommentView } from "./DashDocCommentView"; +// import { FootnoteView } from "./FootnoteView"; +// import { SummaryView } from "./SummaryView"; +// import { DashDocView } from "./DashDocView"; +import { DashFieldView } from "./DashFieldView"; + +import { schema } from "./schema_rts"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; +import { ContextMenu } from '../../ContextMenu'; +import { ContextMenuProps } from '../../ContextMenuItem'; +import { ViewBoxAnnotatableComponent } from "../../DocComponent"; +import { DocumentButtonBar } from '../../DocumentButtonBar'; +import { InkingControl } from "../../InkingControl"; +import { AudioBox } from '../AudioBox'; +import { FieldView, FieldViewProps } from "../FieldView"; +import "./FormattedTextBox.scss"; +import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; +import React = require("react"); + +library.add(faEdit); +library.add(faSmile, faTextHeight, faUpload); + +export interface FormattedTextBoxProps { + hideOnLeave?: boolean; + makeLink?: () => Opt; + xMargin?: number; + yMargin?: number; +} + +const richTextSchema = createSchema({ + documentText: "string" +}); + +export const GoogleRef = "googleDocId"; + +type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; +const RichTextDocument = makeInterface(richTextSchema, documentSchema); + +type PullHandler = (exportState: Opt, dataDoc: Doc) => void; + +@observer +export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } + public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); + public static Instance: FormattedTextBox; + public ProseRef?: HTMLDivElement; + private _ref: React.RefObject = React.createRef(); + private _scrollRef: React.RefObject = React.createRef(); + private _editorView: Opt; + private _applyingChange: boolean = false; + private _searchIndex = 0; + private _sidebarMovement = 0; + private _lastX = 0; + private _lastY = 0; + private _undoTyping?: UndoManager.Batch; + private _disposers: { [name: string]: IReactionDisposer } = {}; + private dropDisposer?: DragManager.DragDropDisposer; + + @computed get _recording() { return this.dataDoc.audioState === "recording"; } + set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } + + @observable private _entered = false; + + public static FocusedBox: FormattedTextBox | undefined; + public static SelectOnLoad = ""; + public static SelectOnLoadChar = ""; + public static IsFragment(html: string) { + return html.indexOf("data-pm-slice") !== -1; + } + public static GetHref(html: string): string { + const parser = new DOMParser(); + const parsedHtml = parser.parseFromString(html, 'text/html'); + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && + (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; + } + return ""; + } + public static GetDocFromUrl(url: string) { + if (url.startsWith(document.location.origin)) { + const split = new URL(url).pathname.split("doc/"); + const docid = split[split.length - 1]; + return docid; + } + return ""; + } + + @undoBatch + public setFontColor(color: string) { + const view = this._editorView!; + if (view.state.selection.from === view.state.selection.to) return false; + if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { + this.layoutDoc.color = color; + } + const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); + view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); + return true; + } + + constructor(props: any) { + super(props); + FormattedTextBox.Instance = this; + this.updateHighlights(); + } + + public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + + linkOnDeselect: Map = new Map(); + + doLinkOnDeselect() { + Array.from(this.linkOnDeselect.entries()).map(entry => { + const key = entry[0]; + const value = entry[1]; + const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + DocServer.GetRefField(value).then(doc => { + DocServer.GetRefField(id).then(linkDoc => { + this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); + DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); + if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } + else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); + }); + }); + }); + this.linkOnDeselect.clear(); + } + + dispatchTransaction = (tx: Transaction) => { + if (this._editorView) { + const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + if (metadata) { + const range = tx.selection.$from.blockRange(tx.selection.$to); + let text = range ? tx.doc.textBetween(range.start, range.end) : ""; + let textEndSelection = tx.selection.to; + for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } + text = text.substr(0, textEndSelection - range!.start); + text = text.split(" ")[text.split(" ").length - 1]; + const split = text.split("::"); + if (split.length > 1 && split[1]) { + const key = split[0]; + const value = split[split.length - 1]; + this.linkOnDeselect.set(key, value); + + const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value }); + const mval = this._editorView.state.schema.marks.metadataVal.create(); + const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); + tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); + this.dataDoc[key] = value; + } + } + const state = this._editorView.state.apply(tx); + this._editorView.updateState(state); + (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks); + + const tsel = this._editorView.state.selection.$from; + tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); + const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); + const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); + if (!this._applyingChange) { + this._applyingChange = true; + this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) + this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + } else { // if we've deleted all the text in a note driven by a template, then restore the template data + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data))); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + } + this._applyingChange = false; + } + this.updateTitle(); + this.tryUpdateHeight(); + } + } + + updateTitle = () => { + if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { + const str = this._editorView.state.doc.textContent; + const titlestr = str.substr(0, Math.min(40, str.length)); + this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); + } + } + + // needs a better API for taking in a set of words with target documents instead of just one target + public hyperlinkTerms = (terms: string[], target: Doc) => { + if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const 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); + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!; + const link = this._editorView.state.schema.marks.link.create({ + href: Utils.prepend("/doc/" + alink[Id]), + title: "a link", location: location, linkId: alink[Id], targetId: target[Id] + }); + this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); + } + } + public highlightSearchTerms = (terms: string[]) => { + 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)); + 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()); + } + } + + public unhighlightSearchTerms = () => { + if (this._editorView && (this._editorView as any).docView) { + 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 end = this._editorView.state.doc.nodeSize - 2; + this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + } + } + adoptAnnotation = (start: number, end: number, mark: Mark) => { + const view = this._editorView!; + const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); + view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); + } + protected createDropTarget = (ele: HTMLDivElement) => { + this.ProseRef = ele; + this.dropDisposer?.(); + ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + } + + @undoBatch + @action + drop = async (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0]; + // replace text contents whend dragging with Alt + if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { + if (draggedDoc.data instanceof RichTextField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); + e.stopPropagation(); + } + // embed document when dragging with a userDropAction or an embedDoc flag set + } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) { + const target = de.complete.docDragData.droppedDocuments[0]; + // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + // if (link) { + target._fitToBox = true; + const node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "right" + }); + const view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + this.tryUpdateHeight(); + e.stopPropagation(); + // } + } // otherwise, fall through to outer collection to handle drop + } else if (de.complete.linkDragData) { + de.complete.linkDragData.linkDropCallback = this.linkDrop; + } + } + linkDrop = (data: DragManager.LinkDragData) => { + const linkDoc = data.linkDocument!; + const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; + const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; + this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id); + } + + getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { + let offset = 0; + + if (context === node) return { from: offset, to: offset + node.nodeSize }; + + if (node.isBlock) { + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < (context.content as any).content.length; i++) { + const result = this.getNodeEndpoints((context.content as any).content[i], node); + if (result) { + return { + from: result.from + offset + (context.type.name === "doc" ? 0 : 1), + to: result.to + offset + (context.type.name === "doc" ? 0 : 1) + }; + } + offset += (context.content as any).content[i].nodeSize; + } + return null; + } else { + return null; + } + } + + + //Recursively finds matches within a given node + findInNode(pm: EditorView, node: Node, find: string) { + let ret: TextSelection[] = []; + + if (node.isTextblock) { + let index = 0, foundAt; + const ep = this.getNodeEndpoints(pm.state.doc, node); + while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { + const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); + ret.push(sel); + index = index + foundAt + find.length; + } + } else { + node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); + } + return ret; + } + static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + + updateHighlights = () => { + clearStyleSheetRules(FormattedTextBox._userStyleSheet); + if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); + } + if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); + } + if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); + } + if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); + } + if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); + } + if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); + } + if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + const min = Math.round(Date.now() / 1000 / 60); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); + setTimeout(() => this.updateHighlights()); + } + if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + const hr = Math.round(Date.now() / 1000 / 60 / 60); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); + } + } + + sidebarDown = (e: React.PointerEvent) => { + this._lastX = e.clientX; + this._lastY = e.clientY; + this._sidebarMovement = 0; + document.addEventListener("pointermove", this.sidebarMove); + document.addEventListener("pointerup", this.sidebarUp); + e.stopPropagation(); + e.preventDefault(); // prevents text from being selected during drag + } + sidebarMove = (e: PointerEvent) => { + const bounds = this.CurrentDiv.getBoundingClientRect(); + this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY)); + this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; + } + sidebarUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.sidebarMove); + document.removeEventListener("pointerup", this.sidebarUp); + } + + toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"); + + public static get DefaultLayout(): Doc | string | undefined { + return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); + } + specificContextMenu = (e: React.MouseEvent): void => { + const cm = ContextMenu.Instance; + + const funcs: ContextMenuProps[] = []; + this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); + funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + !this.props.Document.rootDocument && funcs.push({ + description: "Make Template", event: () => { + this.props.Document.isTemplateDoc = makeTemplate(this.props.Document); + Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); + }, icon: "eye" + }); + funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); + + const highlighting: ContextMenuProps[] = []; + ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + highlighting.push({ + description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + e.stopPropagation(); + if (FormattedTextBox._highlights.indexOf(option) === -1) { + FormattedTextBox._highlights.push(option); + } else { + FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); + } + this.updateHighlights(); + }, icon: "expand-arrows-alt" + })); + funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); + + ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + + const change = cm.findByDescription("Change Perspective..."); + const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : []; + + const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); + DocListCast(noteTypesDoc?.data).forEach(note => { + changeItems.push({ + description: StrCast(note.title), event: undoBatch(() => { + Doc.setNativeView(this.props.Document); + Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); + }), icon: "eye" + }); + }); + changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" }); + !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); + + const open = cm.findByDescription("Add a Perspective..."); + const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; + + openItems.push({ + description: "FreeForm", event: undoBatch(() => { + const alias = Doc.MakeAlias(this.rootDoc); + Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform"); + this.props.addDocTab(alias, "onRight"); + }), icon: "eye" + }); + !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" }); + + } + + recordDictation = () => { + DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + //this._editorView!.focus(); + }); + } + stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; + + @action + toggleMenubar = () => { + this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; + } + + recordBullet = async () => { + const completedCue = "end session"; + const results = await DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + terminators: [completedCue, "bullet", "next"] + }); + if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { + DictationManager.Controls.stop(); + return; + } + this.nextBullet(this._editorView!.state.selection.to); + setTimeout(this.recordBullet, 2000); + } + + setCurrentBulletContent = (value: string) => { + if (this._editorView) { + const state = this._editorView.state; + const now = Date.now(); + let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); + if (!this._break && state.selection.to !== state.selection.from) { + for (let i = state.selection.from; i <= state.selection.to; i++) { + const pos = state.doc.resolve(i); + const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark); + if (um) { + mark = um; + break; + } + } + } + const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime(); + this._break = false; + value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; + const from = state.selection.from; + const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); + this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1))); + } + } + + nextBullet = (pos: number) => { + if (this._editorView) { + const frag = Fragment.fromArray(this.newListItems(2)); + if (this._editorView.state.doc.resolve(pos).depth >= 2) { + const slice = new Slice(frag, 2, 2); + let state = this._editorView.state; + this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); + pos += 4; + state = this._editorView.state; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + } + } + } + + private newListItems = (count: number) => { + return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); + } + + _keymap: any = undefined; + _rules: RichTextRules | undefined; + @computed get config() { + this._keymap = buildKeymap(schema, this.props); + this._rules = new RichTextRules(this.props.Document, this); + return { + schema, + plugins: [ + inputRules(this._rules.inpRules), + this.richTextMenuPlugin(), + history(), + keymap(this._keymap), + keymap(baseKeymap), + new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + }), + formattedTextBoxCommentPlugin + ] + }; + } + + makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) { + if (this._editorView) { + const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); + this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link). + addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link)); + } + } + componentDidMount() { + this._disposers.buttonBar = reaction( + () => DocumentButtonBar.Instance, + instance => { + if (instance) { + this.pullFromGoogleDoc(this.checkState); + this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); + } + } + ); + this._disposers.linkMaker = reaction( + () => this.props.makeLink?.(), + (linkDoc: Opt) => { + if (linkDoc) { + const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; + const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; + this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id); + } + }, + { fireImmediately: true } + ); + this._disposers.editorState = reaction( + () => { + if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { + return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; + } + return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; + }, + incomingValue => { + if (incomingValue !== undefined && this._editorView && !this._applyingChange) { + const updatedState = JSON.parse(incomingValue); + this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + this.tryUpdateHeight(); + } + } + ); + this._disposers.pullDoc = reaction( + () => this.props.Document[Pulls], + () => { + if (!DocumentButtonBar.hasPulledHack) { + DocumentButtonBar.hasPulledHack = true; + const unchanged = this.dataDoc.unchanged; + this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); + } + } + ); + this._disposers.pushDoc = reaction( + () => this.props.Document[Pushes], + () => { + if (!DocumentButtonBar.hasPushedHack) { + DocumentButtonBar.hasPushedHack = true; + this.pushToGoogleDoc(); + } + } + ); + this._disposers.height = reaction( + () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], + () => this.tryUpdateHeight() + ); + + this.setupEditor(this.config, this.props.fieldKey); + + this._disposers.search = reaction(() => this.rootDoc.searchMatch, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), + { fireImmediately: true }); + + this._disposers.record = reaction(() => this._recording, + () => { + if (this._recording) { + setTimeout(action(() => { + this.stopDictation(true); + setTimeout(() => this.recordDictation(), 500); + }), 500); + } else setTimeout(() => this.stopDictation(true), 0); + } + ); + this._disposers.scrollToRegion = reaction( + () => StrCast(this.layoutDoc.scrollToLinkID), + async (scrollToLinkID) => { + const findLinkFrag = (frag: Fragment, editor: EditorView) => { + const nodes: Node[] = []; + frag.forEach((node, index) => { + const examinedNode = findLinkNode(node, editor); + if (examinedNode && examinedNode.textContent) { + nodes.push(examinedNode); + start += index; + } + }); + return { frag: Fragment.fromArray(nodes), start: start }; + }; + const findLinkNode = (node: Node, editor: EditorView) => { + if (!node.isText) { + const content = findLinkFrag(node.content, editor); + return node.copy(content.frag); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); + return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; + }; + + let start = -1; + if (this._editorView && scrollToLinkID) { + const editor = this._editorView; + const ret = findLinkFrag(editor.state.doc.content, editor); + + if (ret.frag.size > 2 && ret.start >= 0) { + let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start + if (ret.frag.firstChild) { + selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + } + editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); + const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); + setTimeout(() => this.unhighlightSearchTerms(), 2000); + } + Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); + } + + }, + { fireImmediately: true } + ); + this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), + pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } + ); + + setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0))); + } + + pushToGoogleDoc = async () => { + this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { + const modes = GoogleApiClientUtils.Docs.WriteMode; + let mode = modes.Replace; + let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + if (!reference) { + mode = modes.Insert; + reference = { title: StrCast(this.dataDoc.title) }; + } + const redo = async () => { + if (this._editorView && reference) { + const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); + const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + response && (this.dataDoc[GoogleRef] = response.documentId); + const pushSuccess = response !== undefined && !("errors" in response); + dataDoc.unchanged = pushSuccess; + DocumentButtonBar.Instance.startPushOutcome(pushSuccess); + } + }; + const undo = () => { + if (!exportState) { + return; + } + const content: GoogleApiClientUtils.Docs.Content = { + text: exportState.text, + requests: [] + }; + if (reference && content) { + GoogleApiClientUtils.Docs.write({ reference, content, mode }); + } + }; + UndoManager.AddEvent({ undo, redo }); + redo(); + }); + } + + pullFromGoogleDoc = async (handler: PullHandler) => { + const dataDoc = this.dataDoc; + const documentId = StrCast(dataDoc[GoogleRef]); + let exportState: Opt; + if (documentId) { + exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); + } + UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); + } + + updateState = (exportState: Opt, dataDoc: Doc) => { + let pullSuccess = false; + if (exportState !== undefined) { + pullSuccess = true; + dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); + setTimeout(() => { + if (this._editorView) { + const state = this._editorView.state; + const end = state.doc.content.size - 1; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); + } + }, 0); + dataDoc.title = exportState.title; + this.rootDoc.customTitle = true; + dataDoc.unchanged = true; + } else { + delete dataDoc[GoogleRef]; + } + DocumentButtonBar.Instance.startPullOutcome(pullSuccess); + } + + checkState = (exportState: Opt, dataDoc: Doc) => { + if (exportState && this._editorView) { + const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); + const equalTitles = dataDoc.title === exportState.title; + const unchanged = equalContent && equalTitles; + dataDoc.unchanged = unchanged; + DocumentButtonBar.Instance.setPullState(unchanged); + } + } + + clipboardTextSerializer = (slice: Slice): string => { + let text = "", separated = true; + const from = 0, to = slice.content.size; + slice.content.nodesBetween(from, to, (node, pos) => { + if (node.isText) { + text += node.text!.slice(Math.max(from, pos) - pos, to - pos); + separated = false; + } else if (!separated && node.isBlock) { + text += "\n"; + separated = true; + } else if (node.type.name === "hard_break") { + text += "\n"; + } + }, 0); + return text; + } + + sliceSingleNode(slice: Slice) { + return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null; + } + + handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { + const cbe = event as ClipboardEvent; + const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); + const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); + if (pdfDocId && pdfRegionId) { + DocServer.GetRefField(pdfDocId).then(pdfDoc => { + DocServer.GetRefField(pdfRegionId).then(pdfRegion => { + if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { + setTimeout(async () => { + const targetField = Doc.LayoutFieldKey(pdfDoc); + const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations + targetAnnotations?.push(pdfRegion); + }); + + const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); + if (link) { + cbe.clipboardData!.setData("dash/linkDoc", link[Id]); + const linkId = link[Id]; + const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + slice = new Slice(frag, slice.openStart, slice.openEnd); + const tr = view.state.tr.replaceSelection(slice); + view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + } + } + }); + }); + return true; + } + return false; + + + function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { + const nodes: Node[] = []; + frag.forEach(node => nodes.push(marker(node))); + return Fragment.fromArray(nodes); + } + function addLinkMark(node: Node, title: string, linkId: string) { + if (!node.isText) { + const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); + return node.copy(content); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type.name === "link"); + const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true }); + marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); + return node.mark(marks); + } + } + + private setupEditor(config: any, fieldKey: string) { + const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); + const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"]; + const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); + if (this.ProseRef) { + const self = this; + this._editorView?.destroy(); + this._editorView = new EditorView(this.ProseRef, { + state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), + handleScrollToSelection: (editorView) => { + const docPos = editorView.coordsAtPos(editorView.state.selection.from); + const viewRect = self._ref.current!.getBoundingClientRect(); + if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) { + docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale); + } + return true; + }, + dispatchTransaction: this.dispatchTransaction, + nodeViews: { + dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, + dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, + dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, + // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); }, + + // image(node, view, getPos) { + // //const addDocTab = this.props.addDocTab; + // return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab }); + // }, + // // WAS : + // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); }, + + summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, + ordered_list(node, view, getPos) { return new OrderedListView(); }, + footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } + }, + clipboardTextSerializer: this.clipboardTextSerializer, + handlePaste: this.handlePaste, + }); + const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); + if (startupText) { + const { state: { tr }, dispatch } = this._editorView; + dispatch(tr.insertText(startupText)); + } + } + + const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad; + if (selectOnLoad && !this.props.dontRegisterView) { + FormattedTextBox.SelectOnLoad = ""; + this.props.select(false); + FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar)); + FormattedTextBox.SelectOnLoadChar = ""; + + } + (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); + // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; + } + getFont(font: string) { + switch (font) { + case "Arial": return schema.marks.arial.create(); + case "Times New Roman": return schema.marks.timesNewRoman.create(); + case "Georgia": return schema.marks.georgia.create(); + case "Comic Sans MS": return schema.marks.comicSans.create(); + case "Tahoma": return schema.marks.tahoma.create(); + case "Impact": return schema.marks.impact.create(); + case "ACrimson Textrial": return schema.marks.crimson.create(); + } + return schema.marks.arial.create(); + } + + componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); + this._editorView?.destroy(); + } + + static _downEvent: any; + _downX = 0; + _downY = 0; + _break = false; + onPointerDown = (e: React.PointerEvent): void => { + if (this._recording && !e.ctrlKey && e.button === 0) { + this.stopDictation(true); + this._break = true; + const state = this._editorView!.state; + const to = state.selection.to; + const updated = TextSelection.create(state.doc, to, to); + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to)); + e.preventDefault(); + e.stopPropagation(); + if (this._recording) setTimeout(() => this.recordDictation(), 500); + } + this._downX = e.clientX; + this._downY = e.clientY; + this.doLinkOnDeselect(); + FormattedTextBox._downEvent = true; + FormattedTextBoxComment.textBox = this; + if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { + e.preventDefault(); + } + if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar + e.stopPropagation(); + } + } + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + e.preventDefault(); + } + } + + onPointerUp = (e: React.PointerEvent): void => { + if (!FormattedTextBox._downEvent) return; + FormattedTextBox._downEvent = false; + if (!(e.nativeEvent as any).formattedHandled) { + FormattedTextBoxComment.textBox = this; + FormattedTextBoxComment.update(this._editorView!); + } + (e.nativeEvent as any).formattedHandled = true; + + if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { + e.stopPropagation(); + } + this._downX = this._downY = Number.NaN; + } + + @action + onFocused = (e: React.FocusEvent): void => { + FormattedTextBox.FocusedBox = this; + this.tryUpdateHeight(); + + // see if we need to preserve the insertion point + const prosediv = this.ProseRef?.children?.[0] as any; + const keeplocation = prosediv?.keeplocation; + prosediv && (prosediv.keeplocation = undefined); + const pos = this._editorView?.state.selection.$from.pos || 1; + keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); + const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos); + + // jump rich text menu to this textbox + const bounds = this._ref.current?.getBoundingClientRect(); + if (bounds && this.props.Document._chromeStatus !== "disabled") { + const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); + let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); + if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { + y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); + } + RichTextMenu.Instance.jumpTo(x, y); + } + } + onPointerWheel = (e: React.WheelEvent): void => { + // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time + if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + e.stopPropagation(); + } + } + + static _bulletStyleSheet: any = addStyleSheet(); + static _userStyleSheet: any = addStyleSheet(); + + onClick = (e: React.MouseEvent): void => { + if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. + const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) + if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); + e.preventDefault(); + } + if (!node && this.ProseRef) { + const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div + if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); + } + } + } + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } + (e.nativeEvent as any).formattedHandled = true; + // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) { + // let href = (e.target as any).href; + // let location: string; + // if ((e.target as any).attributes.location) { + // location = (e.target as any).attributes.location.value; + // } + // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); + // if (node) { + // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); + // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). + // href = link && link.attrs.href; + // location = link && link.attrs.location; + // } + // } + // if (href) { + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + // if (linkClicked) { + // DocServer.GetRefField(linkClicked).then(async linkDoc => { + // (linkDoc instanceof Doc) && + // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false); + // }); + // } + // } else { + // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); + // this.props.addDocument && this.props.addDocument(webDoc); + // } + // e.stopPropagation(); + // e.preventDefault(); + // } + // } + + if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) { + this.props.select(e.ctrlKey); + this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); + } + } + + // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. + hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) { + clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); + const pos = this._editorView!.posAtCoords({ left: x, top: y }); + if (pos && this.props.isSelected(true)) { + // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined; + //const node = this._editorView!.state.doc.nodeAt(pos.pos); + const $pos = this._editorView!.state.doc.resolve(pos.pos); + let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined; + if ($pos.node().type === schema.nodes.ordered_list) { + for (let off = 1; off < 100; off++) { + const pos = this._editorView!.posAtCoords({ left: x + off, top: y }); + const node = pos && this._editorView!.state.doc.nodeAt(pos.pos); + if (node?.type === schema.nodes.list_item) { + list_node = node; + break; + } + } + } + if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { + if (select) { + const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); + if (!highlightOnly) { + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos))); + } + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + } else if (Math.abs(pos.pos - pos.inside) < 2) { + if (!highlightOnly) { + const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0; + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility })); + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset))); + } + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + } + } + } + } + onMouseUp = (e: React.MouseEvent): void => { + e.stopPropagation(); + + const view = this._editorView as any; + // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there + // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. + if (view.mouseDown) { + const originalUpHandler = view.mouseDown.up; + view.root.removeEventListener("mouseup", originalUpHandler); + view.mouseDown.up = (e: MouseEvent) => { + !(e as any).formattedHandled && originalUpHandler(e); + // e.stopPropagation(); + (e as any).formattedHandled = true; + }; + view.root.addEventListener("mouseup", view.mouseDown.up); + } + } + + richTextMenuPlugin() { + return new Plugin({ + view(newView) { + RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); + return RichTextMenu.Instance; + } + }); + } + + public static HadSelection: boolean = false; + onBlur = (e: any) => { + FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; + //DictationManager.Controls.stop(false); + if (this._undoTyping) { + this._undoTyping.end(); + this._undoTyping = undefined; + } + this.doLinkOnDeselect(); + + // move the richtextmenu offscreen + if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300); + } + + _lastTimedMark: Mark | undefined = undefined; + onKeyPress = (e: React.KeyboardEvent) => { + if (e.altKey) { + e.preventDefault(); + return; + } + const state = this._editorView!.state; + if (!state.selection.empty && e.key === "%") { + this._rules!.EnteringStyle = true; + e.preventDefault(); + e.stopPropagation(); + return; + } + + if (state.selection.empty || !this._rules!.EnteringStyle) { + this._rules!.EnteringStyle = false; + } + if (e.key === "Escape") { + this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); + (document.activeElement as any).blur?.(); + SelectionManager.DeselectAll(); + } + e.stopPropagation(); + if (e.key === "Tab" || e.key === "Enter") { + e.preventDefault(); + } + const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); + this._lastTimedMark = mark; + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); + + if (!this._undoTyping) { + this._undoTyping = UndoManager.StartBatch("undoTyping"); + } + } + + onscrolled = (ev: React.UIEvent) => { + this.props.Document.scrollPos = this._scrollRef.current!.scrollTop; + } + @action + tryUpdateHeight(limitHeight?: number) { + let scrollHeight = this._ref.current?.scrollHeight; + if (this.layoutDoc._autoHeight && scrollHeight && + getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + if (limitHeight && scrollHeight > limitHeight) { + scrollHeight = limitHeight; + this.layoutDoc.limitHeight = undefined; + this.layoutDoc._autoHeight = false; + } + const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); + const dh = NumCast(this.layoutDoc._height, 0); + const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle + this.layoutDoc._height = newHeight; + this.dataDoc._nativeHeight = nh ? scrollHeight : undefined; + } + } + } + + @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } + sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0); + @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } + render() { + TraceMobx(); + const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; + if (this.props.isSelected()) { + this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props); + } else if (FormattedTextBoxComment.textBox === this) { + FormattedTextBoxComment.Hide(); + } + return ( + +
this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)} + onBlur={this.onBlur} + onPointerUp={this.onPointerUp} + onPointerDown={this.onPointerDown} + onMouseUp={this.onMouseUp} + onWheel={this.onPointerWheel} + onPointerEnter={action(() => this._entered = true)} + onPointerLeave={action((e: React.PointerEvent) => { + this._entered = false; + const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); + for (let child: any = target; child; child = child?.parentElement) { + if (child === this._ref.current!) { + this._entered = true; + } + } + })} + > +
+
+
+ {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? +
this.toggleSidebar()} /> : +
+ + +
this.toggleSidebar()} /> +
} + {!this.props.Document._showAudio ? (null) : +
{ + runInAction(() => this._recording = !this._recording); + setTimeout(() => this._editorView!.focus(), 500); + e.stopPropagation(); + }} > + +
} +
+ ); + } +} diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss new file mode 100644 index 000000000..2dd63ec21 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss @@ -0,0 +1,33 @@ +.FormattedTextBox-tooltip { + position: absolute; + pointer-events: none; + z-index: 20; + background: white; + border: 1px solid silver; + border-radius: 2px; + margin-bottom: 7px; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + } + .FormattedTextBox-tooltip:before { + content: ""; + height: 0; width: 0; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: -6px; + border: 5px solid transparent; + border-bottom-width: 0; + border-top-color: silver; + } + .FormattedTextBox-tooltip:after { + content: ""; + height: 0; width: 0; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: -4.5px; + border: 5px solid transparent; + border-bottom-width: 0; + border-top-color: white; + } \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx new file mode 100644 index 000000000..f9e4c5210 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -0,0 +1,236 @@ +import { Mark, ResolvedPos } from "prosemirror-model"; +import { EditorState, Plugin } from "prosemirror-state"; +import { EditorView } from "prosemirror-view"; +import * as ReactDOM from 'react-dom'; +import { Doc, DocCastAsync } from "../../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { schema } from "./schema_rts"; +import { Transform } from "../../../util/Transform"; +import { ContentFittingDocumentView } from "../ContentFittingDocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import './FormattedTextBoxComment.scss'; +import React = require("react"); +import { Docs } from "../../../documents/Documents"; +import wiki from "wikijs"; +import { DocumentType } from "../../../documents/DocumentTypes"; + +export let formattedTextBoxCommentPlugin = new Plugin({ + view(editorView) { return new FormattedTextBoxComment(editorView); } +}); +export function findOtherUserMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); +} +export function findUserMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid); +} +export function findLinkMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.type === schema.marks.link); +} +export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { + let before = 0; + let nbef = rpos.nodeBefore; + while (nbef && finder(nbef.marks)) { + before += nbef.nodeSize; + rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); + rpos && (nbef = rpos.nodeBefore); + } + return before; +} +export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { + let after = 0; + let naft = rpos.nodeAfter; + while (naft && finder(naft.marks)) { + after += naft.nodeSize; + rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); + rpos && (naft = rpos.nodeAfter); + } + return after; +} + + +export class FormattedTextBoxComment { + static tooltip: HTMLElement; + static tooltipText: HTMLElement; + static tooltipInput: HTMLInputElement; + static start: number; + static end: number; + static mark: Mark; + static textBox: FormattedTextBox | undefined; + static linkDoc: Doc | undefined; + constructor(view: any) { + if (!FormattedTextBoxComment.tooltip) { + const root = document.getElementById("root"); + FormattedTextBoxComment.tooltipInput = document.createElement("input"); + FormattedTextBoxComment.tooltipInput.type = "checkbox"; + FormattedTextBoxComment.tooltip = document.createElement("div"); + FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltipText.style.width = "100%"; + FormattedTextBoxComment.tooltipText.style.height = "100%"; + FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; + FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; + FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; + FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; + FormattedTextBoxComment.tooltip.style.width = "100%"; + FormattedTextBoxComment.tooltip.style.height = "100%"; + FormattedTextBoxComment.tooltip.style.overflow = "hidden"; + FormattedTextBoxComment.tooltip.style.display = "none"; + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); + FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { + const keep = e.target && (e.target as any).type === "checkbox" ? true : false; + const textBox = FormattedTextBoxComment.textBox; + if (FormattedTextBoxComment.linkDoc && !keep && textBox) { + if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { + textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + } else { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + } + } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight"); + } + keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( + FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); + e.stopPropagation(); + e.preventDefault(); + }; + root && root.appendChild(FormattedTextBoxComment.tooltip); + } + } + + public static Hide() { + FormattedTextBoxComment.textBox = undefined; + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } + public static SetState(textBox: any, start: number, end: number, mark: Mark) { + FormattedTextBoxComment.textBox = textBox; + FormattedTextBoxComment.start = start; + FormattedTextBoxComment.end = end; + FormattedTextBoxComment.mark = mark; + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); + } + + static update(view: EditorView, lastState?: EditorState) { + const state = view.state; + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && + lastState.selection.eq(state.selection)) { + return; + } + FormattedTextBoxComment.linkDoc = undefined; + + const textBox = FormattedTextBoxComment.textBox; + if (!textBox || !textBox.props) { + return; + } + let set = "none"; + let nbef = 0; + FormattedTextBoxComment.tooltipInput.style.display = "none"; + FormattedTextBoxComment.tooltip.style.width = ""; + FormattedTextBoxComment.tooltip.style.height = ""; + (FormattedTextBoxComment.tooltipText as any).href = ""; + FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; + FormattedTextBoxComment.tooltipText.style.overflow = ""; + // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date + if (state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); + const noselection = view.state.selection.$from === view.state.selection.$to; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + const mark = child && findOtherUserMark(child.marks); + if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { + FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); + } + if (mark && child && ((nbef && naft) || !noselection)) { + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); + set = ""; + FormattedTextBoxComment.tooltipInput.style.display = ""; + } + } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. + if (set === "none" && state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + const naft = findEndOfMark(state.selection.$from, view, findLinkMark); + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + const mark = child && findLinkMark(child.marks); + if (mark && child && nbef && naft && mark.attrs.showPreview) { + FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; + if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) { + wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); + } else { + FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; + FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; + } + if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { + FormattedTextBoxComment.tooltipText.textContent = "target not found..."; + (FormattedTextBoxComment.tooltipText as any).href = ""; + const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { } + docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { + if (linkDoc instanceof Doc) { + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; + FormattedTextBoxComment.linkDoc = linkDoc; + const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); + const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; + if (anchor !== target && anchor && target) { + target.scrollY = NumCast(anchor?.y); + } + if (target) { + ReactDOM.render( Math.min(350, NumCast(target._width, 350))} + PanelHeight={() => Math.min(250, NumCast(target._height, 250))} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; + FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; + } + // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + // let text = ext && StrCast(ext.text); + // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); + } + }); + } + set = ""; + } + } + if (set !== "none") { + // These are in screen coordinates + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + const left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + } + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); + } + + destroy() { } +} diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx new file mode 100644 index 000000000..8f98da0fd --- /dev/null +++ b/src/client/views/nodes/formattedText/ImageResizeView.tsx @@ -0,0 +1,138 @@ +import { NodeSelection } from "prosemirror-state"; +import { Doc } from "../../../../new_fields/Doc"; +import { DocServer } from "../../../DocServer"; +import { DocumentManager } from "../../../util/DocumentManager"; +import React = require("react"); + +import { schema } from "./schema_rts"; + +interface IImageResizeView { + node: any; + view: any; + getPos: any; + addDocTab: any; +} + +export class ImageResizeView extends React.Component { + constructor(props: IImageResizeView) { + super(props); + } + + onClickImg = (e: any) => { + e.stopPropagation(); + e.preventDefault(); + if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) { + this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2)))); + } + } + + onPointerDownImg = (e: any) => { + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document, + document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false)); + } + } + + onPointerDownHandle = (e: any) => { + e.preventDefault(); + e.stopPropagation(); + const elementImage = document.getElementById("imageId") as HTMLElement; + const wid = Number(getComputedStyle(elementImage).width.replace(/px/, "")); + const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, "")); + const startX = e.pageX; + const startWidth = parseFloat(this.props.node.attrs.width); + + const onpointermove = (e: any) => { + const elementOuter = document.getElementById("outerId") as HTMLElement; + + const currentX = e.pageX; + const diffInPx = currentX - startX; + elementOuter.style.width = `${startWidth + diffInPx}`; + elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`; + }; + + const onpointerup = () => { + document.removeEventListener("pointermove", onpointermove); + document.removeEventListener("pointerup", onpointerup); + const pos = this.props.view.state.selection.from; + const elementOuter = document.getElementById("outerId") as HTMLElement; + this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height })); + this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos)))); + }; + + document.addEventListener("pointermove", onpointermove); + document.addEventListener("pointerup", onpointerup); + } + + selectNode() { + const elementImage = document.getElementById("imageId") as HTMLElement; + const elementHandle = document.getElementById("handleId") as HTMLElement; + + elementImage.classList.add("ProseMirror-selectednode"); + elementHandle.style.display = ""; + } + + deselectNode() { + const elementImage = document.getElementById("imageId") as HTMLElement; + const elementHandle = document.getElementById("handleId") as HTMLElement; + + elementImage.classList.remove("ProseMirror-selectednode"); + elementHandle.style.display = "none"; + } + + + render() { + + const outerStyle = { + width: this.props.node.attrs.width, + height: this.props.node.attrs.height, + display: "inline-block", + overflow: "hidden", + float: this.props.node.attrs.float + }; + + const imageStyle = { + width: "100%", + }; + + const handleStyle = { + position: "absolute", + width: "20px", + heiht: "20px", + backgroundColor: "blue", + borderRadius: "15px", + display: "none", + bottom: "-10px", + right: "-10px" + + }; + + + + return ( +
+ + + + + +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts new file mode 100644 index 000000000..d80e64634 --- /dev/null +++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts @@ -0,0 +1,143 @@ +import clamp from '../../../util/clamp'; +import convertToCSSPTValue from '../../../util/convertToCSSPTValue'; +import toCSSLineSpacing from '../../../util/toCSSLineSpacing'; +import { Node, DOMOutputSpec } from 'prosemirror-model'; + +//import type { NodeSpec } from './Types'; +type NodeSpec = { + attrs?: { [key: string]: any }, + content?: string, + draggable?: boolean, + group?: string, + inline?: boolean, + name?: string, + parseDOM?: Array, + toDOM?: (node: any) => DOMOutputSpec, +}; + +// This assumes that every 36pt maps to one indent level. +export const INDENT_MARGIN_PT_SIZE = 36; +export const MIN_INDENT_LEVEL = 0; +export const MAX_INDENT_LEVEL = 7; +export const ATTRIBUTE_INDENT = 'data-indent'; + +export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']); + +const ALIGN_PATTERN = /(left|right|center|justify)/; + +// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js +// :: NodeSpec A plain paragraph textblock. Represented in the DOM +// as a `

` element. +const ParagraphNodeSpec: NodeSpec = { + attrs: { + align: { default: null }, + color: { default: null }, + id: { default: null }, + indent: { default: null }, + inset: { default: null }, + lineSpacing: { default: null }, + // TODO: Add UI to let user edit / clear padding. + paddingBottom: { default: null }, + // TODO: Add UI to let user edit / clear padding. + paddingTop: { default: null }, + }, + content: 'inline*', + group: 'block', + parseDOM: [{ tag: 'p', getAttrs }], + toDOM, +}; + +function getAttrs(dom: HTMLElement): Object { + const { + lineHeight, + textAlign, + marginLeft, + paddingTop, + paddingBottom, + } = dom.style; + + let align = dom.getAttribute('align') || textAlign || ''; + align = ALIGN_PATTERN.test(align) ? align : ""; + + let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10); + + if (!indent && marginLeft) { + indent = convertMarginLeftToIndentValue(marginLeft); + } + + indent = indent || MIN_INDENT_LEVEL; + + const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null; + + const id = dom.getAttribute('id') || ''; + return { align, indent, lineSpacing, paddingTop, paddingBottom, id }; +} + +function toDOM(node: Node): DOMOutputSpec { + const { + align, + indent, + inset, + lineSpacing, + paddingTop, + paddingBottom, + id, + } = node.attrs; + const attrs: { [key: string]: any } | null = {}; + + let style = ''; + if (align && align !== 'left') { + style += `text-align: ${align};`; + } + + if (lineSpacing) { + const cssLineSpacing = toCSSLineSpacing(lineSpacing); + style += + `line-height: ${cssLineSpacing};` + + // This creates the local css variable `--czi-content-line-height` + // that its children may apply. + `--czi-content-line-height: ${cssLineSpacing}`; + } + + if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) { + style += `padding-top: ${paddingTop};`; + } + + if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) { + style += `padding-bottom: ${paddingBottom};`; + } + + if (indent) { + style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`; + } + + if (inset) { + style += `margin-left: ${inset}; margin-right: ${inset};`; + } + + style && (attrs.style = style); + + if (indent) { + attrs[ATTRIBUTE_INDENT] = String(indent); + } + + if (id) { + attrs.id = id; + } + + return ['p', attrs, 0]; +} + +export const toParagraphDOM = toDOM; +export const getParagraphNodeAttrs = getAttrs; + +export function convertMarginLeftToIndentValue(marginLeft: string): number { + const ptValue = convertToCSSPTValue(marginLeft); + return clamp( + MIN_INDENT_LEVEL, + Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), + MAX_INDENT_LEVEL + ); +} + +export default ParagraphNodeSpec; \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts new file mode 100644 index 000000000..a0b02880e --- /dev/null +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -0,0 +1,241 @@ +import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { undoInputRule } from "prosemirror-inputrules"; +import { Schema } from "prosemirror-model"; +import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; +import { splitListItem, wrapInList, } from "prosemirror-schema-list"; +import { EditorState, Transaction, TextSelection } from "prosemirror-state"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { Docs } from "../../../documents/Documents"; +import { NumCast, BoolCast, Cast, StrCast } from "../../../../new_fields/Types"; +import { Doc } from "../../../../new_fields/Doc"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { Id } from "../../../../new_fields/FieldSymbols"; + +const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; + +export type KeyMap = { [key: string]: any }; + +export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => { + let fontSize: number | undefined = undefined; + tx2.doc.descendants((node: any, offset: any, index: any) => { + if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { + const path = (tx2.doc.resolve(offset) as any).path; + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); + if (node.type === schema.nodes.ordered_list) depth++; + fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize; + const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined; + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); + } + }); + return tx2; +}; +export default function buildKeymap>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { + const keys: { [key: string]: any } = {}; + + function bind(key: string, cmd: any) { + if (mapKeys) { + const mapped = mapKeys[key]; + if (mapped === false) return; + if (mapped) key = mapped; + } + keys[key] = cmd; + } + + bind("Mod-z", undo); + bind("Shift-Mod-z", redo); + bind("Backspace", undoInputRule); + + !mac && bind("Mod-y", redo); + + bind("Alt-ArrowUp", joinUp); + bind("Alt-ArrowDown", joinDown); + bind("Mod-BracketLeft", lift); + bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { + dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); + (document.activeElement as any).blur?.(); + SelectionManager.DeselectAll(); + }); + + bind("Mod-b", toggleMark(schema.marks.strong)); + bind("Mod-B", toggleMark(schema.marks.strong)); + + bind("Mod-e", toggleMark(schema.marks.em)); + bind("Mod-E", toggleMark(schema.marks.em)); + + bind("Mod-u", toggleMark(schema.marks.underline)); + bind("Mod-U", toggleMark(schema.marks.underline)); + + bind("Mod-`", toggleMark(schema.marks.code)); + + bind("Ctrl-.", wrapInList(schema.nodes.bullet_list)); + + bind("Ctrl-n", wrapInList(schema.nodes.ordered_list)); + + bind("Ctrl->", wrapIn(schema.nodes.blockquote)); + + // bind("^", (state: EditorState, dispatch: (tx: Transaction) => void) => { + // let newNode = schema.nodes.footnote.create({}); + // if (dispatch && state.selection.from === state.selection.to) { + // let tr = state.tr; + // tr.replaceSelectionWith(newNode); // replace insertion with a footnote. + // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display + // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)))); + // return true; + // } + // return false; + // }); + + + const cmd = chainCommands(exitCode, (state, dispatch) => { + if (dispatch) { + dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView()); + return true; + } + return false; + }); + bind("Mod-Enter", cmd); + bind("Shift-Enter", cmd); + mac && bind("Ctrl-Enter", cmd); + + + bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph)); + + bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block)); + + for (let i = 1; i <= 6; i++) { + bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i })); + } + + const hr = schema.nodes.horizontal_rule; + bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => { + dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); + return true; + }); + + bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const ref = state.selection; + const range = ref.$from.blockRange(ref.$to); + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + })) { // couldn't sink into an existing list, so wrap in a new one + const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); + if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + // when promoting to a list, assume list will format things so don't copy the stored marks. + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + })) { + console.log("bullet promote fail"); + } + } + }); + + bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + + if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + })) { + console.log("bullet demote fail"); + } + }); + bind("Ctrl-Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const layoutDoc = props.Document; + const originalDoc = layoutDoc.rootDocument || layoutDoc; + if (originalDoc instanceof Doc) { + const layoutKey = StrCast(originalDoc.layoutKey); + const newDoc = Docs.Create.TextDocument("", { + layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, + layoutKey, + _singleLine: BoolCast(originalDoc._singleLine), + x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) + }); + if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = originalDoc[layoutKey]; + } + FormattedTextBox.SelectOnLoad = newDoc[Id]; + props.addDocument(newDoc); + } + }); + + const splitMetadata = (marks: any, tx: Transaction) => { + marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); + marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); + return tx; + }; + const addTextOnRight = (force: boolean) => { + const layoutDoc = props.Document; + const originalDoc = layoutDoc.rootDocument || layoutDoc; + if (force || props.Document._singleLine) { + const layoutKey = StrCast(originalDoc.layoutKey); + const newDoc = Docs.Create.TextDocument("", { + layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, + layoutKey, + _singleLine: BoolCast(originalDoc._singleLine), + x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) + }); + if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = originalDoc[layoutKey]; + } + FormattedTextBox.SelectOnLoad = newDoc[Id]; + props.addDocument(newDoc); + return true; + } + return false; + }; + bind("Alt-Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + return addTextOnRight(true); + }); + bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + if (addTextOnRight(false)) return true; + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + if (!splitListItem(schema.nodes.list_item)(state, dispatch)) { + if (!splitBlockKeepMarks(state, (tx3: Transaction) => { + splitMetadata(marks, tx3); + if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { + dispatch(tx3); + } + })) { + return false; + } + } + return true; + }); + bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + dispatch(splitMetadata(marks, state.tr)); + return false; + }); + bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { + return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); + }); + const path = (state.doc.resolve(state.selection.from - 1) as any).path; + const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; + const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator; + if (anchor >= 0) { + const textsel = TextSelection.create(state.doc, anchor, range!.end); + const text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; + let whitespace = text.length - 1; + for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } + if (text.endsWith(":")) { + dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). + addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); + } + } + return false; + }); + + + return keys; +} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss new file mode 100644 index 000000000..36da769c3 --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -0,0 +1,121 @@ +@import "../../globalCssVariables"; + +.button-dropdown-wrapper { + position: relative; + + .dropdown-button { + width: 15px; + padding-left: 5px; + padding-right: 5px; + } + + .dropdown-button-combined { + width: 50px; + display: flex; + justify-content: space-between; + + svg:nth-child(2) { + margin-top: 2px; + } + } + + .dropdown { + position: absolute; + top: 35px; + left: 0; + background-color: #323232; + color: $light-color-secondary; + border: 1px solid #4d4d4d; + border-radius: 0 6px 6px 6px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + min-width: 150px; + padding: 5px; + font-size: 12px; + z-index: 10001; + + button { + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + padding: 6px; + margin: 5px 0; + font-size: 10px; + + &:hover { + background-color: black; + } + + &:last-child { + margin-bottom: 0; + } + } + } + + input { + color: black; + } +} + +.link-menu { + .divider { + background-color: white; + height: 1px; + width: 100%; + } +} + +.color-preview-button { + .color-preview { + width: 100%; + height: 3px; + margin-top: 3px; + } +} + +.color-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + button.color-button { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: 2px solid transparent !important; + padding: 3px; + + &.active { + border: 2px solid white !important; + } + } +} + +select { + background-color: #323232; + color: white; + border: 1px solid black; + // border-top: none; + // border-bottom: none; + font-size: 12px; + height: 100%; + margin-right: 3px; + + &:focus, + &:hover { + background-color: black; + } + + &::-ms-expand { + color: white; + } +} + +.row-2 { + display: flex; + justify-content: space-between; + + >div { + display: flex; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx new file mode 100644 index 000000000..cc04e0d6d --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -0,0 +1,875 @@ +import React = require("react"); +import AntimodeMenu from "../../AntimodeMenu"; +import { observable, action, } from "mobx"; +import { observer } from "mobx-react"; +import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; +import { schema } from "./schema_rts"; +import { EditorView } from "prosemirror-view"; +import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons"; +import { updateBullets } from "./ProsemirrorExampleTransfer"; +import { FieldViewProps } from "../FieldView"; +import { Cast, StrCast } from "../../../../new_fields/Types"; +import { FormattedTextBoxProps } from "./FormattedTextBox"; +import { unimplementedFunction, Utils } from "../../../../Utils"; +import { wrapInList } from "prosemirror-schema-list"; +import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../new_fields/SchemaHeaderField'; +import "./RichTextMenu.scss"; +import { DocServer } from "../../../DocServer"; +import { Doc } from "../../../../new_fields/Doc"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { LinkManager } from "../../../util/LinkManager"; +const { toggleMark, setBlockType } = require("prosemirror-commands"); + +library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); + +@observer +export default class RichTextMenu extends AntimodeMenu { + static Instance: RichTextMenu; + public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable + + private view?: EditorView; + public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + + public _brushMap: Map> = new Map(); + private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; + private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; + private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[]; + private fontColors: (string | undefined)[]; + private highlightColors: (string | undefined)[]; + + @observable private collapsed: boolean = false; + @observable private boldActive: boolean = false; + @observable private italicsActive: boolean = false; + @observable private underlineActive: boolean = false; + @observable private strikethroughActive: boolean = false; + @observable private subscriptActive: boolean = false; + @observable private superscriptActive: boolean = false; + + @observable private activeFontSize: string = ""; + @observable private activeFontFamily: string = ""; + @observable private activeListType: string = ""; + + @observable private brushIsEmpty: boolean = true; + @observable private brushMarks: Set = new Set(); + @observable private showBrushDropdown: boolean = false; + + @observable private activeFontColor: string = "black"; + @observable private showColorDropdown: boolean = false; + + @observable private activeHighlightColor: string = "transparent"; + @observable private showHighlightDropdown: boolean = false; + + @observable private currentLink: string | undefined = ""; + @observable private showLinkDropdown: boolean = false; + + constructor(props: Readonly<{}>) { + super(props); + RichTextMenu.Instance = this; + this._canFade = false; + + this.fontSizeOptions = [ + { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize }, + { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, + { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option + ]; + + this.fontFamilyOptions = [ + { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } }, + { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } }, + { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } }, + { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } }, + { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } }, + { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } }, + { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } }, + { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, + // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true }, + ]; + + this.listTypeOptions = [ + { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType }, + { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, + ]; + + this.fontColors = [ + DarkPastelSchemaPalette.get("pink2"), + DarkPastelSchemaPalette.get("purple4"), + DarkPastelSchemaPalette.get("bluegreen1"), + DarkPastelSchemaPalette.get("yellow4"), + DarkPastelSchemaPalette.get("red2"), + DarkPastelSchemaPalette.get("bluegreen7"), + DarkPastelSchemaPalette.get("bluegreen5"), + DarkPastelSchemaPalette.get("orange1"), + "#757472", + "#000" + ]; + + this.highlightColors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + "white", + "transparent" + ]; + } + + @action + changeView(view: EditorView) { + this.view = view; + } + + update(view: EditorView, lastState: EditorState | undefined) { + this.updateFromDash(view, lastState, this.editorProps); + } + + + public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { + if (this.view) { + const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). + addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + return this.view.state.selection.$from.nodeAfter?.text || ""; + } + return ""; + } + + @action + public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { + if (!view) { + console.log("no editor? why?"); + return; + } + this.view = view; + const state = view.state; + props && (this.editorProps = props); + + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; + + // update active marks + const activeMarks = this.getActiveMarksOnSelection(); + this.setActiveMarkButtons(activeMarks); + + // update active font family and size + const active = this.getActiveFontStylesOnSelection(); + const activeFamilies = active && active.get("families"); + const activeSizes = active && active.get("sizes"); + + this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; + this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various"; + + // update link in current selection + const targetTitle = await this.getTextLinkTargetTitle(); + this.setCurrentLink(targetTitle); + } + + setMark = (mark: Mark, state: EditorState, dispatch: any) => { + if (mark) { + const node = (state.selection as NodeSelection).node; + if (node?.type === schema.nodes.ordered_list) { + let attrs = node.attrs; + if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; + if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; + const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); + dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); + } else { + toggleMark(mark.type, mark.attrs)(state, (tx: any) => { + const { from, $from, to, empty } = tx.selection; + if (!tx.doc.rangeHasMark(from, to, mark.type)) { + toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); + } else dispatch(tx); + }); + } + } + } + + // finds font sizes and families in selection + getActiveFontStylesOnSelection() { + if (!this.view) return; + + const activeFamilies: string[] = []; + const activeSizes: string[] = []; + const state = this.view.state; + const pos = this.view.state.selection.$from; + const ref_node = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => { + m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); + }); + } + + const styles = new Map(); + styles.set("families", activeFamilies); + styles.set("sizes", activeSizes); + return styles; + } + + getMarksInSelection(state: EditorState) { + const found = new Set(); + const { from, to } = state.selection as TextSelection; + state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); + return found; + } + + //finds all active marks on selection in given group + getActiveMarksOnSelection() { + if (!this.view) return; + + const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; + if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); + //current selection + const { empty, ranges, $to } = this.view.state.selection as TextSelection; + const state = this.view.state; + let activeMarks: MarkType[] = []; + if (!empty) { + activeMarks = markGroup.filter(mark => { + const has = false; + for (let i = 0; !has && i < ranges.length; i++) { + return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark); + } + return false; + }); + } + else { + const pos = this.view.state.selection.$from; + const ref_node: ProsNode | null = this.reference_node(pos); + if (ref_node !== null && ref_node !== this.view.state.doc) { + if (ref_node.isText) { + } + else { + return []; + } + activeMarks = markGroup.filter(mark_type => { + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + } + const mark = state.schema.mark(mark_type); + return ref_node.marks.includes(mark); + }); + } + } + return activeMarks; + } + + destroy() { + this.fadeOut(true); + } + + @action + setActiveMarkButtons(activeMarks: MarkType[] | undefined) { + if (!activeMarks) return; + + this.boldActive = false; + this.italicsActive = false; + this.underlineActive = false; + this.strikethroughActive = false; + this.subscriptActive = false; + this.superscriptActive = false; + + activeMarks.forEach(mark => { + switch (mark.name) { + case "strong": this.boldActive = true; break; + case "em": this.italicsActive = true; break; + case "underline": this.underlineActive = true; break; + case "strikethrough": this.strikethroughActive = true; break; + case "subscript": this.subscriptActive = true; break; + case "superscript": this.superscriptActive = true; break; + } + }); + } + + createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) { + const self = this; + function onClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && command && command(self.view.state, self.view.dispatch, self.view); + self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); + self.setActiveMarkButtons(self.getActiveMarksOnSelection()); + } + + return ( + + ); + } + + createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { + const items = options.map(({ title, label, hidden, style }) => { + if (hidden) { + return label === activeOption ? + : + ; + } + return label === activeOption ? + : + ; + }); + + const self = this; + function onChange(e: React.ChangeEvent) { + e.stopPropagation(); + e.preventDefault(); + options.forEach(({ label, mark, command }) => { + if (e.target.value === label) { + self.view && mark && command(mark, self.view); + } + }); + } + return ; + } + + createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { + const items = options.map(({ title, label, hidden, style }) => { + if (hidden) { + return label === activeOption ? + : + ; + } + return label === activeOption ? + : + ; + }); + + const self = this; + function onChange(val: string) { + options.forEach(({ label, node, command }) => { + if (val === label) { + self.view && node && command(node); + } + }); + } + return ; + } + + changeFontSize = (mark: Mark, view: EditorView) => { + this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch); + } + + changeFontFamily = (mark: Mark, view: EditorView) => { + this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch); + } + + // TODO: remove doesn't work + //remove all node type and apply the passed-in one to the selected text + changeListType = (nodeType: NodeType | undefined) => { + if (!this.view) return; + + if (nodeType === schema.nodes.bullet_list) { + wrapInList(nodeType)(this.view.state, this.view.dispatch); + } else { + const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view!.dispatch(tx2); + })) { + const tx2 = this.view.state.tr; + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view.dispatch(tx3); + } + } + } + + insertSummarizer(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + const mark = state.schema.marks.summarize.create(); + const tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + const content = tr.selection.content(); + const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + + @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } + + // todo: add brushes to brushMap to save with a style name + onBrushNameKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); + this._brushNameRef.current!.style.background = "lightGray"; + } + } + _brushNameRef = React.createRef(); + + createBrushButton() { + const self = this; + function onBrushClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && self.fillBrush(self.view.state, self.view.dispatch); + } + + let label = "Stored marks: "; + if (this.brushMarks && this.brushMarks.size > 0) { + this.brushMarks.forEach((mark: Mark) => { + const markType = mark.type; + label += markType.name; + label += ", "; + }); + label = label.substring(0, label.length - 2); + } else { + label = "No marks are currently stored"; + } + + const button = + ; + + const dropdownContent = +

+

{label}

+ + +
; + + return ( + + ); + } + + @action + clearBrush() { + RichTextMenu.Instance.brushIsEmpty = true; + RichTextMenu.Instance.brushMarks = new Set(); + } + + @action + fillBrush(state: EditorState, dispatch: any) { + if (!this.view) return; + + if (this.brushIsEmpty) { + const selected_marks = this.getMarksInSelection(this.view.state); + if (selected_marks.size >= 0) { + this.brushMarks = selected_marks; + this.brushIsEmpty = !this.brushIsEmpty; + } + } + else { + const { from, to, $from } = this.view.state.selection; + if (!this.view.state.selection.empty && $from && $from.nodeAfter) { + if (this.brushMarks && to - from > 0) { + this.view.dispatch(this.view.state.tr.removeMark(from, to)); + Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { + this.setMark(mark, this.view!.state, this.view!.dispatch); + }); + } + } + else { + this.brushIsEmpty = !this.brushIsEmpty; + } + } + } + + @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; } + @action setActiveColor(color: string) { this.activeFontColor = color; } + + createColorButton() { + const self = this; + function onColorClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + } + function changeColor(e: React.PointerEvent, color: string) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.setActiveColor(color); + self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + } + + const button = + ; + + const dropdownContent = +
+

Change font color:

+
+ {this.fontColors.map(color => { + if (color) { + return this.activeFontColor === color ? + : + ; + } + })} +
+
; + + return ( + + ); + } + + public insertColor(color: String, state: EditorState, dispatch: any) { + const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color }); + if (state.selection.empty) { + dispatch(state.tr.addStoredMark(colorMark)); + return false; + } + this.setMark(colorMark, state, dispatch); + } + + @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; } + @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } + + createHighlighterButton() { + const self = this; + function onHighlightClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + } + function changeHighlight(e: React.PointerEvent, color: string) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.setActiveHighlight(color); + self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + } + + const button = + ; + + const dropdownContent = +
+

Change highlight color:

+
+ {this.highlightColors.map(color => { + if (color) { + return this.activeHighlightColor === color ? + : + ; + } + })} +
+
; + + return ( + + ); + } + + insertHighlight(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch); + } + + @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; } + @action setCurrentLink(link: string) { this.currentLink = link; } + + createLinkButton() { + const self = this; + + function onLinkChange(e: React.ChangeEvent) { + self.setCurrentLink(e.target.value); + } + + const link = this.currentLink ? this.currentLink : ""; + + const button = ; + + const dropdownContent = +
+

Linked to:

+ + +
+ +
; + + return ( + + ); + } + + async getTextLinkTargetTitle() { + if (!this.view) return; + + const node = this.view.state.selection.$from.nodeAfter; + const link = node && node.marks.find(m => m.type.name === "link"); + if (link) { + const href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + const linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + const anchor1 = await Cast(linkDoc.anchor1, Doc); + const anchor2 = await Cast(linkDoc.anchor2, Doc); + const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) { + return StrCast(anchor2.title); + } + if (Doc.AreProtosEqual(currentDoc, anchor2)) { + return StrCast(anchor1.title); + } + } + } + } + } else { + return href; + } + } else { + return link.attrs.title; + } + } + } + + // TODO: should check for valid URL + makeLinkToURL = (target: String, lcoation: string) => { + if (!this.view) return; + + let node = this.view.state.selection.$from.nodeAfter; + let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); + this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + node = this.view.state.selection.$from.nodeAfter; + link = node && node.marks.find(m => m.type.name === "link"); + } + + deleteLink = () => { + if (!this.view) return; + + const node = this.view.state.selection.$from.nodeAfter; + const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link); + const href = link!.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + DocServer.GetRefField(linkclicked).then(async linkDoc => { + if (linkDoc instanceof Doc) { + LinkManager.Instance.deleteLink(linkDoc); + this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link)); + } + }); + } + } else { + if (node) { + const { tr, schema, selection } = this.view.state; + const extension = this.linkExtend(selection.$anchor, href); + this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link)); + } + } + } + } + + linkExtend($start: ResolvedPos, href: string) { + const mark = this.view!.state.schema.marks.link; + + let startIndex = $start.index(); + let endIndex = $start.indexAfter(); + + while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--; + while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++; + + let startPos = $start.start(); + let endPos = startPos; + for (let i = 0; i < endIndex; i++) { + const size = $start.parent.child(i).nodeSize; + if (i < startIndex) startPos += size; + endPos += size; + } + return { from: startPos, to: endPos }; + } + + reference_node(pos: ResolvedPos): ProsNode | null { + if (!this.view) return null; + + let ref_node: ProsNode = this.view.state.doc; + if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { + ref_node = pos.nodeBefore; + } + else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { + ref_node = pos.nodeAfter; + } + else if (pos.pos > 0) { + let skip = false; + for (let i: number = pos.pos - 1; i > 0; i--) { + this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => { + if (node.isLeaf && !skip) { + ref_node = node; + skip = true; + } + + }); + } + } + if (!ref_node.isLeaf && ref_node.childCount > 0) { + ref_node = ref_node.child(0); + } + return ref_node; + } + + @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; } + @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } + + @action + toggleMenuPin = (e: React.MouseEvent) => { + this.Pinned = !this.Pinned; + if (!this.Pinned) { + this.fadeOut(true); + } + } + + @action + protected toggleCollapse = (e: React.MouseEvent) => { + this.collapsed = !this.collapsed; + setTimeout(() => { + const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width); + RichTextMenu.Instance.jumpTo(x, this._top); + }, 0); + } + + render() { + + const row1 =
{[ + this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), + this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), + this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), + this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), + this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), + this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), + this.createColorButton(), + this.createHighlighterButton(), + this.createLinkButton(), + this.createBrushButton(), + this.createButton("indent", "Summarize", undefined, this.insertSummarizer), + ]}
; + + const row2 =
+
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), + this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), + this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]} +
+
+
+ +
+ + {this.getDragger()} +
+
; + + return ( +
+ {this.getElementWithRows([row1, row2], 2, false)} +
+ ); + } +} + +interface ButtonDropdownProps { + view?: EditorView; + button: JSX.Element; + dropdownContent: JSX.Element; + openDropdownOnButton?: boolean; +} + +@observer +class ButtonDropdown extends React.Component { + + @observable private showDropdown: boolean = false; + private ref: HTMLDivElement | null = null; + + componentDidMount() { + document.addEventListener("pointerdown", this.onBlur); + } + + componentWillUnmount() { + document.removeEventListener("pointerdown", this.onBlur); + } + + @action + setShowDropdown(show: boolean) { + this.showDropdown = show; + } + @action + toggleDropdown() { + this.showDropdown = !this.showDropdown; + } + + onDropdownClick = (e: React.PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.props.view && this.props.view.focus(); + this.toggleDropdown(); + } + + onBlur = (e: PointerEvent) => { + setTimeout(() => { + if (this.ref !== null && !this.ref.contains(e.target as Node)) { + this.setShowDropdown(false); + } + }, 0); + } + + render() { + return ( +
this.ref = node}> + {this.props.openDropdownOnButton ? + : + <> + {this.props.button} + + } + + {this.showDropdown ? this.props.dropdownContent : (null)} +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts new file mode 100644 index 000000000..335094e23 --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -0,0 +1,319 @@ +import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules"; +import { NodeSelection, TextSelection } from "prosemirror-state"; +import { DataSym, Doc } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { Cast, NumCast } from "../../../../new_fields/Types"; +import { returnFalse, Utils } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { Docs, DocUtils } from "../../../documents/Documents"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { wrappingInputRule } from "./prosemirrorPatches"; +import RichTextMenu from "./RichTextMenu"; +import { schema } from "./schema_rts"; + +export class RichTextRules { + public Document: Doc; + public TextBox: FormattedTextBox; + public EnteringStyle: boolean = false; + constructor(doc: Doc, textBox: FormattedTextBox) { + this.Document = doc; + this.TextBox = textBox; + } + public inpRules = { + rules: [ + ...smartQuotes, + ellipsis, + emDash, + + // > blockquote + wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote), + + // 1. ordered list + wrappingInputRule( + /^1\.\s$/, + schema.nodes.ordered_list, + () => { + return ({ mapStyle: "decimal", bulletStyle: 1 }); + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } }) + ), + // a. alphabbetical list + wrappingInputRule( + /^a\.\s$/, + schema.nodes.ordered_list, + // match => { + () => { + return ({ mapStyle: "alpha", bulletStyle: 1 }); + // return ({ order: +match[1] }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } }) + ), + + // * bullet list + wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list), + + // ``` code block + textblockTypeInputRule(/^```$/, schema.nodes.code_block), + + // create an inline view of a tag stored under the '#' field + new InputRule( + new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), + (state, match, start, end) => { + const tag = match[1]; + if (!tag) return state.tr; + this.Document[DataSym]["#"] = tag; + const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), + + // # heading + textblockTypeInputRule( + new RegExp(/^(#{1,6})\s$/), + schema.nodes.heading, + match => { + return ({ level: match[1].length }); + } + ), + + // set the font size using # + new InputRule( + new RegExp(/%([0-9]+)\s$/), + (state, match, start, end) => { + const size = Number(match[1]); + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); + }), + + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ : ]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc + new InputRule( + new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? \-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/), + (state, match, start, end) => { + const fieldKey = match[1]; + const docid = match[3]?.substring(1); + const value = match[2]?.substring(1); + if (!fieldKey) { + if (docid) { + DocServer.GetRefField(docid).then(docx => { + const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid); + DocUtils.Publish(target, docid, returnFalse, returnFalse); + DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to"); + }); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); + } + return state.tr; + } + if (value !== "" && value !== undefined) { + const num = value.match(/^[0-9.]/); + this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value); + } + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), + // create an inline view of a document {{ : }} // {{:Doc}} => show default view of document {{}} => show layout for this doc {{ : Doc}} => show layout for another doc + new InputRule( + new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/), + (state, match, start, end) => { + const fieldKey = match[1] || ""; + const fieldParam = match[2]?.replace("…", "...") || ""; + const docid = match[3]?.substring(1); + if (!fieldKey && !docid) return state.tr; + docid && DocServer.GetRefField(docid).then(docx => { + if (!(docx instanceof Doc && docx)) { + const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid); + DocUtils.Publish(docx, docid, returnFalse, returnFalse); + } + }); + const node = (state.doc.resolve(start) as any).nodeAfter; + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() }); + const sm = state.storedMarks || undefined; + return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + }), + new InputRule( + new RegExp(/>>$/), + (state, match, start, end) => { + const textDoc = this.Document[DataSym]; + const numInlines = NumCast(textDoc.inlineTextCount); + textDoc.inlineTextCount = numInlines + 1; + const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to + const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation + const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" }); + textDocInline.title = inlineFieldKey; // give the annotation its own title + textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc + textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point + textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] + textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); + textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text + textDoc[inlineFieldKey] = ""; // set a default value for the annotation + const node = (state.doc.resolve(start) as any).nodeAfter; + const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" }); + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced; + }), + // stop using active style + new InputRule( + new RegExp(/%%$/), + (state, match, start, end) => { + const tr = state.tr.deleteRange(start, end); + const marks = state.tr.selection.$anchor.nodeBefore?.marks; + return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr; + }), + + // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/[ti!x]$/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; + const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??"; + const node = (state.doc.resolve(start) as any).nodeAfter; + if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), + + // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/(%d|d)$/), + (state, match, start, end) => { + if (!match[0].startsWith("%") && !this.EnteringStyle) return null; + const pos = (state.doc.resolve(start) as any); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; + } + } + return null; + }), + + // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/(%h|h)$/), + (state, match, start, end) => { + if (!match[0].startsWith("%") && !this.EnteringStyle) return null; + const pos = (state.doc.resolve(start) as any); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; + } + } + return null; + }), + // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/(%q|q)$/), + (state, match, start, end) => { + if (!match[0].startsWith("%") && !this.EnteringStyle) return null; + const pos = (state.doc.resolve(start) as any); + if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { + const node = state.selection.node; + return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); + } + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; + } + } + return null; + }), + + + // center justify text + new InputRule( + new RegExp(/%\^$/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + // left justify text + new InputRule( + new RegExp(/%\[$/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + // right justify text + new InputRule( + new RegExp(/%\]$/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + new InputRule( + new RegExp(/%\(/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || []; + const mark = state.schema.marks.summarizeInclusive.create(); + sm.push(mark); + const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); + const content = selected.selection.content(); + const replaced = node ? selected.replaceRangeWith(start, end, + schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); + }), + new InputRule( + new RegExp(/%\)/), + (state, match, start, end) => { + return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); + }), + new InputRule( + new RegExp(/%f$/), + (state, match, start, end) => { + const newNode = schema.nodes.footnote.create({}); + const tr = state.tr; + tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. + return tr.setSelection(new NodeSelection( // select the footnote node to open its display + tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))); + }), + + // activate a style by name using prefix '%' + new InputRule( + new RegExp(/%[a-z]+$/), + (state, match, start, end) => { + const color = match[0].substring(1, match[0].length); + const marks = RichTextMenu.Instance._brushMap.get(color); + if (marks) { + const tr = state.tr.deleteRange(start, end); + return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; + } + const isValidColor = (strColor: string) => { + const s = new Option().style; + s.color = strColor; + return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned + }; + if (isValidColor(color)) { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); + } + return null; + }), + ] + }; +} diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx new file mode 100644 index 000000000..33caf5751 --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -0,0 +1,718 @@ +import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { keymap } from "prosemirror-keymap"; +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; +import { StepMap } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import * as ReactDOM from 'react-dom'; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { List } from "../../../../new_fields/List"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { Docs } from "../../../documents/Documents"; +import { CollectionViewType } from "../../collections/CollectionView"; +import { DocumentView } from "../DocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { Transform } from "../../../util/Transform"; +import React = require("react"); + +import { schema } from "./schema_rts"; + +export class OrderedListView { + update(node: any) { + return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update + } +} + +export class ImageResizeView { + _handle: HTMLElement; + _img: HTMLElement; + _outer: HTMLElement; + constructor(node: any, view: any, getPos: any, addDocTab: any) { + //moved + this._handle = document.createElement("span"); + this._img = document.createElement("img"); + this._outer = document.createElement("span"); + this._outer.style.position = "relative"; + this._outer.style.width = node.attrs.width; + this._outer.style.height = node.attrs.height; + this._outer.style.display = "inline-block"; + this._outer.style.overflow = "hidden"; + (this._outer.style as any).float = node.attrs.float; + //moved + this._img.setAttribute("src", node.attrs.src); + this._img.style.width = "100%"; + this._handle.style.position = "absolute"; + this._handle.style.width = "20px"; + this._handle.style.height = "20px"; + this._handle.style.backgroundColor = "blue"; + this._handle.style.borderRadius = "15px"; + this._handle.style.display = "none"; + this._handle.style.bottom = "-10px"; + this._handle.style.right = "-10px"; + const self = this; + //moved + this._img.onclick = function (e: any) { + e.stopPropagation(); + e.preventDefault(); + if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) { + view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); + } + }; + //moved + this._img.onpointerdown = function (e: any) { + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + DocServer.GetRefField(node.attrs.docid).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document, + document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false)); + } + }; + //moved + this._handle.onpointerdown = function (e: any) { + e.preventDefault(); + e.stopPropagation(); + const wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); + const hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); + const startX = e.pageX; + const startWidth = parseFloat(node.attrs.width); + const onpointermove = (e: any) => { + const currentX = e.pageX; + const diffInPx = currentX - startX; + self._outer.style.width = `${startWidth + diffInPx}`; + self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`; + }; + + const onpointerup = () => { + document.removeEventListener("pointermove", onpointermove); + document.removeEventListener("pointerup", onpointerup); + const pos = view.state.selection.from; + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height })); + view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos)))); + }; + + document.addEventListener("pointermove", onpointermove); + document.addEventListener("pointerup", onpointerup); + }; + //Moved + this._outer.appendChild(this._img); + this._outer.appendChild(this._handle); + (this as any).dom = this._outer; + } + + selectNode() { + this._img.classList.add("ProseMirror-selectednode"); + + this._handle.style.display = ""; + } + + deselectNode() { + this._img.classList.remove("ProseMirror-selectednode"); + + this._handle.style.display = "none"; + } +} + +export class DashDocCommentView { + _collapsed: HTMLElement; + _view: any; + constructor(node: any, view: any, getPos: any) { + //moved + this._collapsed = document.createElement("span"); + this._collapsed.className = "formattedTextBox-inlineComment"; + this._collapsed.id = "DashDocCommentView-" + node.attrs.docid; + this._view = view; + //moved + const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor + for (let i = getPos() + 1; i < view.state.doc.content.size; i++) { + const m = view.state.doc.nodeAt(i); + if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) { + return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; + } + } + const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" }); + view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc)); + setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0); + return undefined; + }; + //moved + this._collapsed.onpointerdown = (e: any) => { + e.stopPropagation(); + }; + //moved + this._collapsed.onpointerup = (e: any) => { + const target = targetNode(); + if (target) { + const expand = target.hidden; + const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); + view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs + setTimeout(() => { + expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); + try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { } + }, 0); + } + e.stopPropagation(); + }; + //moved + this._collapsed.onpointerenter = (e: any) => { + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + e.preventDefault(); + e.stopPropagation(); + }; + //moved + this._collapsed.onpointerleave = (e: any) => { + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); + e.preventDefault(); + e.stopPropagation(); + }; + + (this as any).dom = this._collapsed; + } + //moved + selectNode() { } +} + +export class DashDocView { + _dashSpan: HTMLDivElement; + _outer: HTMLElement; + _dashDoc: Doc | undefined; + _reactionDisposer: IReactionDisposer | undefined; + _renderDisposer: IReactionDisposer | undefined; + _textBox: FormattedTextBox; + + getDocTransform = () => { + const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); + return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); + } + contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; + + outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._textBox = tbox; + this._dashSpan = document.createElement("div"); + this._outer = document.createElement("span"); + this._outer.style.position = "relative"; + this._outer.style.textIndent = "0"; + this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + this._outer.style.width = node.attrs.width; + this._outer.style.height = node.attrs.height; + this._outer.style.display = node.attrs.hidden ? "none" : "inline-block"; + // this._outer.style.overflow = "hidden"; // bcz: not sure if this is needed. if it's used, then the doc doesn't highlight when you hover over a docComment + (this._outer.style as any).float = node.attrs.float; + + this._dashSpan.style.width = node.attrs.width; + this._dashSpan.style.height = node.attrs.height; + this._dashSpan.style.position = "absolute"; + this._dashSpan.style.display = "inline-block"; + this._dashSpan.onpointerleave = () => { + const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = ""; + } + }; + this._dashSpan.onpointerenter = () => { + const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = "orange"; + } + }; + const removeDoc = () => { + const pos = getPos(); + const ns = new NodeSelection(view.state.doc.resolve(pos)); + view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); + return true; + }; + const alias = node.attrs.alias; + + const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; + DocServer.GetRefField(docid + alias).then(async dashDoc => { + if (!(dashDoc instanceof Doc)) { + alias && DocServer.GetRefField(docid).then(async dashDocBase => { + if (dashDocBase instanceof Doc) { + const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); + aliasedDoc.layoutKey = "layout"; + node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + self.doRender(aliasedDoc, removeDoc, node, view, getPos); + } + }); + } else { + self.doRender(dashDoc, removeDoc, node, view, getPos); + } + }); + const self = this; + this._dashSpan.onkeydown = function (e: any) { + e.stopPropagation(); + if (e.key === "Tab" || e.key === "Enter") { + e.preventDefault(); + } + }; + this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onwheel = function (e: any) { e.preventDefault(); }; + this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; + this._outer.appendChild(this._dashSpan); + (this as any).dom = this._outer; + } + + doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { + this._dashDoc = dashDoc; + const self = this; + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); + + if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); + else { + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => { + this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; + this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; + this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + }, { fireImmediately: true }); + const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { + ReactDOM.unmountComponentAtNode(this._dashSpan); + + ReactDOM.render(, this._dashSpan); + if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } catch (e) { + console.log(e); + } + } + }; + this._renderDisposer?.(); + this._renderDisposer = reaction(() => { + // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { + // finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here? + // } + const layoutKey = StrCast(finalLayout.layoutKey); + const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; + if (finalLayout !== dashDoc && finalKey) { + const finalLayoutField = finalLayout[finalKey]; + if (finalLayoutField instanceof ObjectField) { + finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; + }, + (res) => doReactRender(res.finalLayout, res.resolvedDataDoc), + { fireImmediately: true }); + } + } + destroy() { + ReactDOM.unmountComponentAtNode(this._dashSpan); + this._reactionDisposer?.(); + } +} + +export class DashFieldView { + _fieldWrapper: HTMLDivElement; // container for label and value + _labelSpan: HTMLSpanElement; // field label + _fieldSpan: HTMLSpanElement; // field value + _fieldCheck: HTMLInputElement; + _enumerables: HTMLDivElement; // field value + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + @observable _dashDoc: Doc | undefined; + _fieldKey: string; + _options: Doc[] = []; + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldKey = node.attrs.fieldKey; + this._textBoxDoc = tbox.props.Document; + this._fieldWrapper = document.createElement("p"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.fontWeight = "bold"; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + + const self = this; + + this._enumerables = document.createElement("div"); + this._enumerables.style.width = "10px"; + this._enumerables.style.height = "10px"; + this._enumerables.style.position = "relative"; + this._enumerables.style.display = "none"; + + //Moved + this._enumerables.onpointerdown = async (e) => { + e.stopPropagation(); + const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); + collview instanceof Doc && tbox.props.addDocTab(collview, "onRight"); + }; + //Moved + const updateText = (forceMatch: boolean) => { + self._enumerables.style.display = "none"; + const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText; + + // look for a document whose id === the fieldKey being displayed. If there's a match, then that document + // holds the different enumerated values for the field in the titles of its collected documents. + // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. + DocServer.GetRefField(self._fieldKey).then(options => { + let modText = ""; + (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + if (modText) { + self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText; + Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []); + } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key + else if (self._fieldSpan.innerText.startsWith(":=")) { + self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); + } else if (self._fieldSpan.innerText.startsWith("=:=")) { + Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3)); + } else { + self._dashDoc![self._fieldKey] = newText; + } + }); + }; + + //Moved + this._fieldCheck = document.createElement("input"); + this._fieldCheck.id = Utils.GenerateGuid(); + this._fieldCheck.type = "checkbox"; + this._fieldCheck.style.position = "relative"; + this._fieldCheck.style.display = "none"; + this._fieldCheck.style.minWidth = "12px"; + this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; + this._fieldCheck.onchange = function (e: any) { + self._dashDoc![self._fieldKey] = e.target.checked; + }; + + this._fieldSpan = document.createElement("span"); + this._fieldSpan.id = Utils.GenerateGuid(); + this._fieldSpan.contentEditable = "true"; + this._fieldSpan.style.position = "relative"; + this._fieldSpan.style.display = "none"; + this._fieldSpan.style.minWidth = "12px"; + this._fieldSpan.style.fontSize = "large"; + this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; }; + this._fieldSpan.onblur = function (e: any) { updateText(false); }; + + // MOVED + const setDashDoc = (doc: Doc) => { + self._dashDoc = doc; + if (self._options?.length && !self._dashDoc[self._fieldKey]) { + self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); + } + this._labelSpan.innerHTML = `${self._fieldKey}: `; + const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); + this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; + this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none"; + }; + + //Moved + this._fieldSpan.onkeydown = function (e: any) { + e.stopPropagation(); + if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { + if (window.getSelection) { + const range = document.createRange(); + range.selectNodeContents(self._fieldSpan); + window.getSelection()!.removeAllRanges(); + window.getSelection()!.addRange(range); + } + e.preventDefault(); + } + if (e.key === "Enter") { + e.preventDefault(); + e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); + updateText(true); + } + }; + + this._labelSpan = document.createElement("span"); + this._labelSpan.style.position = "relative"; + this._labelSpan.style.fontSize = "small"; + this._labelSpan.title = "click to see related tags"; + this._labelSpan.style.fontSize = "x-small"; + this._labelSpan.onpointerdown = function (e: any) { + e.stopPropagation(); + let container = tbox.props.ContainingCollectionView; + while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { + container = container.props.ContainingCollectionView; + } + if (container) { + const alias = Doc.MakeAlias(container.props.Document); + alias.viewType = CollectionViewType.Time; + let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); + if (!list) { + alias.schemaColumns = list = new List(); + } + list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb")); + list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); + alias._pivotField = self._fieldKey; + tbox.props.addDocTab(alias, "onRight"); + } + }; + this._labelSpan.innerHTML = `${self._fieldKey}: `; + //MOVED + if (node.attrs.docid) { + DocServer.GetRefField(node.attrs.docid). + then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc))); + } else { + setDashDoc(tbox.props.DataDoc || tbox.dataDoc); + } + + //Moved + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes + const dashVal = this._dashDoc?.[self._fieldKey]; + return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal; + }, fval => { + const boolVal = Cast(fval, "boolean", null); + if (boolVal === true || boolVal === false) { + this._fieldCheck.checked = boolVal; + } else { + this._fieldSpan.innerHTML = Field.toString(fval as Field) || ""; + } + this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none"; + this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none"; + }, { fireImmediately: true }); + + //MOVED IN ORDER + this._fieldWrapper.appendChild(this._labelSpan); + this._fieldWrapper.appendChild(this._fieldCheck); + this._fieldWrapper.appendChild(this._fieldSpan); + this._fieldWrapper.appendChild(this._enumerables); + (this as any).dom = this._fieldWrapper; + //updateText(false); + } + //MOVED + destroy() { + this._reactionDisposer?.(); + } + //moved + selectNode() { } +} + +export class FootnoteView { + innerView: any; + outerView: any; + node: any; + dom: any; + getPos: any; + + constructor(node: any, view: any, getPos: any) { + // We'll need these later + this.node = node; + this.outerView = view; + this.getPos = getPos; + + // The node's representation in the editor (empty, for now) + this.dom = document.createElement("footnote"); + this.dom.addEventListener("pointerup", this.toggle, true); + // These are used when the footnote is selected + this.innerView = null; + } + selectNode() { + const attrs = { ...this.node.attrs }; + attrs.visibility = true; + this.dom.classList.add("ProseMirror-selectednode"); + if (!this.innerView) this.open(); + } + + deselectNode() { + const attrs = { ...this.node.attrs }; + attrs.visibility = false; + this.dom.classList.remove("ProseMirror-selectednode"); + if (this.innerView) this.close(); + } + open() { + // Append a tooltip to the outer node + const tooltip = this.dom.appendChild(document.createElement("div")); + tooltip.className = "footnote-tooltip"; + // And put a sub-ProseMirror into that + this.innerView = new EditorView(tooltip, { + // You can use any node as an editor document + state: EditorState.create({ + doc: this.node, + plugins: [keymap(baseKeymap), + keymap({ + "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch), + "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), + "Mod-b": toggleMark(schema.marks.strong) + }), + // new Plugin({ + // view(newView) { + // // TODO -- make this work with RichTextMenu + // // return FormattedTextBox.getToolTip(newView); + // } + // }) + ], + + }), + // This is the magic part + dispatchTransaction: this.dispatchInner.bind(this), + handleDOMEvents: { + pointerdown: ((view: any, e: PointerEvent) => { + // Kludge to prevent issues due to the fact that the whole + // footnote is node-selected (and thus DOM-selected) when + // the parent editor is focused. + e.stopPropagation(); + document.addEventListener("pointerup", this.ignore, true); + if (this.outerView.hasFocus()) this.innerView.focus(); + }) as any + } + + }); + setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0); + } + + ignore = (e: PointerEvent) => { + e.stopPropagation(); + document.removeEventListener("pointerup", this.ignore, true); + } + + toggle = () => { + if (this.innerView) this.close(); + else { + this.open(); + } + } + close() { + this.innerView && this.innerView.destroy(); + this.innerView = null; + this.dom.textContent = ""; + } + + dispatchInner(tr: any) { + const { state, transactions } = this.innerView.state.applyTransaction(tr); + this.innerView.updateState(state); + + if (!tr.getMeta("fromOutside")) { + const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); + for (const transaction of transactions) { + const steps = transaction.steps; + for (const step of steps) { + outerTr.step(step.map(offsetMap)); + } + } + if (outerTr.docChanged) this.outerView.dispatch(outerTr); + } + } + update(node: any) { + if (!node.sameMarkup(this.node)) return false; + this.node = node; + if (this.innerView) { + const state = this.innerView.state; + const start = node.content.findDiffStart(state.doc.content); + if (start !== null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); + const overlap = start - Math.min(endA, endB); + if (overlap > 0) { endA += overlap; endB += overlap; } + this.innerView.dispatch( + state.tr + .replace(start, endB, node.slice(start, endA)) + .setMeta("fromOutside", true)); + } + } + return true; + } + + destroy() { + if (this.innerView) this.close(); + } + + stopEvent(event: any) { + return this.innerView && this.innerView.dom.contains(event.target); + } + + ignoreMutation() { return true; } +} + +export class SummaryView { + _collapsed: HTMLElement; + _view: any; + constructor(node: any, view: any, getPos: any) { + this._collapsed = document.createElement("span"); + this._collapsed.className = this.className(node.attrs.visibility); + this._view = view; + const js = node.toJSON; + node.toJSON = function () { + return js.apply(this, arguments); + }; + + this._collapsed.onpointerdown = (e: any) => { + const visible = !node.attrs.visibility; + const attrs = { ...node.attrs, visibility: visible }; + let textSelection = TextSelection.create(view.state.doc, getPos() + 1); + if (!visible) { // update summarized text and save in attrs + textSelection = this.updateSummarizedText(getPos() + 1); + attrs.text = textSelection.content(); + attrs.textslice = attrs.text.toJSON(); + } + view.dispatch(view.state.tr. + setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) + replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it + setNodeMarkup(getPos(), undefined, attrs)); // update the attrs + e.preventDefault(); + e.stopPropagation(); + this._collapsed.className = this.className(visible); + }; + (this as any).dom = this._collapsed; + } + selectNode() { } + + deselectNode() { } + + className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); + + updateSummarizedText(start?: any) { + const mtype = this._view.state.schema.marks.summarize; + const mtypeInc = this._view.state.schema.marks.summarizeInclusive; + let endPos = start; + + const visited = new Set(); + for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) { + let skip = false; + this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { + if (node.isLeaf && !visited.has(node) && !skip) { + if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { + visited.add(node); + endPos = i + node.nodeSize - 1; + } + else skip = true; + } + }); + } + return TextSelection.create(this._view.state.doc, start, endPos); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx new file mode 100644 index 000000000..89908d8ee --- /dev/null +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -0,0 +1,81 @@ +import { TextSelection } from "prosemirror-state"; +import { Fragment, Node, Slice } from "prosemirror-model"; + +import React = require("react"); + +interface ISummaryView { + node: any; + view: any; + getPos: any; + self: any; +} +export class SummaryView extends React.Component { + + onPointerDown = (e: any) => { + const visible = !this.props.node.attrs.visibility; + const attrs = { ...this.props.node.attrs, visibility: visible }; + let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1); + if (!visible) { // update summarized text and save in attrs + textSelection = this.updateSummarizedText(this.props.getPos() + 1); + attrs.text = textSelection.content(); + attrs.textslice = attrs.text.toJSON(); + } + this.props.view.dispatch(this.props.view.state.tr. + setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) + replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it + setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs + e.preventDefault(); + e.stopPropagation(); + const _collapsed = document.getElementById('collapse') as HTMLElement; + _collapsed.className = this.className(visible); + } + + updateSummarizedText(start?: any) { + const mtype = this.props.view.state.schema.marks.summarize; + const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive; + let endPos = start; + + const visited = new Set(); + for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) { + let skip = false; + this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { + if (this.props.node.isLeaf && !visited.has(node) && !skip) { + if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { + visited.add(node); + endPos = i + this.props.node.nodeSize - 1; + } + else skip = true; + } + }); + } + return TextSelection.create(this.props.view.state.doc, start, endPos); + } + + className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); + + selectNode() { } + + deselectNode() { } + + render() { + const _view = this.props.node.view; + const js = this.props.node.toJSon; + + this.props.node.toJSON = function () { + return js.apply(this, arguments); + }; + + const spanCollapsedClassName = this.className(this.props.node.attrs.visibility); + + return ( + + + + ); + + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss new file mode 100644 index 000000000..e2149e9c1 --- /dev/null +++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss @@ -0,0 +1,372 @@ +@import "../views/globalCssVariables"; +.ProseMirror-menu-dropdown-wrap { + display: inline-block; + position: relative; +} + +.ProseMirror-menu-dropdown { + vertical-align: 1px; + cursor: pointer; + position: relative; + padding: 0 15px 0 4px; + background: white; + border-radius: 2px; + text-align: left; + font-size: 12px; + white-space: nowrap; + margin-right: 4px; + + &:after { + content: ""; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 2px); + } +} + +.ProseMirror-menu-submenu-wrap { + position: relative; + margin-right: -4px; +} + +.ProseMirror-menu-dropdown-menu, +.ProseMirror-menu-submenu { + font-size: 12px; + background: white; + border: 1px solid rgb(223, 223, 223); + min-width: 40px; + z-index: 50000; + position: absolute; + box-sizing: content-box; + + .ProseMirror-menu-dropdown-item { + cursor: pointer; + z-index: 100000; + text-align: left; + padding: 3px; + + &:hover { + background-color: $light-color-secondary; + } + } +} + + +.ProseMirror-menu-submenu-label:after { + content: ""; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 4px); +} + + .ProseMirror-icon { + display: inline-block; + // line-height: .8; + // vertical-align: -2px; /* Compensate for padding */ + // padding: 2px 8px; + cursor: pointer; + + &.ProseMirror-menu-disabled { + cursor: default; + } + + svg { + fill:white; + height: 1em; + } + + span { + vertical-align: text-top; + } + } + +.wrapper { + position: absolute; + pointer-events: all; + display: flex; + align-items: center; + transform: translateY(-85px); + + height: 35px; + background: #323232; + border-radius: 6px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + +} + +.tooltipMenu, .basic-tools { + z-index: 20000; + pointer-events: all; + padding: 3px; + padding-bottom: 5px; + display: flex; + align-items: center; + + .ProseMirror-example-setup-style hr { + padding: 2px 10px; + border: none; + margin: 1em 0; + } + + .ProseMirror-example-setup-style hr:after { + content: ""; + display: block; + height: 1px; + background-color: silver; + line-height: 2px; + } +} + +.menuicon { + width: 25px; + height: 25px; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + + #link-drag { + background-color: black; + } + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: white; + width: 18px; + height: 18px; + } +} + +.menuicon-active { + width: 25px; + height: 25px; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: greenyellow; + width: 18px; + height: 18px; + } +} + +#colorPicker { + position: relative; + + svg { + width: 18px; + height: 18px; + // margin-top: 11px; + } + + .buttonColor { + position: absolute; + top: 24px; + left: 1px; + width: 24px; + height: 4px; + margin-top: 0; + } +} + +#link-drag { + background-color: #323232; +} + +.underline svg { + margin-top: 13px; +} + + .font-size-indicator { + font-size: 12px; + padding-right: 0px; + } + .summarize{ + color: white; + height: 20px; + text-align: center; + } + + +.brush{ + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; + margin-right: 15px; +} + +.brush-active{ + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 3; + fill: greenyellow; + margin-right: 15px; +} + +.dragger-wrapper { + color: #eee; + height: 22px; + padding: 0 5px; + box-sizing: content-box; + cursor: grab; + + .dragger { + width: 18px; + height: 100%; + display: flex; + justify-content: space-evenly; + } + + .dragger-line { + width: 2px; + height: 100%; + background-color: black; + } +} + +.button-dropdown-wrapper { + display: flex; + align-content: center; + + &:hover { + background-color: black; + } +} + +.buttonSettings-dropdown { + + &.ProseMirror-menu-dropdown { + width: 10px; + height: 25px; + margin: 0; + padding: 0 2px; + background-color: #323232; + text-align: center; + + &:after { + border-top: 4px solid white; + right: 2px; + } + + &:hover { + background-color: black; + } + } + + &.ProseMirror-menu-dropdown-menu { + min-width: 150px; + left: -27px; + top: 31px; + background-color: #323232; + border: 1px solid #4d4d4d; + color: $light-color-secondary; + // border: none; + // border: 1px solid $light-color-secondary; + border-radius: 0 6px 6px 6px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + + .ProseMirror-menu-dropdown-item{ + cursor: default; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #323232; + } + + .button-setting, .button-setting-disabled { + padding: 2px; + border-radius: 2px; + } + + .button-setting:hover { + cursor: pointer; + background-color: black; + } + + .separated-button { + border-top: 1px solid $light-color-secondary; + padding-top: 6px; + } + + input { + color: black; + border: none; + border-radius: 1px; + padding: 3px; + } + + button { + padding: 6px; + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + + &:hover { + background-color: black; + } + } + } + + + } +} + +.colorPicker-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 3px; + margin-left: -3px; + width: calc(100% + 6px); +} + +button.colorPicker { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: none !important; + + &.active { + border: 2px solid white !important; + } +} diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts new file mode 100644 index 000000000..46bf481fb --- /dev/null +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -0,0 +1,296 @@ +import React = require("react"); +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { Doc } from "../../../../new_fields/Doc"; + + +const emDOM: DOMOutputSpecArray = ["em", 0]; +const strongDOM: DOMOutputSpecArray = ["strong", 0]; +const codeDOM: DOMOutputSpecArray = ["code", 0]; + +// :: Object [Specs](#model.MarkSpec) for the marks in the schema. +export const marks: { [index: string]: MarkSpec } = { + // :: MarkSpec A link. Has `href` and `title` attributes. `title` + // defaults to the empty string. Rendered and parsed as an `` + // element. + link: { + attrs: { + href: {}, + targetId: { default: "" }, + linkId: { default: "" }, + showPreview: { default: true }, + location: { default: null }, + title: { default: null }, + docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text + }, + inclusive: false, + parseDOM: [{ + tag: "a[href]", getAttrs(dom: any) { + return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") }; + } + }], + toDOM(node: any) { + return node.attrs.docref && node.attrs.title ? + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0]; + } + }, + + + // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. + pFontColor: { + attrs: { + color: { default: "#000" } + }, + inclusive: true, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { color: dom.getAttribute("color") }; + } + }], + toDOM(node: any) { + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; + } + }, + + marker: { + attrs: { + highlight: { default: "transparent" } + }, + inclusive: true, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { highlight: dom.getAttribute("backgroundColor") }; + } + }], + toDOM(node: any) { + return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; + } + }, + + // :: MarkSpec An emphasis mark. Rendered as an `` element. + // Has parse rules that also match `` and `font-style: italic`. + em: { + parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }], + toDOM() { return emDOM; } + }, + + // :: MarkSpec A strong mark. Rendered as ``, parse rules + // also match `` and `font-weight: bold`. + strong: { + parseDOM: [{ tag: "strong" }, + { tag: "b" }, + { style: "font-weight" }], + toDOM() { return strongDOM; } + }, + + strikethrough: { + parseDOM: [ + { tag: 'strike' }, + { style: 'text-decoration=line-through' }, + { style: 'text-decoration-line=line-through' } + ], + toDOM: () => ['span', { + style: 'text-decoration-line:line-through' + }] + }, + + subscript: { + excludes: 'superscript', + parseDOM: [ + { tag: 'sub' }, + { style: 'vertical-align=sub' } + ], + toDOM: () => ['sub'] + }, + + superscript: { + excludes: 'subscript', + parseDOM: [ + { tag: 'sup' }, + { style: 'vertical-align=super' } + ], + toDOM: () => ['sup'] + }, + + mbulletType: { + attrs: { + bulletType: { default: "decimal" } + }, + toDOM(node: any) { + return ['span', { + style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` + }]; + } + }, + + metadata: { + toDOM() { + return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; + } + }, + metadataKey: { + toDOM() { + return ['span', { style: 'font-style:italic; ' }]; + } + }, + metadataVal: { + toDOM() { + return ['span']; + } + }, + + summarizeInclusive: { + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + const style = getComputedStyle(p); + if (style.textDecoration === "underline") return null; + if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && + p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { + return null; + } + } + return false; + } + }, + ], + inclusive: true, + toDOM() { + return ['span', { + style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' + }]; + } + }, + + summarize: { + inclusive: false, + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + const style = getComputedStyle(p); + if (style.textDecoration === "underline") return null; + if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && + p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + return null; + } + } + return false; + } + }, + ], + toDOM() { + return ['span', { + style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' + }]; + } + }, + + underline: { + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + const style = getComputedStyle(p); + if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { + return null; + } + } + return false; + } + } + // { style: "text-decoration=underline" } + ], + toDOM: () => ['span', { + style: 'text-decoration:underline;text-decoration-style:line' + }] + }, + + search_highlight: { + attrs: { + selected: { default: false } + }, + parseDOM: [{ style: 'background: yellow' }], + toDOM(node: any) { + return ['span', { + style: `background: ${node.attrs.selected ? "orange" : "yellow"}` + }]; + } + }, + + // the id of the user who entered the text + user_mark: { + attrs: { + userid: { default: "" }, + modified: { default: "when?" }, // 1 second intervals since 1970 + }, + group: "inline", + toDOM(node: any) { + const uid = node.attrs.userid.replace(".", "").replace("@", ""); + const min = Math.round(node.attrs.modified / 12); + const hr = Math.round(min / 60); + const day = Math.round(hr / 60 / 24); + const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; + return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; + } + }, + // the id of the user who entered the text + user_tag: { + attrs: { + userid: { default: "" }, + modified: { default: "when?" }, // 1 second intervals since 1970 + tag: { default: "" } + }, + group: "inline", + inclusive: false, + toDOM(node: any) { + const uid = node.attrs.userid.replace(".", "").replace("@", ""); + return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; + } + }, + + + // :: MarkSpec Code font mark. Represented as a `` element. + code: { + parseDOM: [{ tag: "code" }], + toDOM() { return codeDOM; } + }, + + /* FONTS */ + pFontFamily: { + attrs: { + family: { default: "Crimson Text" }, + }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + const cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; + if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; + if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; + if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; + if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; + if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; + } + } + }], + toDOM: (node) => ['span', { + style: `font-family: "${node.attrs.family}";` + }] + }, + + /** FONT SIZES */ + pFontSize: { + attrs: { + fontSize: { default: 10 } + }, + parseDOM: [{ style: 'font-size: 10px;' }], + toDOM: (node) => ['span', { + style: `font-size: ${node.attrs.fontSize}px;` + }] + }, +}; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts new file mode 100644 index 000000000..e7bcf444a --- /dev/null +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -0,0 +1,264 @@ +import React = require("react"); +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; +import ParagraphNodeSpec from "./ParagraphNodeSpec"; + +const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], + preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; + +// :: Object +// [Specs](#model.NodeSpec) for the nodes defined in this schema. +export const nodes: { [index: string]: NodeSpec } = { + // :: NodeSpec The top level document node. + doc: { + content: "block+" + }, + + footnote: { + group: "inline", + content: "inline*", + inline: true, + attrs: { + visibility: { default: false } + }, + // This makes the view treat the node as a leaf, even though it + // technically has content + atom: true, + toDOM: () => ["footnote", 0], + parseDOM: [{ tag: "footnote" }] + }, + + paragraph: ParagraphNodeSpec, + + // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. + blockquote: { + content: "block+", + group: "block", + defining: true, + parseDOM: [{ tag: "blockquote" }], + toDOM() { return blockquoteDOM; } + }, + + // :: NodeSpec A horizontal rule (`
`). + horizontal_rule: { + group: "block", + parseDOM: [{ tag: "hr" }], + toDOM() { return hrDOM; } + }, + + // :: NodeSpec A heading textblock, with a `level` attribute that + // should hold the number 1 to 6. Parsed and serialized as `

` to + // `

` elements. + heading: { + attrs: { level: { default: 1 } }, + content: "inline*", + group: "block", + defining: true, + parseDOM: [{ tag: "h1", attrs: { level: 1 } }, + { tag: "h2", attrs: { level: 2 } }, + { tag: "h3", attrs: { level: 3 } }, + { tag: "h4", attrs: { level: 4 } }, + { tag: "h5", attrs: { level: 5 } }, + { tag: "h6", attrs: { level: 6 } }], + toDOM(node: any) { return ["h" + node.attrs.level, 0]; } + }, + + // :: NodeSpec A code listing. Disallows marks or non-text inline + // nodes by default. Represented as a `
` element with a
+    // `` element inside of it.
+    code_block: {
+        content: "text*",
+        marks: "",
+        group: "block",
+        code: true,
+        defining: true,
+        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+        toDOM() { return preDOM; }
+    },
+
+    // :: NodeSpec The text node.
+    text: {
+        group: "inline"
+    },
+
+    dashComment: {
+        attrs: {
+            docid: { default: "" },
+        },
+        inline: true,
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }, "←"];
+        },
+    },
+
+    summary: {
+        inline: true,
+        attrs: {
+            visibility: { default: false },
+            text: { default: undefined },
+            textslice: { default: undefined },
+        },
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }];
+        },
+    },
+
+    // :: NodeSpec An inline image (``) node. Supports `src`,
+    // `alt`, and `href` attributes. The latter two default to the empty
+    // string.
+    image: {
+        inline: true,
+        attrs: {
+            src: {},
+            agnostic: { default: null },
+            width: { default: 100 },
+            alt: { default: null },
+            title: { default: null },
+            float: { default: "left" },
+            location: { default: "onRight" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "img[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        // TODO if we don't define toDom, dragging the image crashes. Why?
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashDoc: {
+        inline: true,
+        attrs: {
+            width: { default: 200 },
+            height: { default: 100 },
+            title: { default: null },
+            float: { default: "right" },
+            location: { default: "onRight" },
+            hidden: { default: false },
+            fieldKey: { default: "" },
+            docid: { default: "" },
+            alias: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashField: {
+        inline: true,
+        attrs: {
+            fieldKey: { default: "" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    video: {
+        inline: true,
+        attrs: {
+            src: {},
+            width: { default: "100px" },
+            alt: { default: null },
+            title: { default: null }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "video[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["video", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    // :: NodeSpec A hard line break, represented in the DOM as `
`. + hard_break: { + inline: true, + group: "inline", + selectable: false, + parseDOM: [{ tag: "br" }], + toDOM() { return brDOM; } + }, + + ordered_list: { + ...orderedList, + content: 'list_item+', + group: 'block', + attrs: { + bulletStyle: { default: 0 }, + mapStyle: { default: "decimal" }, + setFontSize: { default: undefined }, + setFontFamily: { default: "inherit" }, + setFontColor: { default: "inherit" }, + inheritedFontSize: { default: undefined }, + visibility: { default: true }, + indent: { default: undefined } + }, + toDOM(node: Node) { + if (node.attrs.mapStyle === "bullet") return ['ul', 0]; + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; + const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; + const ffam = node.attrs.setFontFamily; + const color = node.attrs.setFontColor; + return node.attrs.visibility ? + ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] : + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; + } + }, + + bullet_list: { + ...bulletList, + content: 'list_item+', + group: 'block', + // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], + toDOM(node: Node) { + return ['ul', 0]; + } + }, + + list_item: { + attrs: { + bulletStyle: { default: 0 }, + mapStyle: { default: "decimal" }, + visibility: { default: true } + }, + ...listItem, + content: 'paragraph block*', + toDOM(node: any) { + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; + return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; + //return ["li", { class: `${map}` }, 0]; + } + }, +}; \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js new file mode 100644 index 000000000..269423482 --- /dev/null +++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js @@ -0,0 +1,139 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var prosemirrorInputRules = require('prosemirror-inputrules'); +var prosemirrorTransform = require('prosemirror-transform'); +var prosemirrorModel = require('prosemirror-model'); + +exports.liftListItem = liftListItem; +exports.sinkListItem = sinkListItem; +exports.wrappingInputRule = wrappingInputRule; +// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool +// Create a command to lift the list item around the selection up into +// a wrapping list. +function liftListItem(itemType) { + return function (tx, dispatch) { + var ref = tx.selection; + var $from = ref.$from; + var $to = ref.$to; + var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); + if (!range) { return false } + if (!dispatch) { return true } + if ($from.node(range.depth - 1).type == itemType) // Inside a parent list + { return liftToOuterList(tx, dispatch, itemType, range) } + else // Outer list node + { return liftOutOfList(tx, dispatch, range) } + } +} + +function liftToOuterList(tr, dispatch, itemType, range) { + var end = range.end, endOfList = range.$to.end(range.depth); + if (end < endOfList) { + // There are siblings after the lifted items, which must become + // children of the last item + tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList, + new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true)); + range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth); + } + dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView()); + return true +} + +function liftOutOfList(tr, dispatch, range) { + var list = range.parent; + // Merge the list items into a single big item + for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) { + pos -= list.child(i).nodeSize; + tr.delete(pos - 1, pos + 1); + } + var $start = tr.doc.resolve(range.start), item = $start.nodeAfter; + var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount; + var parent = $start.node(-1), indexBefore = $start.index(-1); + if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, + item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false } + var start = $start.pos, end = start + item.nodeSize; + // Strip off the surrounding list. At the sides where we're not at + // the end of the list, the existing list is closed. At sides where + // this is the end, it is overwritten to its end. + tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, + new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))) + .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))), + atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1)); + dispatch(tr.scrollIntoView()); + return true +} + +// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool +// Create a command to sink the list item around the selection down +// into an inner list. +function sinkListItem(itemType) { + return function (state, dispatch) { + var ref = state.selection; + var $from = ref.$from; + var $to = ref.$to; + var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); + if (!range) { return false } + var startIndex = range.startIndex; + if (startIndex == 0) { return false } + var parent = range.parent, nodeBefore = parent.child(startIndex - 1); + if (nodeBefore.type != itemType) { return false; } + + if (dispatch) { + var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type; + var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null); + let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))), + nestedBefore ? 3 : 1, 0); + var before = range.start, after = range.end; + dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, + before, after, slice, 1, true)) + .scrollIntoView()); + } + return true + } +} + +function findWrappingOutside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var around = parent.contentMatchAt(startIndex).findWrapping(type); + if (!around) { return null } + var outer = around.length ? around[0] : type; + return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null +} + +function findWrappingInside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var inner = parent.child(startIndex); + var inside = type.contentMatch.findWrapping(inner.type); + if (!inside) { return null } + var lastType = inside.length ? inside[inside.length - 1] : type; + var innerMatch = lastType.contentMatch; + for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); } + if (!innerMatch || !innerMatch.validEnd) { return null } + return inside +} +function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) { + if (innerRange === void 0) innerRange = range; + let withAttrs = (type) => ({ type: type, attrs: null }); + var around = findWrappingOutside(range, nodeType); + var inner = around && findWrappingInside(innerRange, nodeType); + if (!inner) { return null } + return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs)) +} +function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) { + return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) { + var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; + var tr = state.tr.delete(start, end); + var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs); + if (!wrapping) { return null } + tr.wrap(range, wrapping); + var before = tr.doc.resolve(start - 1).nodeBefore; + if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) && + (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } + return tr + }) +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts new file mode 100644 index 000000000..83561073c --- /dev/null +++ b/src/client/views/nodes/formattedText/schema_rts.ts @@ -0,0 +1,26 @@ +import { Schema, Slice } from "prosemirror-model"; + +import { nodes } from "./nodes_rts"; +import { marks } from "./marks_rts"; + + +// :: Schema +// This schema rougly corresponds to the document schema used by +// [CommonMark](http://commonmark.org/), minus the list elements, +// which are defined in the [`prosemirror-schema-list`](#schema-list) +// module. +// +// To reuse elements from this schema, extend or read from its +// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). + +export const schema = new Schema({ nodes, marks }); + +const fromJson = schema.nodeFromJSON; + +schema.nodeFromJSON = (json: any) => { + const node = fromJson(json); + if (json.type === schema.nodes.summary.name) { + node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); + } + return node; +}; \ No newline at end of file diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 73ebbb303..69a80e1b4 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -7,7 +7,7 @@ import { observer } from 'mobx-react'; import { DocServer } from '../client/DocServer'; import { Docs } from '../client/documents/Documents'; import { DocumentManager } from '../client/util/DocumentManager'; -import RichTextMenu from '../client/util/RichTextMenu'; +import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu'; import { Scripting } from '../client/util/Scripting'; import { Transform } from '../client/util/Transform'; import { CollectionView } from '../client/views/collections/CollectionView'; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index c9960e783..c475d0d73 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -4,11 +4,11 @@ import { Fragment, Mark, Node } from "prosemirror-model"; import { sinkListItem } from "prosemirror-schema-list"; import { Utils } from "../Utils"; import { Docs } from "../client/documents/Documents"; -import { schema } from "../client/util/schema_rts"; +import { schema } from "../client/views/nodes/formattedText/schema_rts"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; import { DocServer } from "../client/DocServer"; import { Networking } from "../client/Network"; -import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox"; import { Doc, Opt } from "./Doc"; import { Id } from "./FieldSymbols"; import { RichTextField } from "./RichTextField"; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 1d41c3570..08dc21460 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -17,7 +17,7 @@ import { CollectionViewType } from "../../../client/views/collections/Collection import { makeTemplate } from "../../../client/util/DropConverter"; import { RichTextField } from "../../../new_fields/RichTextField"; import { PrefetchProxy } from "../../../new_fields/Proxy"; -import { FormattedTextBox } from "../../../client/views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox"; import { MainView } from "../../../client/views/MainView"; import { DocumentType } from "../../../client/documents/DocumentTypes"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -- cgit v1.2.3-70-g09d2 From 55d1ea38c1355ef97efedd6c1fbdbc29d7b5d821 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 28 Apr 2020 18:06:07 -0700 Subject: snap lines hidden --- src/client/views/MainView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1f88410b8..12f0adf70 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -584,12 +584,13 @@ export class MainView extends React.Component { + {/* TO VIEW SNAP LINES
- {this._hLines?.map(l => )} - {this._vLines?.map(l => )} + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} -
+
*/}
); } } -- cgit v1.2.3-70-g09d2 From 37b5878ac3a8f0bd5168431b71e58eee27a3ec99 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 29 Apr 2020 22:27:59 -0400 Subject: fixed problems with snapping so that it snaps on finishDrag. cleaned up code a bit. --- src/client/util/DragManager.ts | 81 ++++++++++++---------- src/client/views/MainView.tsx | 14 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 60 +++++----------- 3 files changed, 68 insertions(+), 87 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index a905dff0a..36c26fe2c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -75,8 +75,8 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; - export let horizSnapLines: number[]; - export let vertSnapLines: number[]; + export let horizSnapLines: number[] = []; + export let vertSnapLines: number[] = []; export function Root() { const root = document.getElementById("root"); @@ -296,7 +296,6 @@ export namespace DragManager { StartDrag([ele], {}, downX, downY); } - @action export function SetSnapLines(horizLines: number[], vertLines: number[]) { horizSnapLines = horizLines; vertSnapLines = vertLines; @@ -304,6 +303,36 @@ export namespace DragManager { MainView.Instance._vLines = vertLines; } + function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { + let thisX = e.pageX; + let thisY = e.pageY; + const currLeft = e.pageX - xFromLeft; + const currTop = e.pageY - yFromTop; + const currRight = e.pageX + xFromRight; + const currBottom = e.pageY + yFromBottom; + const closestLeft = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev); + const closestTop = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev); + const closestRight = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev); + const closestBottom = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev); + const distFromClosestLeft = Math.abs(e.pageX - xFromLeft - closestLeft); + const distFromClosestTop = Math.abs(e.pageY - yFromTop - closestTop); + const distFromClosestRight = Math.abs(e.pageX + xFromRight - closestRight); + const distFromClosestBottom = Math.abs(e.pageY + yFromBottom - closestBottom); + if (distFromClosestLeft < 10 && distFromClosestLeft < distFromClosestRight) { + thisX = closestLeft + xFromLeft; + } + else if (distFromClosestRight < 10) { + thisX = closestRight - xFromRight; + } + if (distFromClosestTop < 10 && distFromClosestTop < distFromClosestRight) { + thisY = closestTop + yFromTop; + } + else if (distFromClosestBottom < 10) { + thisY = closestBottom - yFromBottom; + } + return { thisX, thisY }; + } + export let docsBeingDragged: Doc[] = []; function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { eles = eles.filter(e => e); if (!dragDiv) { @@ -318,7 +347,7 @@ export namespace DragManager { const xs: number[] = []; const ys: number[] = []; - const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : []; + docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : []; const elesCont = { left: Number.MAX_SAFE_INTEGER, top: Number.MAX_SAFE_INTEGER, @@ -354,7 +383,7 @@ export namespace DragManager { dragElement.style.width = `${rect.width / scaleX}px`; dragElement.style.height = `${rect.height / scaleY}px`; - if (docs.length) { + if (docsBeingDragged.length) { const pdfBox = dragElement.getElementsByTagName("canvas"); const pdfBoxSrc = ele.getElementsByTagName("canvas"); Array.from(pdfBox).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); @@ -408,32 +437,8 @@ export namespace DragManager { button: 0 }, dragData.droppedDocuments); } - let thisX = e.pageX; - let thisY = e.pageY; - const currLeft = e.pageX - xFromLeft; - const currTop = e.pageY - yFromTop; - const currRight = e.pageX + xFromRight; - const currBottom = e.pageY + yFromBottom; - const closestLeft = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev); - const closestTop = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev); - const closestRight = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev); - const closestBottom = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev); - const distFromClosestLeft = Math.abs(e.pageX - xFromLeft - closestLeft); - const distFromClosestTop = Math.abs(e.pageY - yFromTop - closestTop); - const distFromClosestRight = Math.abs(e.pageX + xFromRight - closestRight); - const distFromClosestBottom = Math.abs(e.pageY + yFromBottom - closestBottom); - if (distFromClosestLeft < 10 && distFromClosestLeft < distFromClosestRight) { - thisX = closestLeft + xFromLeft; - } - else if (distFromClosestRight < 10) { - thisX = closestRight - xFromRight; - } - if (distFromClosestTop < 10 && distFromClosestTop < distFromClosestRight) { - thisY = closestTop + yFromTop; - } - else if (distFromClosestBottom < 10) { - thisY = closestBottom - yFromBottom; - } + + const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom); alias = "move"; const moveX = thisX - lastX; @@ -462,7 +467,7 @@ export namespace DragManager { }; const upHandler = (e: PointerEvent) => { hideDragShowOriginalElements(); - dispatchDrag(eles, e, dragData, options, finishDrag); + dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag); SelectionManager.SetIsDragging(false); endDrag(); options?.dragComplete?.(new DragCompleteEvent(false, dragData)); @@ -471,7 +476,8 @@ export namespace DragManager { document.addEventListener("pointerup", upHandler); } - function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) { + function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, + xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) { const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => { const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow }; dragEle.style.width = "0"; @@ -485,14 +491,15 @@ export namespace DragManager { r.ele.style.height = r.h; r.ele.style.overflow = r.o; }); + const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom); if (target) { const complete = new DragCompleteEvent(false, dragData); target.dispatchEvent( new CustomEvent("dashPreDrop", { bubbles: true, detail: { - x: e.x, - y: e.y, + x: thisX, + y: thisY, complete: complete, shiftKey: e.shiftKey, altKey: e.altKey, @@ -506,8 +513,8 @@ export namespace DragManager { new CustomEvent("dashOnDrop", { bubbles: true, detail: { - x: e.x, - y: e.y, + x: thisX, + y: thisY, complete: complete, shiftKey: e.shiftKey, altKey: e.altKey, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0102d1327..62b2d1d18 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -588,13 +588,13 @@ export class MainView extends React.Component { - {/* TO VIEW SNAP LINES -
- - {this._hLines?.map(l => )} - {this._vLines?.map(l => )} - -
*/} + {// TO VIEW SNAP LINES + /*
+ + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} + +
*/}
); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d291cad21..0c9403429 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1129,7 +1129,7 @@ export class CollectionFreeFormView extends CollectionSubView !doc.isBackground && doc.z === undefined).map(doc => { - const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { + const docDims = (doc: Doc, layoutDoc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) }); + const compareDoc = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { + if (this.intersectRect(docDims(doc, Doc.Layout(doc)), rect)) { selection.push(doc); } - }); - if (!selection.length) { - this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => { - const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { - selection.push(doc); - } - }); - } - if (!selection.length) { - const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => { - const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { - selection.push(doc); - } - }); } + const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; + this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => compareDoc(doc, selRect)); // first try foreground docs + !selection.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => compareDoc(doc, selRect)); // then background docs + !selection.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => compareDoc(doc, otherBounds)); // then floating docs + const horizLines: number[] = []; const vertLines: number[] = []; - selection.forEach(doc => { - const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - const topLeftInScreen = this.getTransform().inverse().transformPoint(x, y); - const docSize = this.getTransform().inverse().transformDirection(w, h); + selection.filter(doc => !DragManager.docsBeingDragged.includes(doc)).forEach(doc => { + const { left, top, width, height } = docDims(doc, Doc.Layout(doc)); + const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); + const docSize = this.getTransform().inverse().transformDirection(width, height); + horizLines.push(topLeftInScreen[1]); // top line horizLines.push(topLeftInScreen[1] + docSize[1]); // bottom line horizLines.push(topLeftInScreen[1] + docSize[1] / 2); // horiz center line @@ -1292,12 +1265,13 @@ export class CollectionFreeFormView extends CollectionSubView
- {/*
+ {// uncomment to show snap lines + /*
{this._hLines?.map(l => )} {this._vLines?.map(l => )} -
*/} +
*/}
; } } -- cgit v1.2.3-70-g09d2 From 90c45914694a971c1b3cb356921c04f337625db5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 00:06:08 -0400 Subject: fixes for snapping & timeline. changed looi of document decorations --- src/client/util/DragManager.ts | 8 ++--- src/client/views/DocumentDecorations.scss | 34 +++++++++++++++++++++- src/client/views/DocumentDecorations.tsx | 9 +++--- src/client/views/MainView.tsx | 1 - src/client/views/MetadataEntryMenu.scss | 9 +++--- src/client/views/animationtimeline/Timeline.tsx | 33 ++++++++------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 14 ++++++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 +++-- 8 files changed, 74 insertions(+), 42 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 36c26fe2c..bccdf38ce 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -310,10 +310,10 @@ export namespace DragManager { const currTop = e.pageY - yFromTop; const currRight = e.pageX + xFromRight; const currBottom = e.pageY + yFromBottom; - const closestLeft = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev); - const closestTop = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev); - const closestRight = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev); - const closestBottom = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev); + const closestLeft = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev, currLeft); + const closestTop = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev, currTop); + const closestRight = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev, currRight); + const closestBottom = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev, currBottom); const distFromClosestLeft = Math.abs(e.pageX - xFromLeft - closestLeft); const distFromClosestTop = Math.abs(e.pageY - yFromTop - closestTop); const distFromClosestRight = Math.abs(e.pageX + xFromRight - closestRight); diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 28cf9fd47..61d517d43 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -21,9 +21,13 @@ $linkGap : 3px; background: none; } + .documentDecorations-resizer { pointer-events: auto; background: $alt-accent; + opacity: 0.1; + } + .documentDecorations-resizer:hover { opacity: 1; } @@ -80,7 +84,20 @@ $linkGap : 3px; #documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer { cursor: nwse-resize; - background: dimGray; + background: unset; + opacity: 1; + } + #documentDecorations-topLeftResizer { + border-left: black 2px solid; + border-top: black solid 2px; + } + #documentDecorations-bottomRightResizer { + border-right: black 2px solid; + border-bottom: black solid 2px; + } + #documentDecorations-topLeftResizer:hover, + #documentDecorations-bottomRightResizer:hover { + opacity: 1; } #documentDecorations-bottomRightResizer { @@ -89,8 +106,23 @@ $linkGap : 3px; #documentDecorations-topRightResizer, #documentDecorations-bottomLeftResizer { + cursor: nesw-resize; + background: unset; + opacity: 1; + } + #documentDecorations-topRightResizer { + border-right: black 2px solid; + border-top: black 2px solid; + } + #documentDecorations-bottomLeftResizer { + border-left: black 2px solid; + border-bottom: black 2px solid; + } + #documentDecorations-topRightResizer:hover, + #documentDecorations-bottomLeftResizer:hover { cursor: nesw-resize; background: dimGray; + opacity: 1; } #documentDecorations-topResizer, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 312acd5b2..973ec2e89 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -473,10 +473,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}>
e.preventDefault()}>
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
e.preventDefault()}> - -
} + {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : +
e.preventDefault()}> + +
}
e.preventDefault()}>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 62b2d1d18..e5a8ebcb5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -42,7 +42,6 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { ScriptField } from '../../new_fields/ScriptField'; -import { DragManager } from '../util/DragManager'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; @observer diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss index 5776cf070..28de0b7a5 100644 --- a/src/client/views/MetadataEntryMenu.scss +++ b/src/client/views/MetadataEntryMenu.scss @@ -9,9 +9,10 @@ } .metadataEntry-autoSuggester { - width: 100%; + width: 80%; height: 100%; - padding-right: 10px; + margin: 0; + display: inline-block; } #metadataEntry-outer { @@ -25,7 +26,7 @@ flex-direction: column; } .metadataEntry-inputArea { - display:flex; + display:inline-block; flex-direction: row; } @@ -44,7 +45,7 @@ .react-autosuggest__input { border: 1px solid #aaa; border-radius: 4px; - width: 100%; + width: 75%; } .react-autosuggest__input--focused { diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index fe1e40778..77656b85f 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -71,7 +71,6 @@ export class Timeline extends React.Component { @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT; @observable private _time = 100000; //DEFAULT @observable private _playButton = faPlayCircle; - @observable private _timelineVisible = false; @observable private _mouseToggled = false; @observable private _doubleClickEnabled = false; @observable private _titleHeight = 0; @@ -336,20 +335,6 @@ export class Timeline extends React.Component { } - /** - * context menu function. - * opens the timeline or closes the timeline. - * Used in: Freeform - */ - timelineContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ - description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => { - this._timelineVisible = !this._timelineVisible; - }), icon: this._timelineVisible ? faEyeSlash : faEye - }); - } - - /** * timeline zoom function * use mouse middle button to zoom in/out the timeline @@ -463,7 +448,7 @@ export class Timeline extends React.Component {
-
+
{this.timeIndicator(lengthString, totalTime)}
this.resetView(this.props.Document)}>
this.setView(this.props.Document)}>
@@ -481,10 +466,16 @@ export class Timeline extends React.Component { ); } else { + const ctime = `Current: ${this.getCurrentTime()}`; + const ttime = `Total: ${this.toReadTime(this._time)}`; return (
-
{`Current: ${this.getCurrentTime()}`}
-
{`Total: ${this.toReadTime(this._time)}`}
+
+ {ctime} +
+
+ {ttime} +
); } @@ -601,8 +592,8 @@ export class Timeline extends React.Component { trace(); // change visible and total width return ( -
-
+
+
{this.drawTicks()} @@ -611,7 +602,7 @@ export class Timeline extends React.Component {
{DocListCast(this.children).map(doc => - this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={this._timelineVisible} /> + this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} /> )}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0c9403429..77de486d9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,5 +1,5 @@ import { library } from "@fortawesome/fontawesome-svg-core"; -import { faEye } from "@fortawesome/free-regular-svg-icons"; +import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; @@ -1093,7 +1093,6 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); @@ -1130,8 +1129,15 @@ export class CollectionFreeFormView extends CollectionSubView { + this._timelineVisible = !this._timelineVisible; + }), icon: this._timelineVisible ? faEyeSlash : faEye + }); } + @observable _timelineVisible = false; intersectRect(r1: { left: number, top: number, width: number, height: number }, r2: { left: number, top: number, width: number, height: number }) { @@ -1215,7 +1221,7 @@ export class CollectionFreeFormView extends CollectionSubView {this.children} - + {this._timelineVisible ? : (null)} ; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2038efbc6..4df693c9a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -435,9 +435,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.rootDoc.isTemplateForField = ""; this.rootDoc.layoutKey = "layout"; this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); - this.rootDoc._width = this.layoutDoc._width || 300; // the width and height are stored on the template, since we're getting rid of the old template - this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields - this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); + setTimeout(() => { + this.rootDoc._width = this.layoutDoc._width || 300; // the width and height are stored on the template, since we're getting rid of the old template + this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields + this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); + }, 10); Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); -- cgit v1.2.3-70-g09d2 From 22748f8d35235941fc6622b19a2d4d3f809ccee7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 17:16:14 -0400 Subject: working version of snapping with resize / templates / centers --- .VSCodeCounter/details.md | 661 +++++++++++++++++ .VSCodeCounter/results.csv | 648 ++++++++++++++++ .VSCodeCounter/results.md | 164 +++++ .VSCodeCounter/results.txt | 813 +++++++++++++++++++++ package-lock.json | 81 +- src/Utils.ts | 4 +- src/client/util/DragManager.ts | 41 +- src/client/views/MainView.tsx | 8 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 70 +- .../views/nodes/formattedText/DashFieldView.tsx | 4 - .../authentication/models/current_user_utils.ts | 6 +- 11 files changed, 2395 insertions(+), 105 deletions(-) create mode 100644 .VSCodeCounter/details.md create mode 100644 .VSCodeCounter/results.csv create mode 100644 .VSCodeCounter/results.md create mode 100644 .VSCodeCounter/results.txt (limited to 'src/client/views/MainView.tsx') diff --git a/.VSCodeCounter/details.md b/.VSCodeCounter/details.md new file mode 100644 index 000000000..2f988953b --- /dev/null +++ b/.VSCodeCounter/details.md @@ -0,0 +1,661 @@ +# Details + +Date : 2020-04-30 14:40:16 + +Directory /Users/bcz/Documents/GitHub/Dash-Web + +Total : 646 files, 224911 codes, 32987 comments, 15880 blanks, all 273778 lines + +[summary](results.md) + +## Files +| filename | language | code | comment | blank | total | +| :--- | :--- | ---: | ---: | ---: | ---: | +| [README.md](file:///Users/bcz/Documents/GitHub/Dash-Web/README.md) | Markdown | 6 | 0 | 3 | 9 | +| [build/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/build/index.html) | HTML | 9 | 0 | 3 | 12 | +| [dash.bat](file:///Users/bcz/Documents/GitHub/Dash-Web/dash.bat) | Batch | 2 | 0 | 1 | 3 | +| [deploy/assets/env.json](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/assets/env.json) | JSON | 15 | 0 | 0 | 15 | +| [deploy/assets/pdf.worker.js](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/assets/pdf.worker.js) | JavaScript | 55,662 | 174 | 686 | 56,522 | +| [deploy/debug/repl.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/debug/repl.html) | HTML | 11 | 0 | 3 | 14 | +| [deploy/debug/test.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/debug/test.html) | HTML | 10 | 0 | 3 | 13 | +| [deploy/debug/viewer.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/debug/viewer.html) | HTML | 11 | 0 | 3 | 14 | +| [deploy/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/index.html) | HTML | 13 | 0 | 3 | 16 | +| [deploy/mobile/image.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/mobile/image.html) | HTML | 12 | 0 | 3 | 15 | +| [deploy/mobile/ink.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/mobile/ink.html) | HTML | 10 | 0 | 3 | 13 | +| [package-lock.json](file:///Users/bcz/Documents/GitHub/Dash-Web/package-lock.json) | JSON | 18,689 | 0 | 1 | 18,690 | +| [package.json](file:///Users/bcz/Documents/GitHub/Dash-Web/package.json) | JSON | 266 | 0 | 1 | 267 | +| [sentence_parser.py](file:///Users/bcz/Documents/GitHub/Dash-Web/sentence_parser.py) | Python | 6 | 0 | 1 | 7 | +| [session.config.json](file:///Users/bcz/Documents/GitHub/Dash-Web/session.config.json) | JSON | 12 | 0 | 1 | 13 | +| [solr-8.3.1/bin/install_solr_service.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/install_solr_service.sh) | Shell Script | 307 | 29 | 35 | 371 | +| [solr-8.3.1/bin/oom_solr.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/oom_solr.sh) | Shell Script | 13 | 15 | 3 | 31 | +| [solr-8.3.1/bin/solr.cmd](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/solr.cmd) | Batch | 1,782 | 43 | 210 | 2,035 | +| [solr-8.3.1/bin/solr.in.cmd](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/solr.in.cmd) | Batch | 16 | 133 | 29 | 178 | +| [solr-8.3.1/bin/solr.in.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/solr.in.sh) | Shell Script | 0 | 172 | 34 | 206 | +| [solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd) | Batch | 82 | 0 | 26 | 108 | +| [solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json) | JSON | 4,465 | 0 | 1 | 4,466 | +| [solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml) | XML | 1,734 | 63 | 10 | 1,807 | +| [solr-8.3.1/docs/images/solr.svg](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/docs/images/solr.svg) | XML | 39 | 0 | 1 | 40 | +| [solr-8.3.1/docs/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/docs/index.html) | HTML | 20 | 0 | 1 | 21 | +| [solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml) | XML | 21 | 6 | 9 | 36 | +| [solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml) | XML | 20 | 37 | 8 | 65 | +| [solr-8.3.1/example/example-DIH/solr/atom/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/atom/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml) | XML | 26 | 0 | 4 | 30 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml) | XML | 292 | 958 | 104 | 1,354 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/example/example-DIH/solr/db/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml) | XML | 8 | 4 | 1 | 13 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml) | XML | 294 | 958 | 105 | 1,357 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/example/example-DIH/solr/mail/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/solr.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr.xml) | XML | 2 | 0 | 1 | 3 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml) | XML | 8 | 16 | 2 | 26 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml) | XML | 292 | 958 | 102 | 1,352 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/example/example-DIH/solr/solr/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml) | XML | 17 | 38 | 7 | 62 | +| [solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml) | XML | 17 | 3 | 7 | 27 | +| [solr-8.3.1/example/example-DIH/solr/tika/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/tika/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/exampledocs/books.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/books.json) | JSON | 51 | 0 | 1 | 52 | +| [solr-8.3.1/example/exampledocs/gb18030-example.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/gb18030-example.xml) | XML | 14 | 16 | 3 | 33 | +| [solr-8.3.1/example/exampledocs/hd.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/hd.xml) | XML | 33 | 20 | 4 | 57 | +| [solr-8.3.1/example/exampledocs/ipod_other.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/ipod_other.xml) | XML | 32 | 20 | 9 | 61 | +| [solr-8.3.1/example/exampledocs/ipod_video.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/ipod_video.xml) | XML | 21 | 18 | 2 | 41 | +| [solr-8.3.1/example/exampledocs/manufacturers.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/manufacturers.xml) | XML | 57 | 16 | 3 | 76 | +| [solr-8.3.1/example/exampledocs/mem.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/mem.xml) | XML | 45 | 24 | 9 | 78 | +| [solr-8.3.1/example/exampledocs/money.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/money.xml) | XML | 42 | 17 | 7 | 66 | +| [solr-8.3.1/example/exampledocs/monitor.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/monitor.xml) | XML | 14 | 18 | 3 | 35 | +| [solr-8.3.1/example/exampledocs/monitor2.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/monitor2.xml) | XML | 13 | 18 | 3 | 34 | +| [solr-8.3.1/example/exampledocs/mp500.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/mp500.xml) | XML | 23 | 18 | 3 | 44 | +| [solr-8.3.1/example/exampledocs/sample.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/sample.html) | HTML | 13 | 0 | 1 | 14 | +| [solr-8.3.1/example/exampledocs/sd500.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/sd500.xml) | XML | 19 | 18 | 2 | 39 | +| [solr-8.3.1/example/exampledocs/solr.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/solr.xml) | XML | 20 | 16 | 3 | 39 | +| [solr-8.3.1/example/exampledocs/test_utf8.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/test_utf8.sh) | Shell Script | 57 | 21 | 16 | 94 | +| [solr-8.3.1/example/exampledocs/utf8-example.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/utf8-example.xml) | XML | 19 | 20 | 4 | 43 | +| [solr-8.3.1/example/exampledocs/vidcard.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/vidcard.xml) | XML | 40 | 21 | 2 | 63 | +| [solr-8.3.1/example/files/browse-resources/velocity/resources.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/browse-resources/velocity/resources.properties) | Properties | 72 | 6 | 5 | 83 | +| [solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties) | Properties | 18 | 0 | 1 | 19 | +| [solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties) | Properties | 18 | 0 | 3 | 21 | +| [solr-8.3.1/example/files/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/files/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/files/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/params.json) | JSON | 34 | 0 | 1 | 35 | +| [solr-8.3.1/example/files/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/solrconfig.xml) | XML | 298 | 979 | 102 | 1,379 | +| [solr-8.3.1/example/files/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/update-script.js) | JavaScript | 80 | 13 | 23 | 116 | +| [solr-8.3.1/example/files/conf/velocity/dropit.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/dropit.js) | JavaScript | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js) | JavaScript | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/files/conf/velocity/js/dropit.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/js/dropit.js) | JavaScript | 64 | 15 | 19 | 98 | +| [solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js) | JavaScript | 620 | 68 | 76 | 764 | +| [solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js) | JavaScript | 46 | 16 | 9 | 71 | +| [solr-8.3.1/example/films/film_data_generator.py](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/films/film_data_generator.py) | Python | 82 | 24 | 12 | 118 | +| [solr-8.3.1/example/films/films.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/films/films.json) | JSON | 15,830 | 0 | 1 | 15,831 | +| [solr-8.3.1/example/films/films.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/films/films.xml) | XML | 11,438 | 0 | 1 | 11,439 | +| [solr-8.3.1/server/contexts/solr-jetty-context.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/contexts/solr-jetty-context.xml) | XML | 8 | 0 | 1 | 9 | +| [solr-8.3.1/server/etc/jetty-http.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-http.xml) | XML | 33 | 15 | 4 | 52 | +| [solr-8.3.1/server/etc/jetty-https.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-https.xml) | XML | 56 | 16 | 5 | 77 | +| [solr-8.3.1/server/etc/jetty-https8.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-https8.xml) | XML | 34 | 32 | 4 | 70 | +| [solr-8.3.1/server/etc/jetty-ssl.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-ssl.xml) | XML | 23 | 11 | 4 | 38 | +| [solr-8.3.1/server/etc/jetty.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty.xml) | XML | 110 | 94 | 18 | 222 | +| [solr-8.3.1/server/etc/webdefault.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/webdefault.xml) | XML | 272 | 232 | 24 | 528 | +| [solr-8.3.1/server/resources/jetty-logging.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/resources/jetty-logging.properties) | Properties | 1 | 0 | 1 | 2 | +| [solr-8.3.1/server/resources/log4j2-console.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/resources/log4j2-console.xml) | XML | 19 | 43 | 6 | 68 | +| [solr-8.3.1/server/resources/log4j2.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/resources/log4j2.xml) | XML | 54 | 80 | 9 | 143 | +| [solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh) | Shell Script | 152 | 2 | 23 | 177 | +| [solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat) | Batch | 11 | 8 | 7 | 26 | +| [solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh) | Shell Script | 9 | 9 | 9 | 27 | +| [solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml) | XML | 62 | 42 | 11 | 115 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css) | CSS | 237 | 19 | 48 | 304 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css) | CSS | 402 | 55 | 9 | 466 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css) | CSS | 594 | 23 | 106 | 723 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css) | CSS | 296 | 18 | 65 | 379 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css) | CSS | 647 | 19 | 106 | 772 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css) | CSS | 171 | 18 | 37 | 226 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css) | CSS | 134 | 18 | 28 | 180 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css) | CSS | 292 | 18 | 61 | 371 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css) | CSS | 131 | 23 | 26 | 180 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css) | CSS | 29 | 18 | 7 | 54 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css) | CSS | 164 | 18 | 35 | 217 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css) | CSS | 24 | 18 | 6 | 48 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css) | CSS | 1 | 26 | 2 | 29 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css) | CSS | 1 | 22 | 2 | 25 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css) | CSS | 303 | 19 | 63 | 385 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css) | CSS | 80 | 18 | 12 | 110 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css) | CSS | 257 | 18 | 56 | 331 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css) | CSS | 20 | 18 | 5 | 43 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css) | CSS | 172 | 18 | 31 | 221 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css) | CSS | 120 | 18 | 25 | 163 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css) | CSS | 404 | 18 | 79 | 501 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css) | CSS | 596 | 20 | 112 | 728 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css) | CSS | 133 | 18 | 22 | 173 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css) | CSS | 178 | 22 | 34 | 234 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css) | CSS | 43 | 18 | 4 | 65 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css) | CSS | 119 | 18 | 24 | 161 | +| [solr-8.3.1/server/solr-webapp/webapp/img/solr.svg](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/img/solr.svg) | XML | 39 | 0 | 1 | 40 | +| [solr-8.3.1/server/solr-webapp/webapp/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/index.html) | HTML | 203 | 16 | 38 | 257 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js) | JavaScript | 512 | 19 | 31 | 562 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js) | JavaScript | 8 | 16 | 4 | 28 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js) | JavaScript | 161 | 18 | 23 | 202 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js) | JavaScript | 847 | 51 | 124 | 1,022 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js) | JavaScript | 43 | 18 | 2 | 63 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js) | JavaScript | 18 | 16 | 6 | 40 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js) | JavaScript | 244 | 19 | 27 | 290 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js) | JavaScript | 69 | 16 | 9 | 94 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js) | JavaScript | 151 | 16 | 14 | 181 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js) | JavaScript | 234 | 25 | 44 | 303 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js) | JavaScript | 107 | 18 | 13 | 138 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js) | JavaScript | 72 | 16 | 13 | 101 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js) | JavaScript | 61 | 23 | 14 | 98 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js) | JavaScript | 27 | 16 | 3 | 46 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js) | JavaScript | 112 | 35 | 12 | 159 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js) | JavaScript | 269 | 30 | 19 | 318 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js) | JavaScript | 130 | 19 | 19 | 168 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js) | JavaScript | 88 | 19 | 14 | 121 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js) | JavaScript | 178 | 18 | 40 | 236 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js) | JavaScript | 524 | 19 | 69 | 612 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js) | JavaScript | 64 | 16 | 20 | 100 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js) | JavaScript | 173 | 16 | 51 | 240 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js) | JavaScript | 33 | 16 | 2 | 51 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js) | JavaScript | 14 | 22 | 2 | 38 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js) | JavaScript | 311 | 18 | 11 | 340 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js) | JavaScript | 112 | 24 | 4 | 140 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js) | JavaScript | 73 | 135 | 22 | 230 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js) | JavaScript | 2 | 29 | 1 | 32 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js) | JavaScript | 7 | 29 | 1 | 37 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js) | JavaScript | 316 | 636 | 67 | 1,019 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js) | JavaScript | 9 | 29 | 1 | 39 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js) | JavaScript | 311 | 328 | 65 | 704 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js) | JavaScript | 10 | 29 | 1 | 40 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js) | JavaScript | 157 | 48 | 13 | 218 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js) | JavaScript | 1 | 42 | 3 | 46 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular.js) | JavaScript | 10,845 | 13,211 | 2,038 | 26,094 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js) | JavaScript | 73 | 201 | 0 | 274 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js) | JavaScript | 1,151 | 36 | 8 | 1,195 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js) | JavaScript | 2 | 29 | 0 | 31 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/d3.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/d3.js) | JavaScript | 7,720 | 519 | 1,135 | 9,374 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js) | JavaScript | 2 | 29 | 1 | 32 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js) | JavaScript | 3 | 26 | 2 | 31 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js) | JavaScript | 3 | 26 | 1 | 30 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js) | JavaScript | 2 | 26 | 3 | 31 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js) | JavaScript | 3,222 | 228 | 85 | 3,535 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js) | JavaScript | 66 | 23 | 13 | 102 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html) | HTML | 21 | 16 | 10 | 47 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html) | HTML | 87 | 16 | 26 | 129 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html) | HTML | 263 | 16 | 24 | 303 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html) | HTML | 30 | 16 | 4 | 50 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html) | HTML | 48 | 16 | 22 | 86 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/collections.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/collections.html) | HTML | 301 | 16 | 79 | 396 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html) | HTML | 125 | 16 | 66 | 207 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/cores.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/cores.html) | HTML | 142 | 16 | 67 | 225 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html) | HTML | 142 | 16 | 52 | 210 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/documents.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/documents.html) | HTML | 83 | 20 | 9 | 112 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/files.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/files.html) | HTML | 17 | 16 | 15 | 48 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/index.html) | HTML | 135 | 42 | 85 | 262 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html) | HTML | 10 | 16 | 2 | 28 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html) | HTML | 35 | 16 | 6 | 57 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/logging.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/logging.html) | HTML | 40 | 16 | 2 | 58 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/login.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/login.html) | HTML | 134 | 16 | 11 | 161 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html) | HTML | 48 | 17 | 8 | 73 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/query.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/query.html) | HTML | 270 | 16 | 84 | 370 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/replication.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/replication.html) | HTML | 153 | 16 | 71 | 240 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/schema.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/schema.html) | HTML | 336 | 16 | 104 | 456 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/segments.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/segments.html) | HTML | 70 | 16 | 14 | 100 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/stream.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/stream.html) | HTML | 40 | 16 | 9 | 65 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/threads.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/threads.html) | HTML | 36 | 16 | 14 | 66 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html) | HTML | 5 | 16 | 3 | 24 | +| [solr-8.3.1/server/solr/configsets/_default/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/_default/conf/params.json) | JSON | 20 | 0 | 1 | 21 | +| [solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml) | XML | 296 | 976 | 98 | 1,370 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json) | JSON | 1 | 0 | 1 | 2 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json) | JSON | 38 | 0 | 1 | 39 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json) | JSON | 11 | 0 | 1 | 12 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json) | JSON | 11 | 0 | 1 | 12 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml) | XML | 410 | 1,097 | 124 | 1,631 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css) | CSS | 34 | 9 | 6 | 49 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js) | JavaScript | 620 | 68 | 76 | 764 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css) | CSS | 185 | 0 | 47 | 232 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/server/solr/dash/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/conf/params.json) | JSON | 20 | 0 | 0 | 20 | +| [solr-8.3.1/server/solr/dash/conf/schema.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/conf/schema.xml) | XML | 51 | 4 | 7 | 62 | +| [solr-8.3.1/server/solr/dash/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/conf/solrconfig.xml) | XML | 270 | 962 | 97 | 1,329 | +| [solr-8.3.1/server/solr/dash/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/core.properties) | Properties | 4 | 2 | 1 | 7 | +| [solr-8.3.1/server/solr/solr.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/solr.xml) | XML | 21 | 25 | 11 | 57 | +| [solr-8.3.1/server/solr/zoo.cfg](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/zoo.cfg) | Properties | 3 | 25 | 4 | 32 | +| [solr-8.3.1/server/tmp/start_3204295554151338130.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_3204295554151338130.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_5812170489311981381.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_5812170489311981381.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_6476327636763392575.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_6476327636763392575.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_7329004517204835686.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_7329004517204835686.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_9067375725008958788.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_9067375725008958788.properties) | Properties | 9 | 2 | 1 | 12 | +| [src/Utils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/Utils.ts) | TypeScript | 441 | 23 | 81 | 545 | +| [src/client/ClientRecommender.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/ClientRecommender.scss) | SCSS | 9 | 1 | 2 | 12 | +| [src/client/ClientRecommender.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/ClientRecommender.tsx) | TypeScript React | 320 | 61 | 44 | 425 | +| [src/client/DocServer.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/DocServer.ts) | TypeScript | 279 | 136 | 65 | 480 | +| [src/client/Network.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/Network.ts) | TypeScript | 34 | 0 | 5 | 39 | +| [src/client/apis/GoogleAuthenticationManager.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/GoogleAuthenticationManager.scss) | SCSS | 16 | 0 | 3 | 19 | +| [src/client/apis/GoogleAuthenticationManager.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/GoogleAuthenticationManager.tsx) | TypeScript React | 115 | 2 | 11 | 128 | +| [src/client/apis/IBM_Recommender.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/IBM_Recommender.ts) | TypeScript | 0 | 33 | 7 | 40 | +| [src/client/apis/google_docs/GoogleApiClientUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/google_docs/GoogleApiClientUtils.ts) | TypeScript | 225 | 9 | 27 | 261 | +| [src/client/apis/google_docs/GooglePhotosClientUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/google_docs/GooglePhotosClientUtils.ts) | TypeScript | 318 | 0 | 46 | 364 | +| [src/client/apis/youtube/YoutubeBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/youtube/YoutubeBox.scss) | SCSS | 105 | 5 | 16 | 126 | +| [src/client/apis/youtube/YoutubeBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/youtube/YoutubeBox.tsx) | TypeScript React | 274 | 48 | 40 | 362 | +| [src/client/cognitive_services/CognitiveServices.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/cognitive_services/CognitiveServices.ts) | TypeScript | 342 | 10 | 57 | 409 | +| [src/client/documents/DocumentTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/documents/DocumentTypes.ts) | TypeScript | 32 | 2 | 3 | 37 | +| [src/client/documents/Documents.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/documents/Documents.ts) | TypeScript | 807 | 141 | 87 | 1,035 | +| [src/client/goldenLayout.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/goldenLayout.d.ts) | TypeScript | 2 | 0 | 1 | 3 | +| [src/client/goldenLayout.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/goldenLayout.js) | JavaScript | 3,084 | 1,571 | 720 | 5,375 | +| [src/client/util/DictationManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DictationManager.ts) | TypeScript | 312 | 25 | 51 | 388 | +| [src/client/util/DocumentManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DocumentManager.ts) | TypeScript | 207 | 16 | 21 | 244 | +| [src/client/util/DragManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DragManager.ts) | TypeScript | 504 | 11 | 35 | 550 | +| [src/client/util/DropConverter.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DropConverter.ts) | TypeScript | 70 | 6 | 2 | 78 | +| [src/client/util/History.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/History.ts) | TypeScript | 166 | 13 | 27 | 206 | +| [src/client/util/Import & Export/DirectoryImportBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/DirectoryImportBox.scss) | SCSS | 6 | 0 | 0 | 6 | +| [src/client/util/Import & Export/DirectoryImportBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/DirectoryImportBox.tsx) | TypeScript React | 393 | 0 | 31 | 424 | +| [src/client/util/Import & Export/ImageUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/ImageUtils.ts) | TypeScript | 35 | 0 | 4 | 39 | +| [src/client/util/Import & Export/ImportMetadataEntry.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/ImportMetadataEntry.tsx) | TypeScript React | 132 | 0 | 17 | 149 | +| [src/client/util/InteractionUtils.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/InteractionUtils.tsx) | TypeScript React | 122 | 112 | 21 | 255 | +| [src/client/util/KeyCodes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/KeyCodes.ts) | TypeScript | 100 | 36 | 0 | 136 | +| [src/client/util/LinkManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/LinkManager.ts) | TypeScript | 161 | 30 | 23 | 214 | +| [src/client/util/ProsemirrorCopy/prompt.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/ProsemirrorCopy/prompt.js) | JavaScript | 128 | 30 | 22 | 180 | +| [src/client/util/Scripting.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Scripting.ts) | TypeScript | 247 | 15 | 30 | 292 | +| [src/client/util/ScrollBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/ScrollBox.tsx) | TypeScript React | 19 | 0 | 2 | 21 | +| [src/client/util/SearchUtil.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SearchUtil.ts) | TypeScript | 123 | 4 | 18 | 145 | +| [src/client/util/SelectionManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SelectionManager.ts) | TypeScript | 69 | 5 | 15 | 89 | +| [src/client/util/SerializationHelper.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SerializationHelper.ts) | TypeScript | 113 | 15 | 15 | 143 | +| [src/client/util/SettingsManager.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SettingsManager.scss) | SCSS | 111 | 0 | 25 | 136 | +| [src/client/util/SettingsManager.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SettingsManager.tsx) | TypeScript React | 114 | 0 | 17 | 131 | +| [src/client/util/SharingManager.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SharingManager.scss) | SCSS | 122 | 0 | 18 | 140 | +| [src/client/util/SharingManager.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SharingManager.tsx) | TypeScript React | 273 | 0 | 25 | 298 | +| [src/client/util/Transform.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Transform.ts) | TypeScript | 76 | 0 | 23 | 99 | +| [src/client/util/TypedEvent.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/TypedEvent.ts) | TypeScript | 29 | 3 | 8 | 40 | +| [src/client/util/UndoManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/UndoManager.ts) | TypeScript | 167 | 1 | 27 | 195 | +| [src/client/util/clamp.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/clamp.js) | JavaScript | 14 | 0 | 1 | 15 | +| [src/client/util/convertToCSSPTValue.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/convertToCSSPTValue.js) | JavaScript | 31 | 4 | 8 | 43 | +| [src/client/util/jsx-decl.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/jsx-decl.d.ts) | TypeScript | 1 | 0 | 1 | 2 | +| [src/client/util/request-image-size.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/request-image-size.js) | JavaScript | 51 | 10 | 14 | 75 | +| [src/client/util/toCSSLineSpacing.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/toCSSLineSpacing.js) | JavaScript | 35 | 15 | 14 | 64 | +| [src/client/views/AntimodeMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/AntimodeMenu.scss) | SCSS | 35 | 1 | 6 | 42 | +| [src/client/views/AntimodeMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/AntimodeMenu.tsx) | TypeScript React | 121 | 14 | 22 | 157 | +| [src/client/views/ContextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ContextMenu.scss) | SCSS | 130 | 17 | 14 | 161 | +| [src/client/views/ContextMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ContextMenu.tsx) | TypeScript React | 258 | 3 | 33 | 294 | +| [src/client/views/ContextMenuItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ContextMenuItem.tsx) | TypeScript React | 107 | 0 | 10 | 117 | +| [src/client/views/DictationOverlay.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DictationOverlay.tsx) | TypeScript React | 64 | 0 | 7 | 71 | +| [src/client/views/DocComponent.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocComponent.tsx) | TypeScript React | 87 | 20 | 15 | 122 | +| [src/client/views/DocumentButtonBar.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentButtonBar.scss) | SCSS | 89 | 0 | 16 | 105 | +| [src/client/views/DocumentButtonBar.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentButtonBar.tsx) | TypeScript React | 284 | 4 | 27 | 315 | +| [src/client/views/DocumentDecorations.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.scss) | SCSS | 319 | 0 | 46 | 365 | +| [src/client/views/DocumentDecorations.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx) | TypeScript React | 479 | 2 | 26 | 507 | +| [src/client/views/EditableView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/EditableView.scss) | SCSS | 22 | 0 | 3 | 25 | +| [src/client/views/EditableView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx) | TypeScript React | 149 | 19 | 16 | 184 | +| [src/client/views/GestureOverlay.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/GestureOverlay.scss) | SCSS | 56 | 0 | 8 | 64 | +| [src/client/views/GestureOverlay.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/GestureOverlay.tsx) | TypeScript React | 711 | 45 | 67 | 823 | +| [src/client/views/GlobalKeyHandler.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/GlobalKeyHandler.ts) | TypeScript | 232 | 10 | 27 | 269 | +| [src/client/views/InkingControl.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingControl.scss) | SCSS | 127 | 4 | 0 | 131 | +| [src/client/views/InkingControl.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingControl.tsx) | TypeScript React | 78 | 3 | 10 | 91 | +| [src/client/views/InkingStroke.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingStroke.scss) | SCSS | 7 | 0 | 0 | 7 | +| [src/client/views/InkingStroke.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingStroke.tsx) | TypeScript React | 63 | 0 | 5 | 68 | +| [src/client/views/KeyphraseQueryView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/KeyphraseQueryView.scss) | SCSS | 7 | 0 | 1 | 8 | +| [src/client/views/KeyphraseQueryView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/KeyphraseQueryView.tsx) | TypeScript React | 30 | 2 | 3 | 35 | +| [src/client/views/Main.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Main.scss) | SCSS | 51 | 8 | 10 | 69 | +| [src/client/views/Main.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Main.tsx) | TypeScript React | 22 | 1 | 2 | 25 | +| [src/client/views/MainView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainView.scss) | SCSS | 138 | 1 | 21 | 160 | +| [src/client/views/MainView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx) | TypeScript React | 553 | 13 | 36 | 602 | +| [src/client/views/MainViewModal.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewModal.scss) | SCSS | 24 | 0 | 1 | 25 | +| [src/client/views/MainViewModal.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewModal.tsx) | TypeScript React | 39 | 0 | 5 | 44 | +| [src/client/views/MainViewNotifs.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewNotifs.scss) | SCSS | 17 | 0 | 1 | 18 | +| [src/client/views/MainViewNotifs.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewNotifs.tsx) | TypeScript React | 29 | 0 | 4 | 33 | +| [src/client/views/MetadataEntryMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MetadataEntryMenu.scss) | SCSS | 79 | 0 | 14 | 93 | +| [src/client/views/MetadataEntryMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MetadataEntryMenu.tsx) | TypeScript React | 207 | 0 | 16 | 223 | +| [src/client/views/OCRUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/OCRUtils.ts) | TypeScript | 2 | 2 | 4 | 8 | +| [src/client/views/OverlayView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/OverlayView.scss) | SCSS | 41 | 0 | 6 | 47 | +| [src/client/views/OverlayView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/OverlayView.tsx) | TypeScript React | 194 | 4 | 19 | 217 | +| [src/client/views/Palette.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Palette.scss) | SCSS | 26 | 0 | 4 | 30 | +| [src/client/views/Palette.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Palette.tsx) | TypeScript React | 65 | 0 | 5 | 70 | +| [src/client/views/PreviewCursor.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/PreviewCursor.scss) | SCSS | 9 | 0 | 1 | 10 | +| [src/client/views/PreviewCursor.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/PreviewCursor.tsx) | TypeScript React | 115 | 8 | 9 | 132 | +| [src/client/views/RecommendationsBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/RecommendationsBox.scss) | SCSS | 52 | 10 | 8 | 70 | +| [src/client/views/RecommendationsBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/RecommendationsBox.tsx) | TypeScript React | 116 | 67 | 17 | 200 | +| [src/client/views/ScriptBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptBox.scss) | SCSS | 15 | 0 | 2 | 17 | +| [src/client/views/ScriptBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptBox.tsx) | TypeScript React | 112 | 2 | 12 | 126 | +| [src/client/views/ScriptingRepl.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptingRepl.scss) | SCSS | 42 | 0 | 9 | 51 | +| [src/client/views/ScriptingRepl.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptingRepl.tsx) | TypeScript React | 220 | 1 | 24 | 245 | +| [src/client/views/SearchDocBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/SearchDocBox.tsx) | TypeScript React | 359 | 17 | 55 | 431 | +| [src/client/views/TemplateMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/TemplateMenu.scss) | SCSS | 46 | 0 | 5 | 51 | +| [src/client/views/TemplateMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/TemplateMenu.tsx) | TypeScript React | 167 | 1 | 14 | 182 | +| [src/client/views/Templates.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Templates.tsx) | TypeScript React | 34 | 0 | 8 | 42 | +| [src/client/views/TouchScrollableMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/TouchScrollableMenu.tsx) | TypeScript React | 52 | 0 | 7 | 59 | +| [src/client/views/Touchable.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Touchable.tsx) | TypeScript React | 172 | 28 | 39 | 239 | +| [src/client/views/_nodeModuleOverrides.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/_nodeModuleOverrides.scss) | SCSS | 9 | 9 | 4 | 22 | +| [src/client/views/animationtimeline/Keyframe.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Keyframe.scss) | SCSS | 83 | 5 | 17 | 105 | +| [src/client/views/animationtimeline/Keyframe.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Keyframe.tsx) | TypeScript React | 468 | 50 | 42 | 560 | +| [src/client/views/animationtimeline/Timeline.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Timeline.scss) | SCSS | 264 | 14 | 44 | 322 | +| [src/client/views/animationtimeline/Timeline.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Timeline.tsx) | TypeScript React | 467 | 97 | 58 | 622 | +| [src/client/views/animationtimeline/TimelineMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineMenu.scss) | SCSS | 75 | 2 | 17 | 94 | +| [src/client/views/animationtimeline/TimelineMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineMenu.tsx) | TypeScript React | 68 | 0 | 10 | 78 | +| [src/client/views/animationtimeline/TimelineOverview.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineOverview.scss) | SCSS | 89 | 6 | 12 | 107 | +| [src/client/views/animationtimeline/TimelineOverview.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineOverview.tsx) | TypeScript React | 155 | 0 | 27 | 182 | +| [src/client/views/animationtimeline/Track.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Track.scss) | SCSS | 13 | 0 | 2 | 15 | +| [src/client/views/animationtimeline/Track.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Track.tsx) | TypeScript React | 284 | 63 | 33 | 380 | +| [src/client/views/collections/CollectionCarouselView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionCarouselView.scss) | SCSS | 37 | 0 | 1 | 38 | +| [src/client/views/collections/CollectionCarouselView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionCarouselView.tsx) | TypeScript React | 110 | 1 | 10 | 121 | +| [src/client/views/collections/CollectionDockingView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionDockingView.scss) | SCSS | 391 | 7 | 60 | 458 | +| [src/client/views/collections/CollectionDockingView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionDockingView.tsx) | TypeScript React | 707 | 61 | 65 | 833 | +| [src/client/views/collections/CollectionLinearView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionLinearView.scss) | SCSS | 68 | 0 | 10 | 78 | +| [src/client/views/collections/CollectionLinearView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionLinearView.tsx) | TypeScript React | 125 | 1 | 9 | 135 | +| [src/client/views/collections/CollectionMapView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionMapView.scss) | SCSS | 27 | 0 | 3 | 30 | +| [src/client/views/collections/CollectionMapView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionMapView.tsx) | TypeScript React | 235 | 10 | 18 | 263 | +| [src/client/views/collections/CollectionMasonryViewFieldRow.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionMasonryViewFieldRow.tsx) | TypeScript React | 308 | 0 | 24 | 332 | +| [src/client/views/collections/CollectionPileView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionPileView.scss) | SCSS | 8 | 0 | 1 | 9 | +| [src/client/views/collections/CollectionPileView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionPileView.tsx) | TypeScript React | 112 | 5 | 11 | 128 | +| [src/client/views/collections/CollectionSchemaCells.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaCells.tsx) | TypeScript React | 274 | 17 | 38 | 329 | +| [src/client/views/collections/CollectionSchemaHeaders.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaHeaders.tsx) | TypeScript React | 318 | 5 | 41 | 364 | +| [src/client/views/collections/CollectionSchemaMovableTableHOC.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx) | TypeScript React | 216 | 0 | 27 | 243 | +| [src/client/views/collections/CollectionSchemaView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaView.scss) | SCSS | 406 | 4 | 88 | 498 | +| [src/client/views/collections/CollectionSchemaView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaView.tsx) | TypeScript React | 651 | 20 | 82 | 753 | +| [src/client/views/collections/CollectionStackingView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStackingView.scss) | SCSS | 353 | 1 | 50 | 404 | +| [src/client/views/collections/CollectionStackingView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStackingView.tsx) | TypeScript React | 430 | 5 | 25 | 460 | +| [src/client/views/collections/CollectionStackingViewFieldColumn.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStackingViewFieldColumn.tsx) | TypeScript React | 367 | 0 | 27 | 394 | +| [src/client/views/collections/CollectionStaffView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStaffView.scss) | SCSS | 12 | 0 | 1 | 13 | +| [src/client/views/collections/CollectionStaffView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStaffView.tsx) | TypeScript React | 46 | 0 | 7 | 53 | +| [src/client/views/collections/CollectionSubView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSubView.tsx) | TypeScript React | 390 | 7 | 25 | 422 | +| [src/client/views/collections/CollectionTimeView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTimeView.scss) | SCSS | 80 | 0 | 13 | 93 | +| [src/client/views/collections/CollectionTimeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTimeView.tsx) | TypeScript React | 177 | 0 | 15 | 192 | +| [src/client/views/collections/CollectionTreeView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTreeView.scss) | SCSS | 125 | 4 | 23 | 152 | +| [src/client/views/collections/CollectionTreeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTreeView.tsx) | TypeScript React | 801 | 19 | 40 | 860 | +| [src/client/views/collections/CollectionView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.scss) | SCSS | 70 | 0 | 8 | 78 | +| [src/client/views/collections/CollectionView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx) | TypeScript React | 457 | 13 | 34 | 504 | +| [src/client/views/collections/CollectionViewChromes.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionViewChromes.scss) | SCSS | 308 | 4 | 45 | 357 | +| [src/client/views/collections/CollectionViewChromes.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionViewChromes.tsx) | TypeScript React | 393 | 67 | 47 | 507 | +| [src/client/views/collections/KeyRestrictionRow.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/KeyRestrictionRow.tsx) | TypeScript React | 49 | 2 | 4 | 55 | +| [src/client/views/collections/ParentDocumentSelector.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/ParentDocumentSelector.scss) | SCSS | 54 | 0 | 2 | 56 | +| [src/client/views/collections/ParentDocumentSelector.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/ParentDocumentSelector.tsx) | TypeScript React | 120 | 0 | 11 | 131 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx) | TypeScript React | 422 | 10 | 27 | 459 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss) | SCSS | 19 | 0 | 1 | 20 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx) | TypeScript React | 110 | 5 | 4 | 119 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss) | SCSS | 11 | 0 | 0 | 11 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx) | TypeScript React | 44 | 0 | 2 | 46 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss) | SCSS | 20 | 1 | 3 | 24 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx) | TypeScript React | 67 | 0 | 12 | 79 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss) | SCSS | 95 | 9 | 17 | 121 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx) | TypeScript React | 1,186 | 47 | 97 | 1,330 | +| [src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx) | TypeScript React | 52 | 0 | 5 | 57 | +| [src/client/views/collections/collectionFreeForm/MarqueeView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/MarqueeView.scss) | SCSS | 30 | 0 | 2 | 32 | +| [src/client/views/collections/collectionFreeForm/MarqueeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/MarqueeView.tsx) | TypeScript React | 484 | 105 | 33 | 622 | +| [src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss) | SCSS | 28 | 0 | 6 | 34 | +| [src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx) | TypeScript React | 202 | 72 | 23 | 297 | +| [src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss) | SCSS | 29 | 0 | 6 | 35 | +| [src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx) | TypeScript React | 204 | 72 | 22 | 298 | +| [src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx) | TypeScript React | 94 | 0 | 9 | 103 | +| [src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx) | TypeScript React | 51 | 0 | 5 | 56 | +| [src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx) | TypeScript React | 51 | 0 | 5 | 56 | +| [src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx) | TypeScript React | 92 | 0 | 9 | 101 | +| [src/client/views/globalCssVariables.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/globalCssVariables.scss) | SCSS | 30 | 10 | 3 | 43 | +| [src/client/views/globalCssVariables.scss.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/globalCssVariables.scss.d.ts) | TypeScript | 9 | 0 | 2 | 11 | +| [src/client/views/linking/LinkEditor.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkEditor.scss) | SCSS | 124 | 1 | 25 | 150 | +| [src/client/views/linking/LinkEditor.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkEditor.tsx) | TypeScript React | 252 | 7 | 50 | 309 | +| [src/client/views/linking/LinkMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenu.scss) | SCSS | 40 | 0 | 13 | 53 | +| [src/client/views/linking/LinkMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenu.tsx) | TypeScript React | 65 | 1 | 10 | 76 | +| [src/client/views/linking/LinkMenuGroup.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenuGroup.tsx) | TypeScript React | 84 | 0 | 10 | 94 | +| [src/client/views/linking/LinkMenuItem.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenuItem.scss) | SCSS | 75 | 1 | 11 | 87 | +| [src/client/views/linking/LinkMenuItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenuItem.tsx) | TypeScript React | 107 | 0 | 19 | 126 | +| [src/client/views/nodes/AudioBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/AudioBox.scss) | SCSS | 146 | 0 | 0 | 146 | +| [src/client/views/nodes/AudioBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/AudioBox.tsx) | TypeScript React | 261 | 2 | 24 | 287 | +| [src/client/views/nodes/CollectionFreeFormDocumentView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/CollectionFreeFormDocumentView.scss) | SCSS | 8 | 0 | 0 | 8 | +| [src/client/views/nodes/CollectionFreeFormDocumentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/CollectionFreeFormDocumentView.tsx) | TypeScript React | 124 | 0 | 7 | 131 | +| [src/client/views/nodes/ColorBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ColorBox.scss) | SCSS | 22 | 0 | 1 | 23 | +| [src/client/views/nodes/ColorBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ColorBox.tsx) | TypeScript React | 28 | 0 | 4 | 32 | +| [src/client/views/nodes/ContentFittingDocumentView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ContentFittingDocumentView.scss) | SCSS | 20 | 0 | 4 | 24 | +| [src/client/views/nodes/ContentFittingDocumentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ContentFittingDocumentView.tsx) | TypeScript React | 117 | 0 | 7 | 124 | +| [src/client/views/nodes/DocumentBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentBox.scss) | SCSS | 14 | 0 | 0 | 14 | +| [src/client/views/nodes/DocumentBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentBox.tsx) | TypeScript React | 154 | 0 | 4 | 158 | +| [src/client/views/nodes/DocumentContentsView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentContentsView.tsx) | TypeScript React | 183 | 10 | 17 | 210 | +| [src/client/views/nodes/DocumentIcon.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentIcon.tsx) | TypeScript React | 60 | 0 | 5 | 65 | +| [src/client/views/nodes/DocumentView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.scss) | SCSS | 110 | 1 | 15 | 126 | +| [src/client/views/nodes/DocumentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx) | TypeScript React | 1,041 | 54 | 94 | 1,189 | +| [src/client/views/nodes/FaceRectangle.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FaceRectangle.tsx) | TypeScript React | 25 | 0 | 4 | 29 | +| [src/client/views/nodes/FaceRectangles.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FaceRectangles.tsx) | TypeScript React | 41 | 0 | 5 | 46 | +| [src/client/views/nodes/FieldTextBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FieldTextBox.scss) | SCSS | 12 | 0 | 3 | 15 | +| [src/client/views/nodes/FieldView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FieldView.tsx) | TypeScript React | 89 | 43 | 4 | 136 | +| [src/client/views/nodes/FontIconBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FontIconBox.scss) | SCSS | 25 | 0 | 2 | 27 | +| [src/client/views/nodes/FontIconBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FontIconBox.tsx) | TypeScript React | 58 | 0 | 5 | 63 | +| [src/client/views/nodes/ImageBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ImageBox.scss) | SCSS | 135 | 0 | 17 | 152 | +| [src/client/views/nodes/ImageBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ImageBox.tsx) | TypeScript React | 430 | 10 | 35 | 475 | +| [src/client/views/nodes/KeyValueBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValueBox.scss) | SCSS | 120 | 0 | 3 | 123 | +| [src/client/views/nodes/KeyValueBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValueBox.tsx) | TypeScript React | 244 | 1 | 26 | 271 | +| [src/client/views/nodes/KeyValuePair.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValuePair.scss) | SCSS | 55 | 1 | 4 | 60 | +| [src/client/views/nodes/KeyValuePair.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValuePair.tsx) | TypeScript React | 125 | 2 | 8 | 135 | +| [src/client/views/nodes/LabelBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LabelBox.scss) | SCSS | 31 | 0 | 4 | 35 | +| [src/client/views/nodes/LabelBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LabelBox.tsx) | TypeScript React | 86 | 1 | 9 | 96 | +| [src/client/views/nodes/LinkAnchorBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkAnchorBox.scss) | SCSS | 27 | 0 | 2 | 29 | +| [src/client/views/nodes/LinkAnchorBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkAnchorBox.tsx) | TypeScript React | 141 | 0 | 9 | 150 | +| [src/client/views/nodes/LinkBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkBox.scss) | SCSS | 3 | 0 | 0 | 3 | +| [src/client/views/nodes/LinkBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkBox.tsx) | TypeScript React | 33 | 0 | 3 | 36 | +| [src/client/views/nodes/PDFBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PDFBox.scss) | SCSS | 200 | 0 | 19 | 219 | +| [src/client/views/nodes/PDFBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PDFBox.tsx) | TypeScript React | 246 | 0 | 19 | 265 | +| [src/client/views/nodes/PresBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PresBox.scss) | SCSS | 52 | 0 | 1 | 53 | +| [src/client/views/nodes/PresBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PresBox.tsx) | TypeScript React | 268 | 31 | 32 | 331 | +| [src/client/views/nodes/QueryBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/QueryBox.scss) | SCSS | 5 | 0 | 0 | 5 | +| [src/client/views/nodes/QueryBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/QueryBox.tsx) | TypeScript React | 37 | 0 | 4 | 41 | +| [src/client/views/nodes/RadialMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/RadialMenu.scss) | SCSS | 60 | 3 | 7 | 70 | +| [src/client/views/nodes/RadialMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/RadialMenu.tsx) | TypeScript React | 174 | 26 | 36 | 236 | +| [src/client/views/nodes/RadialMenuItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/RadialMenuItem.tsx) | TypeScript React | 101 | 0 | 16 | 117 | +| [src/client/views/nodes/ScreenshotBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScreenshotBox.scss) | SCSS | 40 | 6 | 5 | 51 | +| [src/client/views/nodes/ScreenshotBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScreenshotBox.tsx) | TypeScript React | 174 | 2 | 18 | 194 | +| [src/client/views/nodes/ScriptingBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScriptingBox.scss) | SCSS | 33 | 0 | 3 | 36 | +| [src/client/views/nodes/ScriptingBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScriptingBox.tsx) | TypeScript React | 87 | 0 | 12 | 99 | +| [src/client/views/nodes/SliderBox-components.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox-components.tsx) | TypeScript React | 220 | 12 | 25 | 257 | +| [src/client/views/nodes/SliderBox-tooltip.css](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox-tooltip.css) | CSS | 30 | 0 | 3 | 33 | +| [src/client/views/nodes/SliderBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox.scss) | SCSS | 7 | 0 | 0 | 7 | +| [src/client/views/nodes/SliderBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox.tsx) | TypeScript React | 117 | 0 | 8 | 125 | +| [src/client/views/nodes/VideoBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/VideoBox.scss) | SCSS | 65 | 3 | 6 | 74 | +| [src/client/views/nodes/VideoBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/VideoBox.tsx) | TypeScript React | 343 | 2 | 34 | 379 | +| [src/client/views/nodes/WebBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/WebBox.scss) | SCSS | 109 | 0 | 18 | 127 | +| [src/client/views/nodes/WebBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/WebBox.tsx) | TypeScript React | 345 | 15 | 35 | 395 | +| [src/client/views/nodes/formattedText/DashDocCommentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashDocCommentView.tsx) | TypeScript React | 82 | 0 | 13 | 95 | +| [src/client/views/nodes/formattedText/DashDocView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashDocView.tsx) | TypeScript React | 223 | 9 | 37 | 269 | +| [src/client/views/nodes/formattedText/DashFieldView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashFieldView.scss) | SCSS | 34 | 0 | 2 | 36 | +| [src/client/views/nodes/formattedText/DashFieldView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashFieldView.tsx) | TypeScript React | 178 | 13 | 20 | 211 | +| [src/client/views/nodes/formattedText/FootnoteView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FootnoteView.tsx) | TypeScript React | 131 | 13 | 19 | 163 | +| [src/client/views/nodes/formattedText/FormattedTextBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBox.scss) | SCSS | 220 | 11 | 34 | 265 | +| [src/client/views/nodes/formattedText/FormattedTextBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBox.tsx) | TypeScript React | 1,169 | 68 | 93 | 1,330 | +| [src/client/views/nodes/formattedText/FormattedTextBoxComment.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss) | SCSS | 33 | 0 | 0 | 33 | +| [src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx) | TypeScript React | 218 | 11 | 8 | 237 | +| [src/client/views/nodes/formattedText/ImageResizeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/ImageResizeView.tsx) | TypeScript React | 113 | 0 | 25 | 138 | +| [src/client/views/nodes/formattedText/ParagraphNodeSpec.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts) | TypeScript | 108 | 9 | 26 | 143 | +| [src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts) | TypeScript | 231 | 0 | 11 | 242 | +| [src/client/views/nodes/formattedText/RichTextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextMenu.scss) | SCSS | 100 | 2 | 19 | 121 | +| [src/client/views/nodes/formattedText/RichTextMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextMenu.tsx) | TypeScript React | 754 | 12 | 109 | 875 | +| [src/client/views/nodes/formattedText/RichTextRules.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextRules.ts) | TypeScript | 308 | 7 | 5 | 320 | +| [src/client/views/nodes/formattedText/RichTextSchema.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextSchema.tsx) | TypeScript React | 465 | 33 | 39 | 537 | +| [src/client/views/nodes/formattedText/SummaryView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/SummaryView.tsx) | TypeScript React | 67 | 0 | 14 | 81 | +| [src/client/views/nodes/formattedText/TooltipTextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/TooltipTextMenu.scss) | SCSS | 306 | 6 | 61 | 373 | +| [src/client/views/nodes/formattedText/marks_rts.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/marks_rts.ts) | TypeScript | 259 | 15 | 23 | 297 | +| [src/client/views/nodes/formattedText/nodes_rts.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/nodes_rts.ts) | TypeScript | 224 | 21 | 19 | 264 | +| [src/client/views/nodes/formattedText/prosemirrorPatches.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/prosemirrorPatches.js) | JavaScript | 118 | 12 | 9 | 139 | +| [src/client/views/nodes/formattedText/schema_rts.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/schema_rts.ts) | TypeScript | 12 | 8 | 6 | 26 | +| [src/client/views/pdf/Annotation.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/Annotation.scss) | SCSS | 6 | 0 | 0 | 6 | +| [src/client/views/pdf/Annotation.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/Annotation.tsx) | TypeScript React | 114 | 0 | 16 | 130 | +| [src/client/views/pdf/PDFMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFMenu.scss) | SCSS | 6 | 0 | 0 | 6 | +| [src/client/views/pdf/PDFMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFMenu.tsx) | TypeScript React | 102 | 0 | 21 | 123 | +| [src/client/views/pdf/PDFViewer.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFViewer.scss) | SCSS | 74 | 4 | 10 | 88 | +| [src/client/views/pdf/PDFViewer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFViewer.tsx) | TypeScript React | 662 | 23 | 46 | 731 | +| [src/client/views/presentationview/PresElementBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/presentationview/PresElementBox.scss) | SCSS | 93 | 0 | 10 | 103 | +| [src/client/views/presentationview/PresElementBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/presentationview/PresElementBox.tsx) | TypeScript React | 179 | 31 | 14 | 224 | +| [src/client/views/search/CheckBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CheckBox.scss) | SCSS | 50 | 1 | 8 | 59 | +| [src/client/views/search/CheckBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CheckBox.tsx) | TypeScript React | 42 | 74 | 15 | 131 | +| [src/client/views/search/CollectionFilters.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CollectionFilters.scss) | SCSS | 17 | 0 | 3 | 20 | +| [src/client/views/search/CollectionFilters.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CollectionFilters.tsx) | TypeScript React | 69 | 0 | 14 | 83 | +| [src/client/views/search/FieldFilters.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FieldFilters.scss) | SCSS | 10 | 1 | 1 | 12 | +| [src/client/views/search/FieldFilters.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FieldFilters.tsx) | TypeScript React | 34 | 0 | 7 | 41 | +| [src/client/views/search/FilterBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FilterBox.scss) | SCSS | 153 | 0 | 25 | 178 | +| [src/client/views/search/FilterBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FilterBox.tsx) | TypeScript React | 354 | 24 | 54 | 432 | +| [src/client/views/search/IconBar.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconBar.scss) | SCSS | 9 | 0 | 1 | 10 | +| [src/client/views/search/IconBar.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconBar.tsx) | TypeScript React | 69 | 1 | 17 | 87 | +| [src/client/views/search/IconButton.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconButton.scss) | SCSS | 46 | 1 | 6 | 53 | +| [src/client/views/search/IconButton.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconButton.tsx) | TypeScript React | 170 | 2 | 19 | 191 | +| [src/client/views/search/NaviconButton.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/NaviconButton.scss) | SCSS | 58 | 0 | 11 | 69 | +| [src/client/views/search/NaviconButton.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/NaviconButton.tsx) | TypeScript React | 32 | 0 | 5 | 37 | +| [src/client/views/search/SearchBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchBox.scss) | SCSS | 203 | 82 | 51 | 336 | +| [src/client/views/search/SearchBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchBox.tsx) | TypeScript React | 530 | 47 | 94 | 671 | +| [src/client/views/search/SearchItem.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchItem.scss) | SCSS | 138 | 0 | 25 | 163 | +| [src/client/views/search/SearchItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchItem.tsx) | TypeScript React | 272 | 2 | 29 | 303 | +| [src/client/views/search/SelectorContextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SelectorContextMenu.scss) | SCSS | 12 | 1 | 3 | 16 | +| [src/client/views/search/ToggleBar.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/ToggleBar.scss) | SCSS | 35 | 2 | 4 | 41 | +| [src/client/views/search/ToggleBar.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/ToggleBar.tsx) | TypeScript React | 77 | 0 | 9 | 86 | +| [src/client/views/webcam/DashWebRTCVideo.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/webcam/DashWebRTCVideo.scss) | SCSS | 70 | 4 | 9 | 83 | +| [src/client/views/webcam/DashWebRTCVideo.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/webcam/DashWebRTCVideo.tsx) | TypeScript React | 67 | 6 | 16 | 89 | +| [src/client/views/webcam/WebCamLogic.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/webcam/WebCamLogic.js) | JavaScript | 234 | 7 | 51 | 292 | +| [src/debug/Repl.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/debug/Repl.tsx) | TypeScript React | 59 | 0 | 7 | 66 | +| [src/debug/Test.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/debug/Test.tsx) | TypeScript React | 12 | 0 | 2 | 14 | +| [src/debug/Viewer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/debug/Viewer.tsx) | TypeScript React | 173 | 0 | 19 | 192 | +| [src/extensions/ArrayExtensions.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/ArrayExtensions.ts) | TypeScript | 26 | 5 | 6 | 37 | +| [src/extensions/General/Extensions.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/General/Extensions.ts) | TypeScript | 7 | 0 | 2 | 9 | +| [src/extensions/General/ExtensionsTypings.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/General/ExtensionsTypings.ts) | TypeScript | 7 | 0 | 1 | 8 | +| [src/extensions/StringExtensions.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/StringExtensions.ts) | TypeScript | 13 | 0 | 4 | 17 | +| [src/mobile/ImageUpload.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/ImageUpload.scss) | SCSS | 30 | 0 | 4 | 34 | +| [src/mobile/ImageUpload.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/ImageUpload.tsx) | TypeScript React | 78 | 41 | 12 | 131 | +| [src/mobile/InkControls.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/InkControls.tsx) | TypeScript React | 0 | 0 | 1 | 1 | +| [src/mobile/MobileInkOverlay.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInkOverlay.scss) | SCSS | 33 | 1 | 5 | 39 | +| [src/mobile/MobileInkOverlay.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInkOverlay.tsx) | TypeScript React | 162 | 3 | 26 | 191 | +| [src/mobile/MobileInterface.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInterface.scss) | SCSS | 17 | 0 | 2 | 19 | +| [src/mobile/MobileInterface.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInterface.tsx) | TypeScript React | 297 | 15 | 32 | 344 | +| [src/new_fields/CursorField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/CursorField.ts) | TypeScript | 54 | 0 | 12 | 66 | +| [src/new_fields/DateField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/DateField.ts) | TypeScript | 30 | 0 | 7 | 37 | +| [src/new_fields/Doc.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Doc.ts) | TypeScript | 897 | 85 | 76 | 1,058 | +| [src/new_fields/FieldSymbols.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/FieldSymbols.ts) | TypeScript | 11 | 0 | 2 | 13 | +| [src/new_fields/HtmlField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/HtmlField.ts) | TypeScript | 22 | 0 | 5 | 27 | +| [src/new_fields/IconField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/IconField.ts) | TypeScript | 22 | 0 | 5 | 27 | +| [src/new_fields/InkField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/InkField.ts) | TypeScript | 41 | 0 | 10 | 51 | +| [src/new_fields/List.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/List.ts) | TypeScript | 244 | 38 | 20 | 302 | +| [src/new_fields/ListSpec.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/ListSpec.ts) | TypeScript | 0 | 0 | 1 | 1 | +| [src/new_fields/ObjectField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/ObjectField.ts) | TypeScript | 16 | 0 | 4 | 20 | +| [src/new_fields/PresField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/PresField.ts) | TypeScript | 3 | 1 | 2 | 6 | +| [src/new_fields/Proxy.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Proxy.ts) | TypeScript | 95 | 2 | 14 | 111 | +| [src/new_fields/RefField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/RefField.ts) | TypeScript | 17 | 0 | 5 | 22 | +| [src/new_fields/RichTextField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/RichTextField.ts) | TypeScript | 33 | 0 | 8 | 41 | +| [src/new_fields/RichTextUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/RichTextUtils.ts) | TypeScript | 455 | 8 | 56 | 519 | +| [src/new_fields/Schema.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Schema.ts) | TypeScript | 107 | 5 | 8 | 120 | +| [src/new_fields/SchemaHeaderField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/SchemaHeaderField.ts) | TypeScript | 104 | 4 | 14 | 122 | +| [src/new_fields/ScriptField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/ScriptField.ts) | TypeScript | 137 | 21 | 19 | 177 | +| [src/new_fields/Types.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Types.ts) | TypeScript | 86 | 5 | 17 | 108 | +| [src/new_fields/URLField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/URLField.ts) | TypeScript | 45 | 0 | 9 | 54 | +| [src/new_fields/documentSchemas.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/documentSchemas.ts) | TypeScript | 87 | 0 | 6 | 93 | +| [src/new_fields/util.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/util.ts) | TypeScript | 176 | 3 | 15 | 194 | +| [src/pen-gestures/GestureUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/pen-gestures/GestureUtils.ts) | TypeScript | 41 | 0 | 5 | 46 | +| [src/pen-gestures/ndollar.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/pen-gestures/ndollar.ts) | TypeScript | 356 | 172 | 22 | 550 | +| [src/scraping/acm/.gitignore](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/.gitignore) | Ignore | 2 | 0 | 0 | 2 | +| [src/scraping/acm/debug.log](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/debug.log) | log | 38 | 0 | 1 | 39 | +| [src/scraping/acm/index.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/index.js) | JavaScript | 82 | 185 | 13 | 280 | +| [src/scraping/acm/package.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/package.json) | JSON | 17 | 0 | 1 | 18 | +| [src/scraping/buxton/.idea/buxton.iml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/buxton.iml) | XML | 8 | 0 | 0 | 8 | +| [src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml) | XML | 6 | 0 | 0 | 6 | +| [src/scraping/buxton/.idea/misc.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/misc.xml) | XML | 4 | 0 | 0 | 4 | +| [src/scraping/buxton/.idea/modules.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/modules.xml) | XML | 8 | 0 | 0 | 8 | +| [src/scraping/buxton/.idea/vcs.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/vcs.xml) | XML | 6 | 0 | 0 | 6 | +| [src/scraping/buxton/.idea/workspace.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/workspace.xml) | XML | 173 | 0 | 0 | 173 | +| [src/scraping/buxton/final/BuxtonImporter.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/final/BuxtonImporter.ts) | TypeScript | 228 | 142 | 26 | 396 | +| [src/scraping/buxton/jsonifier.py](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/jsonifier.py) | Python | 183 | 1 | 48 | 232 | +| [src/scraping/buxton/narratives.py](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/narratives.py) | Python | 11 | 19 | 9 | 39 | +| [src/scraping/buxton/narratives/chord_keyboards.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/narratives/chord_keyboards.json) | JSON | 39 | 0 | 0 | 39 | +| [src/scraping/buxton/scraper.py](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/scraper.py) | Python | 350 | 5 | 78 | 433 | +| [src/server/ActionUtilities.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ActionUtilities.ts) | TypeScript | 136 | 1 | 23 | 160 | +| [src/server/ApiManagers/ApiManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/ApiManager.ts) | TypeScript | 8 | 0 | 3 | 11 | +| [src/server/ApiManagers/DeleteManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/DeleteManager.ts) | TypeScript | 71 | 0 | 11 | 82 | +| [src/server/ApiManagers/DownloadManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/DownloadManager.ts) | TypeScript | 173 | 80 | 16 | 269 | +| [src/server/ApiManagers/GeneralGoogleManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/GeneralGoogleManager.ts) | TypeScript | 53 | 0 | 8 | 61 | +| [src/server/ApiManagers/GooglePhotosManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/GooglePhotosManager.ts) | TypeScript | 190 | 119 | 22 | 331 | +| [src/server/ApiManagers/PDFManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/PDFManager.ts) | TypeScript | 103 | 0 | 12 | 115 | +| [src/server/ApiManagers/SearchManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/SearchManager.ts) | TypeScript | 200 | 0 | 15 | 215 | +| [src/server/ApiManagers/SessionManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/SessionManager.ts) | TypeScript | 56 | 0 | 11 | 67 | +| [src/server/ApiManagers/UploadManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/UploadManager.ts) | TypeScript | 226 | 1 | 20 | 247 | +| [src/server/ApiManagers/UserManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/UserManager.ts) | TypeScript | 101 | 4 | 21 | 126 | +| [src/server/ApiManagers/UtilManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/UtilManager.ts) | TypeScript | 42 | 22 | 10 | 74 | +| [src/server/Client.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Client.ts) | TypeScript | 8 | 0 | 3 | 11 | +| [src/server/DashSession/DashSessionAgent.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/DashSessionAgent.ts) | TypeScript | 155 | 52 | 23 | 230 | +| [src/server/DashSession/Session/agents/applied_session_agent.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/applied_session_agent.ts) | TypeScript | 47 | 2 | 9 | 58 | +| [src/server/DashSession/Session/agents/monitor.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/monitor.ts) | TypeScript | 213 | 59 | 26 | 298 | +| [src/server/DashSession/Session/agents/process_message_router.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/process_message_router.ts) | TypeScript | 24 | 10 | 7 | 41 | +| [src/server/DashSession/Session/agents/promisified_ipc_manager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/promisified_ipc_manager.ts) | TypeScript | 106 | 52 | 15 | 173 | +| [src/server/DashSession/Session/agents/server_worker.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/server_worker.ts) | TypeScript | 99 | 46 | 15 | 160 | +| [src/server/DashSession/Session/utilities/repl.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/utilities/repl.ts) | TypeScript | 116 | 0 | 12 | 128 | +| [src/server/DashSession/Session/utilities/session_config.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/utilities/session_config.ts) | TypeScript | 119 | 0 | 10 | 129 | +| [src/server/DashSession/Session/utilities/utilities.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/utilities/utilities.ts) | TypeScript | 24 | 8 | 5 | 37 | +| [src/server/DashUploadUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashUploadUtils.ts) | TypeScript | 285 | 52 | 30 | 367 | +| [src/server/GarbageCollector.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/GarbageCollector.ts) | TypeScript | 138 | 2 | 11 | 151 | +| [src/server/IDatabase.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/IDatabase.ts) | TypeScript | 17 | 0 | 8 | 25 | +| [src/server/MemoryDatabase.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/MemoryDatabase.ts) | TypeScript | 87 | 0 | 14 | 101 | +| [src/server/Message.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Message.ts) | TypeScript | 86 | 0 | 18 | 104 | +| [src/server/PdfTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/PdfTypes.ts) | TypeScript | 19 | 0 | 2 | 21 | +| [src/server/ProcessFactory.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ProcessFactory.ts) | TypeScript | 34 | 0 | 10 | 44 | +| [src/server/Recommender.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Recommender.ts) | TypeScript | 0 | 120 | 18 | 138 | +| [src/server/RouteManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/RouteManager.ts) | TypeScript | 187 | 4 | 19 | 210 | +| [src/server/RouteSubscriber.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/RouteSubscriber.ts) | TypeScript | 21 | 0 | 5 | 26 | +| [src/server/Search.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Search.ts) | TypeScript | 71 | 2 | 8 | 81 | +| [src/server/SharedMediaTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/SharedMediaTypes.ts) | TypeScript | 41 | 0 | 10 | 51 | +| [src/server/Websocket/Websocket.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Websocket/Websocket.ts) | TypeScript | 263 | 5 | 46 | 314 | +| [src/server/apis/google/GoogleApiServerUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/google/GoogleApiServerUtils.ts) | TypeScript | 172 | 168 | 25 | 365 | +| [src/server/apis/google/SharedTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/google/SharedTypes.ts) | TypeScript | 19 | 0 | 2 | 21 | +| [src/server/apis/youtube/youtubeApiSample.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/youtube/youtubeApiSample.d.ts) | TypeScript | 2 | 0 | 0 | 2 | +| [src/server/apis/youtube/youtubeApiSample.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/youtube/youtubeApiSample.js) | JavaScript | 135 | 30 | 14 | 179 | +| [src/server/authentication/config/passport.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/config/passport.ts) | TypeScript | 23 | 2 | 4 | 29 | +| [src/server/authentication/controllers/user_controller.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/controllers/user_controller.ts) | TypeScript | 218 | 25 | 25 | 268 | +| [src/server/authentication/models/current_user_utils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/models/current_user_utils.ts) | TypeScript | 586 | 30 | 57 | 673 | +| [src/server/authentication/models/user_model.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/models/user_model.ts) | TypeScript | 63 | 9 | 14 | 86 | +| [src/server/credentials/CredentialsLoader.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/credentials/CredentialsLoader.ts) | TypeScript | 24 | 0 | 6 | 30 | +| [src/server/credentials/google_project_credentials.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/credentials/google_project_credentials.json) | JSON | 11 | 0 | 0 | 11 | +| [src/server/database.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/database.ts) | TypeScript | 312 | 0 | 38 | 350 | +| [src/server/downsize.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/downsize.ts) | TypeScript | 34 | 5 | 1 | 40 | +| [src/server/index.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/index.ts) | TypeScript | 107 | 36 | 16 | 159 | +| [src/server/remapUrl.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/remapUrl.ts) | TypeScript | 53 | 4 | 6 | 63 | +| [src/server/server_Initialization.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/server_Initialization.ts) | TypeScript | 138 | 6 | 24 | 168 | +| [src/server/slides.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/slides.json) | JSON | 10,820 | 0 | 0 | 10,820 | +| [src/server/updateProtos.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/updateProtos.ts) | TypeScript | 11 | 0 | 3 | 14 | +| [src/typings/index.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/typings/index.d.ts) | TypeScript | 219 | 72 | 38 | 329 | +| [test/test.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/test/test.ts) | TypeScript | 141 | 0 | 20 | 161 | +| [tsconfig.json](file:///Users/bcz/Documents/GitHub/Dash-Web/tsconfig.json) | JSON | 21 | 5 | 0 | 26 | +| [tslint.json](file:///Users/bcz/Documents/GitHub/Dash-Web/tslint.json) | JSON | 30 | 32 | 1 | 63 | +| [views/forgot.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/forgot.pug) | Pug | 19 | 1 | 2 | 22 | +| [views/layout.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/layout.pug) | Pug | 13 | 0 | 1 | 14 | +| [views/login.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/login.pug) | Pug | 24 | 0 | 2 | 26 | +| [views/reset.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/reset.pug) | Pug | 20 | 0 | 2 | 22 | +| [views/signup.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/signup.pug) | Pug | 25 | 0 | 2 | 27 | +| [views/stylesheets/authentication.css](file:///Users/bcz/Documents/GitHub/Dash-Web/views/stylesheets/authentication.css) | CSS | 185 | 4 | 34 | 223 | +| [views/user_activity.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/user_activity.pug) | Pug | 18 | 0 | 1 | 19 | +| [webpack.config.js](file:///Users/bcz/Documents/GitHub/Dash-Web/webpack.config.js) | JavaScript | 116 | 0 | 5 | 121 | + +[summary](results.md) \ No newline at end of file diff --git a/.VSCodeCounter/results.csv b/.VSCodeCounter/results.csv new file mode 100644 index 000000000..b31bc8262 --- /dev/null +++ b/.VSCodeCounter/results.csv @@ -0,0 +1,648 @@ +filename, language, Markdown, JavaScript, JSON, Batch, Python, TypeScript, HTML, Pug, Shell Script, CSS, TypeScript React, SCSS, XML, Properties, Ignore, log, XSL, comment, blank, total +README.md, Markdown, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 9 +build/index.html, HTML, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 12 +dash.bat, Batch, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3 +deploy/assets/env.json, JSON, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 +deploy/assets/pdf.worker.js, JavaScript, 0, 55662, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 686, 56522 +deploy/debug/repl.html, HTML, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14 +deploy/debug/test.html, HTML, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 13 +deploy/debug/viewer.html, HTML, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14 +deploy/index.html, HTML, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 16 +deploy/mobile/image.html, HTML, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 15 +deploy/mobile/ink.html, HTML, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 13 +package-lock.json, JSON, 0, 0, 18689, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 18690 +package.json, JSON, 0, 0, 266, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 267 +sentence_parser.py, Python, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7 +session.config.json, JSON, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 13 +solr-8.3.1/bin/install_solr_service.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 307, 0, 0, 0, 0, 0, 0, 0, 0, 29, 35, 371 +solr-8.3.1/bin/oom_solr.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 15, 3, 31 +solr-8.3.1/bin/solr.cmd, Batch, 0, 0, 0, 1782, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 210, 2035 +solr-8.3.1/bin/solr.in.cmd, Batch, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 29, 178 +solr-8.3.1/bin/solr.in.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 34, 206 +solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd, Batch, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 108 +solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json, JSON, 0, 0, 4465, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4466 +solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1734, 0, 0, 0, 0, 63, 10, 1807 +solr-8.3.1/docs/images/solr.svg, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 1, 40 +solr-8.3.1/docs/index.html, HTML, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 21 +solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 6, 9, 36 +solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 37, 8, 65 +solr-8.3.1/example/example-DIH/solr/atom/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 4, 30 +solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 958, 104, 1354 +solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/example/example-DIH/solr/db/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 4, 1, 13 +solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 958, 105, 1357 +solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/example/example-DIH/solr/mail/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/solr.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 3 +solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 16, 2, 26 +solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 958, 102, 1352 +solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/example/example-DIH/solr/solr/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 38, 7, 62 +solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 3, 7, 27 +solr-8.3.1/example/example-DIH/solr/tika/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/exampledocs/books.json, JSON, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 52 +solr-8.3.1/example/exampledocs/gb18030-example.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 16, 3, 33 +solr-8.3.1/example/exampledocs/hd.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 20, 4, 57 +solr-8.3.1/example/exampledocs/ipod_other.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 20, 9, 61 +solr-8.3.1/example/exampledocs/ipod_video.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 18, 2, 41 +solr-8.3.1/example/exampledocs/manufacturers.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 16, 3, 76 +solr-8.3.1/example/exampledocs/mem.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 24, 9, 78 +solr-8.3.1/example/exampledocs/money.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 17, 7, 66 +solr-8.3.1/example/exampledocs/monitor.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 18, 3, 35 +solr-8.3.1/example/exampledocs/monitor2.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 18, 3, 34 +solr-8.3.1/example/exampledocs/mp500.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 18, 3, 44 +solr-8.3.1/example/exampledocs/sample.html, HTML, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 14 +solr-8.3.1/example/exampledocs/sd500.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 18, 2, 39 +solr-8.3.1/example/exampledocs/solr.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 16, 3, 39 +solr-8.3.1/example/exampledocs/test_utf8.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 21, 16, 94 +solr-8.3.1/example/exampledocs/utf8-example.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 20, 4, 43 +solr-8.3.1/example/exampledocs/vidcard.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 21, 2, 63 +solr-8.3.1/example/files/browse-resources/velocity/resources.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 6, 5, 83 +solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 1, 19 +solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 3, 21 +solr-8.3.1/example/files/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/files/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/files/conf/params.json, JSON, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 35 +solr-8.3.1/example/files/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 298, 0, 0, 0, 0, 979, 102, 1379 +solr-8.3.1/example/files/conf/update-script.js, JavaScript, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 23, 116 +solr-8.3.1/example/files/conf/velocity/dropit.js, JavaScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js, JavaScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/files/conf/velocity/js/dropit.js, JavaScript, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 19, 98 +solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js, JavaScript, 0, 620, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 76, 764 +solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js, JavaScript, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 9, 71 +solr-8.3.1/example/films/film_data_generator.py, Python, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 12, 118 +solr-8.3.1/example/films/films.json, JSON, 0, 0, 15830, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 15831 +solr-8.3.1/example/films/films.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11438, 0, 0, 0, 0, 0, 1, 11439 +solr-8.3.1/server/contexts/solr-jetty-context.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 1, 9 +solr-8.3.1/server/etc/jetty-http.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 15, 4, 52 +solr-8.3.1/server/etc/jetty-https.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 16, 5, 77 +solr-8.3.1/server/etc/jetty-https8.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 32, 4, 70 +solr-8.3.1/server/etc/jetty-ssl.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 11, 4, 38 +solr-8.3.1/server/etc/jetty.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 94, 18, 222 +solr-8.3.1/server/etc/webdefault.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 232, 24, 528 +solr-8.3.1/server/resources/jetty-logging.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2 +solr-8.3.1/server/resources/log4j2-console.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 43, 6, 68 +solr-8.3.1/server/resources/log4j2.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 80, 9, 143 +solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 0, 2, 23, 177 +solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat, Batch, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 26 +solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 27 +solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0, 42, 11, 115 +solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 0, 0, 0, 0, 0, 0, 0, 19, 48, 304 +solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 402, 0, 0, 0, 0, 0, 0, 0, 55, 9, 466 +solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 594, 0, 0, 0, 0, 0, 0, 0, 23, 106, 723 +solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296, 0, 0, 0, 0, 0, 0, 0, 18, 65, 379 +solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 647, 0, 0, 0, 0, 0, 0, 0, 19, 106, 772 +solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0, 0, 18, 37, 226 +solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 18, 28, 180 +solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 0, 0, 0, 18, 61, 371 +solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 0, 0, 0, 0, 0, 0, 0, 23, 26, 180 +solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 18, 7, 54 +solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 0, 0, 0, 0, 0, 0, 0, 18, 35, 217 +solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 18, 6, 48 +solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 26, 2, 29 +solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 22, 2, 25 +solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 303, 0, 0, 0, 0, 0, 0, 0, 19, 63, 385 +solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 18, 12, 110 +solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 257, 0, 0, 0, 0, 0, 0, 0, 18, 56, 331 +solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 18, 5, 43 +solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 0, 0, 0, 0, 0, 0, 0, 18, 31, 221 +solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 18, 25, 163 +solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 404, 0, 0, 0, 0, 0, 0, 0, 18, 79, 501 +solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 596, 0, 0, 0, 0, 0, 0, 0, 20, 112, 728 +solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, 0, 18, 22, 173 +solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 0, 0, 0, 0, 22, 34, 234 +solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 18, 4, 65 +solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 18, 24, 161 +solr-8.3.1/server/solr-webapp/webapp/img/solr.svg, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 1, 40 +solr-8.3.1/server/solr-webapp/webapp/index.html, HTML, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 38, 257 +solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js, JavaScript, 0, 512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 31, 562 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js, JavaScript, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 4, 28 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js, JavaScript, 0, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 23, 202 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js, JavaScript, 0, 847, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 124, 1022 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js, JavaScript, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 2, 63 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js, JavaScript, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 6, 40 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js, JavaScript, 0, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 27, 290 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js, JavaScript, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 9, 94 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js, JavaScript, 0, 151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 14, 181 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js, JavaScript, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 44, 303 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js, JavaScript, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 138 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js, JavaScript, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 13, 101 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js, JavaScript, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 14, 98 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js, JavaScript, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 3, 46 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js, JavaScript, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 12, 159 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js, JavaScript, 0, 269, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 19, 318 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js, JavaScript, 0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 19, 168 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js, JavaScript, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 14, 121 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js, JavaScript, 0, 178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 40, 236 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js, JavaScript, 0, 524, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 69, 612 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js, JavaScript, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 20, 100 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js, JavaScript, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 51, 240 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js, JavaScript, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2, 51 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js, JavaScript, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 2, 38 +solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js, JavaScript, 0, 311, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 11, 340 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js, JavaScript, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 4, 140 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js, JavaScript, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 22, 230 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 32 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js, JavaScript, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 37 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js, JavaScript, 0, 316, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 636, 67, 1019 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js, JavaScript, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 39 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js, JavaScript, 0, 311, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 328, 65, 704 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js, JavaScript, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 40 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js, JavaScript, 0, 157, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 13, 218 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js, JavaScript, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 3, 46 +solr-8.3.1/server/solr-webapp/webapp/libs/angular.js, JavaScript, 0, 10845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13211, 2038, 26094 +solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js, JavaScript, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 274 +solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js, JavaScript, 0, 1151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 8, 1195 +solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 31 +solr-8.3.1/server/solr-webapp/webapp/libs/d3.js, JavaScript, 0, 7720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 519, 1135, 9374 +solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 32 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js, JavaScript, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 2, 31 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js, JavaScript, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 1, 30 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 3, 31 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js, JavaScript, 0, 3222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 85, 3535 +solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js, JavaScript, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 13, 102 +solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html, HTML, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 10, 47 +solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html, HTML, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 26, 129 +solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html, HTML, 0, 0, 0, 0, 0, 0, 263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 24, 303 +solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html, HTML, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 4, 50 +solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html, HTML, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 22, 86 +solr-8.3.1/server/solr-webapp/webapp/partials/collections.html, HTML, 0, 0, 0, 0, 0, 0, 301, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 79, 396 +solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html, HTML, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 66, 207 +solr-8.3.1/server/solr-webapp/webapp/partials/cores.html, HTML, 0, 0, 0, 0, 0, 0, 142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 67, 225 +solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html, HTML, 0, 0, 0, 0, 0, 0, 142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 52, 210 +solr-8.3.1/server/solr-webapp/webapp/partials/documents.html, HTML, 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 9, 112 +solr-8.3.1/server/solr-webapp/webapp/partials/files.html, HTML, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 15, 48 +solr-8.3.1/server/solr-webapp/webapp/partials/index.html, HTML, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 85, 262 +solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html, HTML, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2, 28 +solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html, HTML, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 6, 57 +solr-8.3.1/server/solr-webapp/webapp/partials/logging.html, HTML, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2, 58 +solr-8.3.1/server/solr-webapp/webapp/partials/login.html, HTML, 0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 11, 161 +solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html, HTML, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 8, 73 +solr-8.3.1/server/solr-webapp/webapp/partials/query.html, HTML, 0, 0, 0, 0, 0, 0, 270, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 84, 370 +solr-8.3.1/server/solr-webapp/webapp/partials/replication.html, HTML, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 71, 240 +solr-8.3.1/server/solr-webapp/webapp/partials/schema.html, HTML, 0, 0, 0, 0, 0, 0, 336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 104, 456 +solr-8.3.1/server/solr-webapp/webapp/partials/segments.html, HTML, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 14, 100 +solr-8.3.1/server/solr-webapp/webapp/partials/stream.html, HTML, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 9, 65 +solr-8.3.1/server/solr-webapp/webapp/partials/threads.html, HTML, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 14, 66 +solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html, HTML, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 3, 24 +solr-8.3.1/server/solr/configsets/_default/conf/params.json, JSON, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 21 +solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296, 0, 0, 0, 0, 976, 98, 1370 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json, JSON, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json, JSON, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 39 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json, JSON, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 12 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json, JSON, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 12 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 410, 0, 0, 0, 0, 1097, 124, 1631 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 9, 6, 49 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js, JavaScript, 0, 620, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 76, 764 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 0, 0, 0, 0, 0, 0, 0, 0, 47, 232 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/server/solr/dash/conf/params.json, JSON, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 +solr-8.3.1/server/solr/dash/conf/schema.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 4, 7, 62 +solr-8.3.1/server/solr/dash/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 270, 0, 0, 0, 0, 962, 97, 1329 +solr-8.3.1/server/solr/dash/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, 7 +solr-8.3.1/server/solr/solr.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 25, 11, 57 +solr-8.3.1/server/solr/zoo.cfg, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 25, 4, 32 +solr-8.3.1/server/tmp/start_3204295554151338130.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_5812170489311981381.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_6476327636763392575.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_7329004517204835686.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_9067375725008958788.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +src/Utils.ts, TypeScript, 0, 0, 0, 0, 0, 441, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 81, 545 +src/client/ClientRecommender.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 1, 2, 12 +src/client/ClientRecommender.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 320, 0, 0, 0, 0, 0, 0, 61, 44, 425 +src/client/DocServer.ts, TypeScript, 0, 0, 0, 0, 0, 279, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 65, 480 +src/client/Network.ts, TypeScript, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 39 +src/client/apis/GoogleAuthenticationManager.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 3, 19 +src/client/apis/GoogleAuthenticationManager.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, 2, 11, 128 +src/client/apis/IBM_Recommender.ts, TypeScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 7, 40 +src/client/apis/google_docs/GoogleApiClientUtils.ts, TypeScript, 0, 0, 0, 0, 0, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 27, 261 +src/client/apis/google_docs/GooglePhotosClientUtils.ts, TypeScript, 0, 0, 0, 0, 0, 318, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 364 +src/client/apis/youtube/YoutubeBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 0, 0, 0, 0, 0, 5, 16, 126 +src/client/apis/youtube/YoutubeBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 274, 0, 0, 0, 0, 0, 0, 48, 40, 362 +src/client/cognitive_services/CognitiveServices.ts, TypeScript, 0, 0, 0, 0, 0, 342, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 57, 409 +src/client/documents/DocumentTypes.ts, TypeScript, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 37 +src/client/documents/Documents.ts, TypeScript, 0, 0, 0, 0, 0, 807, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 141, 87, 1035 +src/client/goldenLayout.d.ts, TypeScript, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3 +src/client/goldenLayout.js, JavaScript, 0, 3084, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1571, 720, 5375 +src/client/util/DictationManager.ts, TypeScript, 0, 0, 0, 0, 0, 312, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 51, 388 +src/client/util/DocumentManager.ts, TypeScript, 0, 0, 0, 0, 0, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 21, 244 +src/client/util/DragManager.ts, TypeScript, 0, 0, 0, 0, 0, 504, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 35, 550 +src/client/util/DropConverter.ts, TypeScript, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 2, 78 +src/client/util/History.ts, TypeScript, 0, 0, 0, 0, 0, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 27, 206 +src/client/util/Import & Export/DirectoryImportBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6 +src/client/util/Import & Export/DirectoryImportBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 393, 0, 0, 0, 0, 0, 0, 0, 31, 424 +src/client/util/Import & Export/ImageUtils.ts, TypeScript, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 39 +src/client/util/Import & Export/ImportMetadataEntry.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 0, 0, 0, 0, 0, 0, 0, 17, 149 +src/client/util/InteractionUtils.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 112, 21, 255 +src/client/util/KeyCodes.ts, TypeScript, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 136 +src/client/util/LinkManager.ts, TypeScript, 0, 0, 0, 0, 0, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 23, 214 +src/client/util/ProsemirrorCopy/prompt.js, JavaScript, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 22, 180 +src/client/util/Scripting.ts, TypeScript, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 30, 292 +src/client/util/ScrollBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 2, 21 +src/client/util/SearchUtil.ts, TypeScript, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 18, 145 +src/client/util/SelectionManager.ts, TypeScript, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 15, 89 +src/client/util/SerializationHelper.ts, TypeScript, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 143 +src/client/util/SettingsManager.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 25, 136 +src/client/util/SettingsManager.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 17, 131 +src/client/util/SharingManager.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 18, 140 +src/client/util/SharingManager.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 273, 0, 0, 0, 0, 0, 0, 0, 25, 298 +src/client/util/Transform.ts, TypeScript, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 99 +src/client/util/TypedEvent.ts, TypeScript, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 40 +src/client/util/UndoManager.ts, TypeScript, 0, 0, 0, 0, 0, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 27, 195 +src/client/util/clamp.js, JavaScript, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 15 +src/client/util/convertToCSSPTValue.js, JavaScript, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 43 +src/client/util/jsx-decl.d.ts, TypeScript, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2 +src/client/util/request-image-size.js, JavaScript, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 14, 75 +src/client/util/toCSSLineSpacing.js, JavaScript, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 14, 64 +src/client/views/AntimodeMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 1, 6, 42 +src/client/views/AntimodeMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121, 0, 0, 0, 0, 0, 0, 14, 22, 157 +src/client/views/ContextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 0, 0, 0, 0, 0, 17, 14, 161 +src/client/views/ContextMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 258, 0, 0, 0, 0, 0, 0, 3, 33, 294 +src/client/views/ContextMenuItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 10, 117 +src/client/views/DictationOverlay.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 7, 71 +src/client/views/DocComponent.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 20, 15, 122 +src/client/views/DocumentButtonBar.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 16, 105 +src/client/views/DocumentButtonBar.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 284, 0, 0, 0, 0, 0, 0, 4, 27, 315 +src/client/views/DocumentDecorations.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 319, 0, 0, 0, 0, 0, 0, 46, 365 +src/client/views/DocumentDecorations.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 479, 0, 0, 0, 0, 0, 0, 2, 26, 507 +src/client/views/EditableView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 3, 25 +src/client/views/EditableView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 19, 16, 184 +src/client/views/GestureOverlay.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 8, 64 +src/client/views/GestureOverlay.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 711, 0, 0, 0, 0, 0, 0, 45, 67, 823 +src/client/views/GlobalKeyHandler.ts, TypeScript, 0, 0, 0, 0, 0, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 27, 269 +src/client/views/InkingControl.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 4, 0, 131 +src/client/views/InkingControl.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 3, 10, 91 +src/client/views/InkingStroke.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7 +src/client/views/InkingStroke.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 5, 68 +src/client/views/KeyphraseQueryView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 1, 8 +src/client/views/KeyphraseQueryView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 2, 3, 35 +src/client/views/Main.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 8, 10, 69 +src/client/views/Main.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 2, 25 +src/client/views/MainView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 1, 21, 160 +src/client/views/MainView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 553, 0, 0, 0, 0, 0, 0, 13, 36, 602 +src/client/views/MainViewModal.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 1, 25 +src/client/views/MainViewModal.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 5, 44 +src/client/views/MainViewNotifs.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 1, 18 +src/client/views/MainViewNotifs.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 4, 33 +src/client/views/MetadataEntryMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 0, 0, 0, 0, 0, 0, 14, 93 +src/client/views/MetadataEntryMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 0, 0, 0, 0, 0, 16, 223 +src/client/views/OCRUtils.ts, TypeScript, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 8 +src/client/views/OverlayView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 6, 47 +src/client/views/OverlayView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, 0, 0, 0, 0, 4, 19, 217 +src/client/views/Palette.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 4, 30 +src/client/views/Palette.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 5, 70 +src/client/views/PreviewCursor.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 1, 10 +src/client/views/PreviewCursor.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, 8, 9, 132 +src/client/views/RecommendationsBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 10, 8, 70 +src/client/views/RecommendationsBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 0, 0, 0, 0, 0, 0, 67, 17, 200 +src/client/views/ScriptBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 2, 17 +src/client/views/ScriptBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 2, 12, 126 +src/client/views/ScriptingRepl.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 9, 51 +src/client/views/ScriptingRepl.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 1, 24, 245 +src/client/views/SearchDocBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 359, 0, 0, 0, 0, 0, 0, 17, 55, 431 +src/client/views/TemplateMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 5, 51 +src/client/views/TemplateMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 167, 0, 0, 0, 0, 0, 0, 1, 14, 182 +src/client/views/Templates.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 8, 42 +src/client/views/TouchScrollableMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 7, 59 +src/client/views/Touchable.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 0, 0, 0, 0, 0, 0, 28, 39, 239 +src/client/views/_nodeModuleOverrides.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 9, 4, 22 +src/client/views/animationtimeline/Keyframe.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, 0, 0, 5, 17, 105 +src/client/views/animationtimeline/Keyframe.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 468, 0, 0, 0, 0, 0, 0, 50, 42, 560 +src/client/views/animationtimeline/Timeline.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 264, 0, 0, 0, 0, 0, 14, 44, 322 +src/client/views/animationtimeline/Timeline.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 467, 0, 0, 0, 0, 0, 0, 97, 58, 622 +src/client/views/animationtimeline/TimelineMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 2, 17, 94 +src/client/views/animationtimeline/TimelineMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 10, 78 +src/client/views/animationtimeline/TimelineOverview.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 6, 12, 107 +src/client/views/animationtimeline/TimelineOverview.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 155, 0, 0, 0, 0, 0, 0, 0, 27, 182 +src/client/views/animationtimeline/Track.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 2, 15 +src/client/views/animationtimeline/Track.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 284, 0, 0, 0, 0, 0, 0, 63, 33, 380 +src/client/views/collections/CollectionCarouselView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 38 +src/client/views/collections/CollectionCarouselView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 0, 1, 10, 121 +src/client/views/collections/CollectionDockingView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 391, 0, 0, 0, 0, 0, 7, 60, 458 +src/client/views/collections/CollectionDockingView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 707, 0, 0, 0, 0, 0, 0, 61, 65, 833 +src/client/views/collections/CollectionLinearView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 10, 78 +src/client/views/collections/CollectionLinearView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 1, 9, 135 +src/client/views/collections/CollectionMapView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 3, 30 +src/client/views/collections/CollectionMapView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 235, 0, 0, 0, 0, 0, 0, 10, 18, 263 +src/client/views/collections/CollectionMasonryViewFieldRow.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 308, 0, 0, 0, 0, 0, 0, 0, 24, 332 +src/client/views/collections/CollectionPileView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 9 +src/client/views/collections/CollectionPileView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 5, 11, 128 +src/client/views/collections/CollectionSchemaCells.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 274, 0, 0, 0, 0, 0, 0, 17, 38, 329 +src/client/views/collections/CollectionSchemaHeaders.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 318, 0, 0, 0, 0, 0, 0, 5, 41, 364 +src/client/views/collections/CollectionSchemaMovableTableHOC.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 27, 243 +src/client/views/collections/CollectionSchemaView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 406, 0, 0, 0, 0, 0, 4, 88, 498 +src/client/views/collections/CollectionSchemaView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 651, 0, 0, 0, 0, 0, 0, 20, 82, 753 +src/client/views/collections/CollectionStackingView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 353, 0, 0, 0, 0, 0, 1, 50, 404 +src/client/views/collections/CollectionStackingView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 430, 0, 0, 0, 0, 0, 0, 5, 25, 460 +src/client/views/collections/CollectionStackingViewFieldColumn.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367, 0, 0, 0, 0, 0, 0, 0, 27, 394 +src/client/views/collections/CollectionStaffView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 1, 13 +src/client/views/collections/CollectionStaffView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 7, 53 +src/client/views/collections/CollectionSubView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 390, 0, 0, 0, 0, 0, 0, 7, 25, 422 +src/client/views/collections/CollectionTimeView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 13, 93 +src/client/views/collections/CollectionTimeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 0, 0, 15, 192 +src/client/views/collections/CollectionTreeView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 4, 23, 152 +src/client/views/collections/CollectionTreeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 801, 0, 0, 0, 0, 0, 0, 19, 40, 860 +src/client/views/collections/CollectionView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 8, 78 +src/client/views/collections/CollectionView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 457, 0, 0, 0, 0, 0, 0, 13, 34, 504 +src/client/views/collections/CollectionViewChromes.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 308, 0, 0, 0, 0, 0, 4, 45, 357 +src/client/views/collections/CollectionViewChromes.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 393, 0, 0, 0, 0, 0, 0, 67, 47, 507 +src/client/views/collections/KeyRestrictionRow.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 2, 4, 55 +src/client/views/collections/ParentDocumentSelector.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 2, 56 +src/client/views/collections/ParentDocumentSelector.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 11, 131 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 422, 0, 0, 0, 0, 0, 0, 10, 27, 459 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 1, 20 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 0, 5, 4, 119 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 11 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 2, 46 +src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 1, 3, 24 +src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 12, 79 +src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 9, 17, 121 +src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1186, 0, 0, 0, 0, 0, 0, 47, 97, 1330 +src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 5, 57 +src/client/views/collections/collectionFreeForm/MarqueeView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 2, 32 +src/client/views/collections/collectionFreeForm/MarqueeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 484, 0, 0, 0, 0, 0, 0, 105, 33, 622 +src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 6, 34 +src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 0, 0, 0, 72, 23, 297 +src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 6, 35 +src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 0, 0, 0, 0, 0, 0, 72, 22, 298 +src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 9, 103 +src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 5, 56 +src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 5, 56 +src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 9, 101 +src/client/views/globalCssVariables.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 10, 3, 43 +src/client/views/globalCssVariables.scss.d.ts, TypeScript, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 11 +src/client/views/linking/LinkEditor.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 1, 25, 150 +src/client/views/linking/LinkEditor.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 0, 7, 50, 309 +src/client/views/linking/LinkMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 13, 53 +src/client/views/linking/LinkMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 1, 10, 76 +src/client/views/linking/LinkMenuGroup.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 10, 94 +src/client/views/linking/LinkMenuItem.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 1, 11, 87 +src/client/views/linking/LinkMenuItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 19, 126 +src/client/views/nodes/AudioBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 146, 0, 0, 0, 0, 0, 0, 0, 146 +src/client/views/nodes/AudioBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 261, 0, 0, 0, 0, 0, 0, 2, 24, 287 +src/client/views/nodes/CollectionFreeFormDocumentView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8 +src/client/views/nodes/CollectionFreeFormDocumentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 7, 131 +src/client/views/nodes/ColorBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 23 +src/client/views/nodes/ColorBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 4, 32 +src/client/views/nodes/ContentFittingDocumentView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 4, 24 +src/client/views/nodes/ContentFittingDocumentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 7, 124 +src/client/views/nodes/DocumentBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14 +src/client/views/nodes/DocumentBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 0, 0, 0, 0, 0, 0, 0, 4, 158 +src/client/views/nodes/DocumentContentsView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 10, 17, 210 +src/client/views/nodes/DocumentIcon.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 5, 65 +src/client/views/nodes/DocumentView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 1, 15, 126 +src/client/views/nodes/DocumentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1041, 0, 0, 0, 0, 0, 0, 54, 94, 1189 +src/client/views/nodes/FaceRectangle.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 4, 29 +src/client/views/nodes/FaceRectangles.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 5, 46 +src/client/views/nodes/FieldTextBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 3, 15 +src/client/views/nodes/FieldView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 43, 4, 136 +src/client/views/nodes/FontIconBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 2, 27 +src/client/views/nodes/FontIconBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 5, 63 +src/client/views/nodes/ImageBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 17, 152 +src/client/views/nodes/ImageBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 430, 0, 0, 0, 0, 0, 0, 10, 35, 475 +src/client/views/nodes/KeyValueBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 3, 123 +src/client/views/nodes/KeyValueBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 1, 26, 271 +src/client/views/nodes/KeyValuePair.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 1, 4, 60 +src/client/views/nodes/KeyValuePair.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 2, 8, 135 +src/client/views/nodes/LabelBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 4, 35 +src/client/views/nodes/LabelBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 1, 9, 96 +src/client/views/nodes/LinkAnchorBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 2, 29 +src/client/views/nodes/LinkAnchorBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 141, 0, 0, 0, 0, 0, 0, 0, 9, 150 +src/client/views/nodes/LinkBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3 +src/client/views/nodes/LinkBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 3, 36 +src/client/views/nodes/PDFBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 19, 219 +src/client/views/nodes/PDFBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 0, 0, 0, 0, 0, 0, 0, 19, 265 +src/client/views/nodes/PresBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 1, 53 +src/client/views/nodes/PresBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 268, 0, 0, 0, 0, 0, 0, 31, 32, 331 +src/client/views/nodes/QueryBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 5 +src/client/views/nodes/QueryBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 4, 41 +src/client/views/nodes/RadialMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 3, 7, 70 +src/client/views/nodes/RadialMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 0, 0, 0, 0, 0, 0, 26, 36, 236 +src/client/views/nodes/RadialMenuItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 16, 117 +src/client/views/nodes/ScreenshotBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 6, 5, 51 +src/client/views/nodes/ScreenshotBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 0, 0, 0, 0, 0, 0, 2, 18, 194 +src/client/views/nodes/ScriptingBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 3, 36 +src/client/views/nodes/ScriptingBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 12, 99 +src/client/views/nodes/SliderBox-components.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 12, 25, 257 +src/client/views/nodes/SliderBox-tooltip.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 3, 33 +src/client/views/nodes/SliderBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7 +src/client/views/nodes/SliderBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 8, 125 +src/client/views/nodes/VideoBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 3, 6, 74 +src/client/views/nodes/VideoBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 343, 0, 0, 0, 0, 0, 0, 2, 34, 379 +src/client/views/nodes/WebBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, 0, 0, 18, 127 +src/client/views/nodes/WebBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 345, 0, 0, 0, 0, 0, 0, 15, 35, 395 +src/client/views/nodes/formattedText/DashDocCommentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 13, 95 +src/client/views/nodes/formattedText/DashDocView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 9, 37, 269 +src/client/views/nodes/formattedText/DashFieldView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 2, 36 +src/client/views/nodes/formattedText/DashFieldView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 0, 0, 0, 13, 20, 211 +src/client/views/nodes/formattedText/FootnoteView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 0, 0, 0, 0, 0, 0, 13, 19, 163 +src/client/views/nodes/formattedText/FormattedTextBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 11, 34, 265 +src/client/views/nodes/formattedText/FormattedTextBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1169, 0, 0, 0, 0, 0, 0, 68, 93, 1330 +src/client/views/nodes/formattedText/FormattedTextBoxComment.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 33 +src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 0, 0, 0, 0, 0, 0, 11, 8, 237 +src/client/views/nodes/formattedText/ImageResizeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 25, 138 +src/client/views/nodes/formattedText/ParagraphNodeSpec.ts, TypeScript, 0, 0, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 26, 143 +src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts, TypeScript, 0, 0, 0, 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 242 +src/client/views/nodes/formattedText/RichTextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 2, 19, 121 +src/client/views/nodes/formattedText/RichTextMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 754, 0, 0, 0, 0, 0, 0, 12, 109, 875 +src/client/views/nodes/formattedText/RichTextRules.ts, TypeScript, 0, 0, 0, 0, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 5, 320 +src/client/views/nodes/formattedText/RichTextSchema.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 465, 0, 0, 0, 0, 0, 0, 33, 39, 537 +src/client/views/nodes/formattedText/SummaryView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 14, 81 +src/client/views/nodes/formattedText/TooltipTextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 306, 0, 0, 0, 0, 0, 6, 61, 373 +src/client/views/nodes/formattedText/marks_rts.ts, TypeScript, 0, 0, 0, 0, 0, 259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 23, 297 +src/client/views/nodes/formattedText/nodes_rts.ts, TypeScript, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 264 +src/client/views/nodes/formattedText/prosemirrorPatches.js, JavaScript, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 9, 139 +src/client/views/nodes/formattedText/schema_rts.ts, TypeScript, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 26 +src/client/views/pdf/Annotation.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6 +src/client/views/pdf/Annotation.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 16, 130 +src/client/views/pdf/PDFMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6 +src/client/views/pdf/PDFMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 21, 123 +src/client/views/pdf/PDFViewer.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0, 0, 4, 10, 88 +src/client/views/pdf/PDFViewer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 662, 0, 0, 0, 0, 0, 0, 23, 46, 731 +src/client/views/presentationview/PresElementBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 0, 0, 0, 0, 0, 0, 10, 103 +src/client/views/presentationview/PresElementBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 179, 0, 0, 0, 0, 0, 0, 31, 14, 224 +src/client/views/search/CheckBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 1, 8, 59 +src/client/views/search/CheckBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 74, 15, 131 +src/client/views/search/CollectionFilters.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 3, 20 +src/client/views/search/CollectionFilters.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 14, 83 +src/client/views/search/FieldFilters.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 1, 1, 12 +src/client/views/search/FieldFilters.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 7, 41 +src/client/views/search/FilterBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 25, 178 +src/client/views/search/FilterBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 354, 0, 0, 0, 0, 0, 0, 24, 54, 432 +src/client/views/search/IconBar.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 1, 10 +src/client/views/search/IconBar.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 1, 17, 87 +src/client/views/search/IconButton.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 1, 6, 53 +src/client/views/search/IconButton.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0, 2, 19, 191 +src/client/views/search/NaviconButton.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 0, 11, 69 +src/client/views/search/NaviconButton.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 5, 37 +src/client/views/search/SearchBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 0, 0, 82, 51, 336 +src/client/views/search/SearchBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 530, 0, 0, 0, 0, 0, 0, 47, 94, 671 +src/client/views/search/SearchItem.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 25, 163 +src/client/views/search/SearchItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 0, 0, 2, 29, 303 +src/client/views/search/SelectorContextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 1, 3, 16 +src/client/views/search/ToggleBar.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 2, 4, 41 +src/client/views/search/ToggleBar.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 9, 86 +src/client/views/webcam/DashWebRTCVideo.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 4, 9, 83 +src/client/views/webcam/DashWebRTCVideo.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 6, 16, 89 +src/client/views/webcam/WebCamLogic.js, JavaScript, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 51, 292 +src/debug/Repl.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 7, 66 +src/debug/Test.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 2, 14 +src/debug/Viewer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 19, 192 +src/extensions/ArrayExtensions.ts, TypeScript, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 37 +src/extensions/General/Extensions.ts, TypeScript, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 9 +src/extensions/General/ExtensionsTypings.ts, TypeScript, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 8 +src/extensions/StringExtensions.ts, TypeScript, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 17 +src/mobile/ImageUpload.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 4, 34 +src/mobile/ImageUpload.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 41, 12, 131 +src/mobile/InkControls.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +src/mobile/MobileInkOverlay.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 1, 5, 39 +src/mobile/MobileInkOverlay.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 162, 0, 0, 0, 0, 0, 0, 3, 26, 191 +src/mobile/MobileInterface.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 2, 19 +src/mobile/MobileInterface.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 297, 0, 0, 0, 0, 0, 0, 15, 32, 344 +src/new_fields/CursorField.ts, TypeScript, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 66 +src/new_fields/DateField.ts, TypeScript, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 37 +src/new_fields/Doc.ts, TypeScript, 0, 0, 0, 0, 0, 897, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 76, 1058 +src/new_fields/FieldSymbols.ts, TypeScript, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 13 +src/new_fields/HtmlField.ts, TypeScript, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 27 +src/new_fields/IconField.ts, TypeScript, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 27 +src/new_fields/InkField.ts, TypeScript, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 51 +src/new_fields/List.ts, TypeScript, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 20, 302 +src/new_fields/ListSpec.ts, TypeScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +src/new_fields/ObjectField.ts, TypeScript, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 20 +src/new_fields/PresField.ts, TypeScript, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 6 +src/new_fields/Proxy.ts, TypeScript, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 14, 111 +src/new_fields/RefField.ts, TypeScript, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 22 +src/new_fields/RichTextField.ts, TypeScript, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 41 +src/new_fields/RichTextUtils.ts, TypeScript, 0, 0, 0, 0, 0, 455, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 56, 519 +src/new_fields/Schema.ts, TypeScript, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 120 +src/new_fields/SchemaHeaderField.ts, TypeScript, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 14, 122 +src/new_fields/ScriptField.ts, TypeScript, 0, 0, 0, 0, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 177 +src/new_fields/Types.ts, TypeScript, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 17, 108 +src/new_fields/URLField.ts, TypeScript, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 54 +src/new_fields/documentSchemas.ts, TypeScript, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 93 +src/new_fields/util.ts, TypeScript, 0, 0, 0, 0, 0, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 15, 194 +src/pen-gestures/GestureUtils.ts, TypeScript, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 46 +src/pen-gestures/ndollar.ts, TypeScript, 0, 0, 0, 0, 0, 356, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 22, 550 +src/scraping/acm/.gitignore, Ignore, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2 +src/scraping/acm/debug.log, log, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 1, 39 +src/scraping/acm/index.js, JavaScript, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 13, 280 +src/scraping/acm/package.json, JSON, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 18 +src/scraping/buxton/.idea/buxton.iml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 8 +src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6 +src/scraping/buxton/.idea/misc.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 4 +src/scraping/buxton/.idea/modules.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 8 +src/scraping/buxton/.idea/vcs.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6 +src/scraping/buxton/.idea/workspace.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 173 +src/scraping/buxton/final/BuxtonImporter.ts, TypeScript, 0, 0, 0, 0, 0, 228, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 142, 26, 396 +src/scraping/buxton/jsonifier.py, Python, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 48, 232 +src/scraping/buxton/narratives.py, Python, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 9, 39 +src/scraping/buxton/narratives/chord_keyboards.json, JSON, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 +src/scraping/buxton/scraper.py, Python, 0, 0, 0, 0, 350, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 78, 433 +src/server/ActionUtilities.ts, TypeScript, 0, 0, 0, 0, 0, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 23, 160 +src/server/ApiManagers/ApiManager.ts, TypeScript, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 11 +src/server/ApiManagers/DeleteManager.ts, TypeScript, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 82 +src/server/ApiManagers/DownloadManager.ts, TypeScript, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 16, 269 +src/server/ApiManagers/GeneralGoogleManager.ts, TypeScript, 0, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 61 +src/server/ApiManagers/GooglePhotosManager.ts, TypeScript, 0, 0, 0, 0, 0, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 22, 331 +src/server/ApiManagers/PDFManager.ts, TypeScript, 0, 0, 0, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 115 +src/server/ApiManagers/SearchManager.ts, TypeScript, 0, 0, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 215 +src/server/ApiManagers/SessionManager.ts, TypeScript, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 67 +src/server/ApiManagers/UploadManager.ts, TypeScript, 0, 0, 0, 0, 0, 226, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 20, 247 +src/server/ApiManagers/UserManager.ts, TypeScript, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 21, 126 +src/server/ApiManagers/UtilManager.ts, TypeScript, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 10, 74 +src/server/Client.ts, TypeScript, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 11 +src/server/DashSession/DashSessionAgent.ts, TypeScript, 0, 0, 0, 0, 0, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 23, 230 +src/server/DashSession/Session/agents/applied_session_agent.ts, TypeScript, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 9, 58 +src/server/DashSession/Session/agents/monitor.ts, TypeScript, 0, 0, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 26, 298 +src/server/DashSession/Session/agents/process_message_router.ts, TypeScript, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 41 +src/server/DashSession/Session/agents/promisified_ipc_manager.ts, TypeScript, 0, 0, 0, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 15, 173 +src/server/DashSession/Session/agents/server_worker.ts, TypeScript, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 15, 160 +src/server/DashSession/Session/utilities/repl.ts, TypeScript, 0, 0, 0, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 128 +src/server/DashSession/Session/utilities/session_config.ts, TypeScript, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 129 +src/server/DashSession/Session/utilities/utilities.ts, TypeScript, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 37 +src/server/DashUploadUtils.ts, TypeScript, 0, 0, 0, 0, 0, 285, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 30, 367 +src/server/GarbageCollector.ts, TypeScript, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 11, 151 +src/server/IDatabase.ts, TypeScript, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 25 +src/server/MemoryDatabase.ts, TypeScript, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 101 +src/server/Message.ts, TypeScript, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 104 +src/server/PdfTypes.ts, TypeScript, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 21 +src/server/ProcessFactory.ts, TypeScript, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 44 +src/server/Recommender.ts, TypeScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 18, 138 +src/server/RouteManager.ts, TypeScript, 0, 0, 0, 0, 0, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 19, 210 +src/server/RouteSubscriber.ts, TypeScript, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 26 +src/server/Search.ts, TypeScript, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 8, 81 +src/server/SharedMediaTypes.ts, TypeScript, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 51 +src/server/Websocket/Websocket.ts, TypeScript, 0, 0, 0, 0, 0, 263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 46, 314 +src/server/apis/google/GoogleApiServerUtils.ts, TypeScript, 0, 0, 0, 0, 0, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 25, 365 +src/server/apis/google/SharedTypes.ts, TypeScript, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 21 +src/server/apis/youtube/youtubeApiSample.d.ts, TypeScript, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 +src/server/apis/youtube/youtubeApiSample.js, JavaScript, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 14, 179 +src/server/authentication/config/passport.ts, TypeScript, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 29 +src/server/authentication/controllers/user_controller.ts, TypeScript, 0, 0, 0, 0, 0, 218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 268 +src/server/authentication/models/current_user_utils.ts, TypeScript, 0, 0, 0, 0, 0, 586, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 57, 673 +src/server/authentication/models/user_model.ts, TypeScript, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 86 +src/server/credentials/CredentialsLoader.ts, TypeScript, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 30 +src/server/credentials/google_project_credentials.json, JSON, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11 +src/server/database.ts, TypeScript, 0, 0, 0, 0, 0, 312, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 350 +src/server/downsize.ts, TypeScript, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 40 +src/server/index.ts, TypeScript, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 16, 159 +src/server/remapUrl.ts, TypeScript, 0, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 63 +src/server/server_Initialization.ts, TypeScript, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 24, 168 +src/server/slides.json, JSON, 0, 0, 10820, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10820 +src/server/updateProtos.ts, TypeScript, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14 +src/typings/index.d.ts, TypeScript, 0, 0, 0, 0, 0, 219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 38, 329 +test/test.ts, TypeScript, 0, 0, 0, 0, 0, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 161 +tsconfig.json, JSON, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 26 +tslint.json, JSON, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 1, 63 +views/forgot.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 22 +views/layout.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 14 +views/login.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 26 +views/reset.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 22 +views/signup.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 27 +views/stylesheets/authentication.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 0, 0, 0, 0, 0, 0, 0, 4, 34, 223 +views/user_activity.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 19 +webpack.config.js, JavaScript, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 121 +Total, -, 6, 89717, 50401, 1893, 632, 15246, 2883, 119, 538, 5982, 30250, 7726, 17257, 161, 2, 38, 2060, 32987, 15880, 273778 \ No newline at end of file diff --git a/.VSCodeCounter/results.md b/.VSCodeCounter/results.md new file mode 100644 index 000000000..3265d800b --- /dev/null +++ b/.VSCodeCounter/results.md @@ -0,0 +1,164 @@ +# Summary + +Date : 2020-04-30 14:40:17 + +Directory /Users/bcz/Documents/GitHub/Dash-Web + +Total : 646 files, 224911 codes, 32987 comments, 15880 blanks, all 273778 lines + +[details](details.md) + +## Languages +| language | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | +| JavaScript | 69 | 89,717 | 18,520 | 5,866 | 114,103 | +| JSON | 20 | 50,401 | 37 | 14 | 50,452 | +| TypeScript React | 140 | 30,250 | 1,878 | 3,154 | 35,282 | +| XML | 73 | 17,257 | 8,281 | 992 | 26,530 | +| TypeScript | 115 | 15,246 | 2,095 | 1,958 | 19,299 | +| SCSS | 104 | 7,726 | 255 | 1,136 | 9,117 | +| CSS | 30 | 5,982 | 549 | 1,095 | 7,626 | +| HTML | 34 | 2,883 | 431 | 848 | 4,162 | +| XSL | 20 | 2,060 | 416 | 232 | 2,708 | +| Batch | 5 | 1,893 | 184 | 273 | 2,350 | +| Python | 5 | 632 | 49 | 148 | 829 | +| Shell Script | 6 | 538 | 248 | 120 | 906 | +| Properties | 16 | 161 | 43 | 30 | 234 | +| Pug | 6 | 119 | 1 | 10 | 130 | +| log | 1 | 38 | 0 | 1 | 39 | +| Markdown | 1 | 6 | 0 | 3 | 9 | +| Ignore | 1 | 2 | 0 | 0 | 2 | + +## Directories +| path | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | +| . | 646 | 224,911 | 32,987 | 15,880 | 273,778 | +| build | 1 | 9 | 0 | 3 | 12 | +| deploy | 8 | 55,744 | 174 | 704 | 56,622 | +| deploy/assets | 2 | 55,677 | 174 | 686 | 56,537 | +| deploy/debug | 3 | 32 | 0 | 9 | 41 | +| deploy/mobile | 2 | 22 | 0 | 6 | 28 | +| solr-8.3.1 | 236 | 80,866 | 26,654 | 7,861 | 115,381 | +| solr-8.3.1/bin | 5 | 2,118 | 392 | 311 | 2,821 | +| solr-8.3.1/contrib | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter/bin | 1 | 82 | 0 | 26 | 108 | +| solr-8.3.1/contrib/prometheus-exporter/conf | 2 | 6,199 | 63 | 11 | 6,273 | +| solr-8.3.1/docs | 2 | 59 | 0 | 2 | 61 | +| solr-8.3.1/docs/images | 1 | 39 | 0 | 1 | 40 | +| solr-8.3.1/example | 82 | 32,009 | 5,063 | 942 | 38,014 | +| solr-8.3.1/example/example-DIH | 49 | 2,848 | 3,605 | 603 | 7,056 | +| solr-8.3.1/example/example-DIH/solr | 49 | 2,848 | 3,605 | 603 | 7,056 | +| solr-8.3.1/example/example-DIH/solr/atom | 3 | 41 | 43 | 19 | 103 | +| solr-8.3.1/example/example-DIH/solr/atom/conf | 2 | 41 | 43 | 17 | 101 | +| solr-8.3.1/example/example-DIH/solr/db | 14 | 935 | 1,167 | 191 | 2,293 | +| solr-8.3.1/example/example-DIH/solr/db/conf | 13 | 935 | 1,167 | 189 | 2,291 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/mail | 14 | 919 | 1,171 | 189 | 2,279 | +| solr-8.3.1/example/example-DIH/solr/mail/conf | 13 | 919 | 1,171 | 187 | 2,277 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/solr | 14 | 917 | 1,183 | 187 | 2,287 | +| solr-8.3.1/example/example-DIH/solr/solr/conf | 13 | 917 | 1,183 | 185 | 2,285 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/tika | 3 | 34 | 41 | 16 | 91 | +| solr-8.3.1/example/example-DIH/solr/tika/conf | 2 | 34 | 41 | 14 | 89 | +| solr-8.3.1/example/exampledocs | 17 | 513 | 281 | 75 | 869 | +| solr-8.3.1/example/files | 13 | 1,298 | 1,153 | 250 | 2,701 | +| solr-8.3.1/example/files/browse-resources | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/example/files/browse-resources/velocity | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/example/files/conf | 10 | 1,190 | 1,147 | 241 | 2,578 | +| solr-8.3.1/example/files/conf/velocity | 5 | 730 | 99 | 108 | 937 | +| solr-8.3.1/example/files/conf/velocity/js | 3 | 730 | 99 | 104 | 933 | +| solr-8.3.1/example/films | 3 | 27,350 | 24 | 14 | 27,388 | +| solr-8.3.1/server | 144 | 40,399 | 21,136 | 6,569 | 68,104 | +| solr-8.3.1/server/contexts | 1 | 8 | 0 | 1 | 9 | +| solr-8.3.1/server/etc | 6 | 528 | 400 | 59 | 987 | +| solr-8.3.1/server/resources | 3 | 74 | 123 | 16 | 213 | +| solr-8.3.1/server/scripts | 3 | 172 | 19 | 39 | 230 | +| solr-8.3.1/server/scripts/cloud-scripts | 3 | 172 | 19 | 39 | 230 | +| solr-8.3.1/server/solr | 27 | 2,612 | 3,377 | 557 | 6,546 | +| solr-8.3.1/server/solr-webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| solr-8.3.1/server/solr-webapp/webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| solr-8.3.1/server/solr-webapp/webapp/WEB-INF | 1 | 62 | 42 | 11 | 115 | +| solr-8.3.1/server/solr-webapp/webapp/css | 26 | 5,548 | 536 | 1,005 | 7,089 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular | 26 | 5,548 | 536 | 1,005 | 7,089 | +| solr-8.3.1/server/solr-webapp/webapp/img | 1 | 39 | 0 | 1 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/js | 25 | 4,450 | 515 | 586 | 5,551 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular | 25 | 4,450 | 515 | 586 | 5,551 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers | 23 | 3,627 | 478 | 544 | 4,649 | +| solr-8.3.1/server/solr-webapp/webapp/libs | 21 | 24,087 | 15,683 | 3,464 | 43,234 | +| solr-8.3.1/server/solr-webapp/webapp/partials | 24 | 2,571 | 415 | 787 | 3,773 | +| solr-8.3.1/server/solr/configsets | 21 | 2,243 | 2,359 | 437 | 5,039 | +| solr-8.3.1/server/solr/configsets/_default | 2 | 316 | 976 | 99 | 1,391 | +| solr-8.3.1/server/solr/configsets/_default/conf | 2 | 316 | 976 | 99 | 1,391 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity | 3 | 839 | 77 | 129 | 1,045 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/server/solr/dash | 4 | 345 | 968 | 105 | 1,418 | +| solr-8.3.1/server/solr/dash/conf | 3 | 341 | 966 | 104 | 1,411 | +| solr-8.3.1/server/tmp | 5 | 45 | 10 | 5 | 60 | +| src | 384 | 68,699 | 6,117 | 7,234 | 82,050 | +| src/client | 277 | 46,644 | 4,305 | 5,743 | 56,692 | +| src/client/apis | 7 | 1,053 | 97 | 150 | 1,300 | +| src/client/apis/google_docs | 2 | 543 | 9 | 73 | 625 | +| src/client/apis/youtube | 2 | 379 | 53 | 56 | 488 | +| src/client/cognitive_services | 1 | 342 | 10 | 57 | 409 | +| src/client/documents | 2 | 839 | 143 | 90 | 1,072 | +| src/client/util | 30 | 3,931 | 351 | 515 | 4,797 | +| src/client/util/Import & Export | 4 | 566 | 0 | 52 | 618 | +| src/client/util/ProsemirrorCopy | 1 | 128 | 30 | 22 | 180 | +| src/client/views | 231 | 36,751 | 1,935 | 4,094 | 42,780 | +| src/client/views/animationtimeline | 10 | 1,966 | 237 | 262 | 2,465 | +| src/client/views/collections | 53 | 11,516 | 574 | 1,153 | 13,243 | +| src/client/views/collections/collectionFreeForm | 12 | 2,540 | 177 | 203 | 2,920 | +| src/client/views/collections/collectionMulticolumn | 8 | 751 | 144 | 85 | 980 | +| src/client/views/linking | 7 | 747 | 10 | 138 | 895 | +| src/client/views/nodes | 75 | 12,034 | 475 | 1,218 | 13,727 | +| src/client/views/nodes/formattedText | 22 | 5,353 | 250 | 592 | 6,195 | +| src/client/views/pdf | 6 | 964 | 27 | 93 | 1,084 | +| src/client/views/presentationview | 2 | 272 | 31 | 24 | 327 | +| src/client/views/search | 21 | 2,380 | 238 | 401 | 3,019 | +| src/client/views/webcam | 3 | 371 | 17 | 76 | 464 | +| src/debug | 3 | 244 | 0 | 28 | 272 | +| src/extensions | 4 | 53 | 5 | 13 | 71 | +| src/extensions/General | 2 | 14 | 0 | 3 | 17 | +| src/mobile | 7 | 617 | 60 | 82 | 759 | +| src/new_fields | 22 | 2,682 | 172 | 315 | 3,169 | +| src/pen-gestures | 2 | 397 | 172 | 27 | 596 | +| src/scraping | 15 | 1,155 | 352 | 176 | 1,683 | +| src/scraping/acm | 4 | 139 | 185 | 15 | 339 | +| src/scraping/buxton | 11 | 1,016 | 167 | 161 | 1,344 | +| src/scraping/buxton/.idea | 6 | 205 | 0 | 0 | 205 | +| src/scraping/buxton/.idea/inspectionProfiles | 1 | 6 | 0 | 0 | 6 | +| src/scraping/buxton/final | 1 | 228 | 142 | 26 | 396 | +| src/scraping/buxton/narratives | 1 | 39 | 0 | 0 | 39 | +| src/server | 52 | 16,247 | 956 | 731 | 17,934 | +| src/server/ApiManagers | 11 | 1,223 | 226 | 149 | 1,598 | +| src/server/DashSession | 9 | 903 | 229 | 122 | 1,254 | +| src/server/DashSession/Session | 8 | 748 | 177 | 99 | 1,024 | +| src/server/DashSession/Session/agents | 5 | 489 | 169 | 72 | 730 | +| src/server/DashSession/Session/utilities | 3 | 259 | 8 | 27 | 294 | +| src/server/Websocket | 1 | 263 | 5 | 46 | 314 | +| src/server/apis | 4 | 328 | 198 | 41 | 567 | +| src/server/apis/google | 2 | 191 | 168 | 27 | 386 | +| src/server/apis/youtube | 2 | 137 | 30 | 14 | 181 | +| src/server/authentication | 4 | 890 | 66 | 100 | 1,056 | +| src/server/authentication/config | 1 | 23 | 2 | 4 | 29 | +| src/server/authentication/controllers | 1 | 218 | 25 | 25 | 268 | +| src/server/authentication/models | 2 | 649 | 39 | 71 | 759 | +| src/server/credentials | 2 | 35 | 0 | 6 | 41 | +| src/typings | 1 | 219 | 72 | 38 | 329 | +| test | 1 | 141 | 0 | 20 | 161 | +| views | 7 | 304 | 5 | 44 | 353 | +| views/stylesheets | 1 | 185 | 4 | 34 | 223 | + +[details](details.md) \ No newline at end of file diff --git a/.VSCodeCounter/results.txt b/.VSCodeCounter/results.txt new file mode 100644 index 000000000..aaf54b147 --- /dev/null +++ b/.VSCodeCounter/results.txt @@ -0,0 +1,813 @@ +Date : 2020-04-30 14:40:16 +Directory : /Users/bcz/Documents/GitHub/Dash-Web +Total : 646 files, 224911 codes, 32987 comments, 15880 blanks, all 273778 lines + +Languages ++------------------+------------+------------+------------+------------+------------+ +| language | files | code | comment | blank | total | ++------------------+------------+------------+------------+------------+------------+ +| JavaScript | 69 | 89,717 | 18,520 | 5,866 | 114,103 | +| JSON | 20 | 50,401 | 37 | 14 | 50,452 | +| TypeScript React | 140 | 30,250 | 1,878 | 3,154 | 35,282 | +| XML | 73 | 17,257 | 8,281 | 992 | 26,530 | +| TypeScript | 115 | 15,246 | 2,095 | 1,958 | 19,299 | +| SCSS | 104 | 7,726 | 255 | 1,136 | 9,117 | +| CSS | 30 | 5,982 | 549 | 1,095 | 7,626 | +| HTML | 34 | 2,883 | 431 | 848 | 4,162 | +| XSL | 20 | 2,060 | 416 | 232 | 2,708 | +| Batch | 5 | 1,893 | 184 | 273 | 2,350 | +| Python | 5 | 632 | 49 | 148 | 829 | +| Shell Script | 6 | 538 | 248 | 120 | 906 | +| Properties | 16 | 161 | 43 | 30 | 234 | +| Pug | 6 | 119 | 1 | 10 | 130 | +| log | 1 | 38 | 0 | 1 | 39 | +| Markdown | 1 | 6 | 0 | 3 | 9 | +| Ignore | 1 | 2 | 0 | 0 | 2 | ++------------------+------------+------------+------------+------------+------------+ + +Directories ++-------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ +| path | files | code | comment | blank | total | ++-------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ +| . | 646 | 224,911 | 32,987 | 15,880 | 273,778 | +| solr-8.3.1 | 236 | 80,866 | 26,654 | 7,861 | 115,381 | +| src | 384 | 68,699 | 6,117 | 7,234 | 82,050 | +| deploy | 8 | 55,744 | 174 | 704 | 56,622 | +| deploy/assets | 2 | 55,677 | 174 | 686 | 56,537 | +| src/client | 277 | 46,644 | 4,305 | 5,743 | 56,692 | +| solr-8.3.1/server | 144 | 40,399 | 21,136 | 6,569 | 68,104 | +| solr-8.3.1/server/solr-webapp/webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| solr-8.3.1/server/solr-webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| src/client/views | 231 | 36,751 | 1,935 | 4,094 | 42,780 | +| solr-8.3.1/example | 82 | 32,009 | 5,063 | 942 | 38,014 | +| solr-8.3.1/example/films | 3 | 27,350 | 24 | 14 | 27,388 | +| solr-8.3.1/server/solr-webapp/webapp/libs | 21 | 24,087 | 15,683 | 3,464 | 43,234 | +| src/server | 52 | 16,247 | 956 | 731 | 17,934 | +| src/client/views/nodes | 75 | 12,034 | 475 | 1,218 | 13,727 | +| src/client/views/collections | 53 | 11,516 | 574 | 1,153 | 13,243 | +| solr-8.3.1/contrib | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter/conf | 2 | 6,199 | 63 | 11 | 6,273 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular | 26 | 5,548 | 536 | 1,005 | 7,089 | +| solr-8.3.1/server/solr-webapp/webapp/css | 26 | 5,548 | 536 | 1,005 | 7,089 | +| src/client/views/nodes/formattedText | 22 | 5,353 | 250 | 592 | 6,195 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular | 25 | 4,450 | 515 | 586 | 5,551 | +| solr-8.3.1/server/solr-webapp/webapp/js | 25 | 4,450 | 515 | 586 | 5,551 | +| src/client/util | 30 | 3,931 | 351 | 515 | 4,797 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers | 23 | 3,627 | 478 | 544 | 4,649 | +| solr-8.3.1/example/example-DIH/solr | 49 | 2,848 | 3,605 | 603 | 7,056 | +| solr-8.3.1/example/example-DIH | 49 | 2,848 | 3,605 | 603 | 7,056 | +| src/new_fields | 22 | 2,682 | 172 | 315 | 3,169 | +| solr-8.3.1/server/solr | 27 | 2,612 | 3,377 | 557 | 6,546 | +| solr-8.3.1/server/solr-webapp/webapp/partials | 24 | 2,571 | 415 | 787 | 3,773 | +| src/client/views/collections/collectionFreeForm | 12 | 2,540 | 177 | 203 | 2,920 | +| src/client/views/search | 21 | 2,380 | 238 | 401 | 3,019 | +| solr-8.3.1/server/solr/configsets | 21 | 2,243 | 2,359 | 437 | 5,039 | +| solr-8.3.1/bin | 5 | 2,118 | 392 | 311 | 2,821 | +| src/client/views/animationtimeline | 10 | 1,966 | 237 | 262 | 2,465 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/example/files | 13 | 1,298 | 1,153 | 250 | 2,701 | +| src/server/ApiManagers | 11 | 1,223 | 226 | 149 | 1,598 | +| solr-8.3.1/example/files/conf | 10 | 1,190 | 1,147 | 241 | 2,578 | +| src/scraping | 15 | 1,155 | 352 | 176 | 1,683 | +| src/client/apis | 7 | 1,053 | 97 | 150 | 1,300 | +| src/scraping/buxton | 11 | 1,016 | 167 | 161 | 1,344 | +| src/client/views/pdf | 6 | 964 | 27 | 93 | 1,084 | +| solr-8.3.1/example/example-DIH/solr/db | 14 | 935 | 1,167 | 191 | 2,293 | +| solr-8.3.1/example/example-DIH/solr/db/conf | 13 | 935 | 1,167 | 189 | 2,291 | +| solr-8.3.1/example/example-DIH/solr/mail | 14 | 919 | 1,171 | 189 | 2,279 | +| solr-8.3.1/example/example-DIH/solr/mail/conf | 13 | 919 | 1,171 | 187 | 2,277 | +| solr-8.3.1/example/example-DIH/solr/solr/conf | 13 | 917 | 1,183 | 185 | 2,285 | +| solr-8.3.1/example/example-DIH/solr/solr | 14 | 917 | 1,183 | 187 | 2,287 | +| src/server/DashSession | 9 | 903 | 229 | 122 | 1,254 | +| src/server/authentication | 4 | 890 | 66 | 100 | 1,056 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity | 3 | 839 | 77 | 129 | 1,045 | +| src/client/documents | 2 | 839 | 143 | 90 | 1,072 | +| src/client/views/collections/collectionMulticolumn | 8 | 751 | 144 | 85 | 980 | +| src/server/DashSession/Session | 8 | 748 | 177 | 99 | 1,024 | +| src/client/views/linking | 7 | 747 | 10 | 138 | 895 | +| solr-8.3.1/example/files/conf/velocity | 5 | 730 | 99 | 108 | 937 | +| solr-8.3.1/example/files/conf/velocity/js | 3 | 730 | 99 | 104 | 933 | +| src/server/authentication/models | 2 | 649 | 39 | 71 | 759 | +| src/mobile | 7 | 617 | 60 | 82 | 759 | +| src/client/util/Import & Export | 4 | 566 | 0 | 52 | 618 | +| src/client/apis/google_docs | 2 | 543 | 9 | 73 | 625 | +| solr-8.3.1/server/etc | 6 | 528 | 400 | 59 | 987 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/exampledocs | 17 | 513 | 281 | 75 | 869 | +| src/server/DashSession/Session/agents | 5 | 489 | 169 | 72 | 730 | +| src/pen-gestures | 2 | 397 | 172 | 27 | 596 | +| src/client/apis/youtube | 2 | 379 | 53 | 56 | 488 | +| src/client/views/webcam | 3 | 371 | 17 | 76 | 464 | +| solr-8.3.1/server/solr/dash | 4 | 345 | 968 | 105 | 1,418 | +| src/client/cognitive_services | 1 | 342 | 10 | 57 | 409 | +| solr-8.3.1/server/solr/dash/conf | 3 | 341 | 966 | 104 | 1,411 | +| src/server/apis | 4 | 328 | 198 | 41 | 567 | +| solr-8.3.1/server/solr/configsets/_default/conf | 2 | 316 | 976 | 99 | 1,391 | +| solr-8.3.1/server/solr/configsets/_default | 2 | 316 | 976 | 99 | 1,391 | +| views | 7 | 304 | 5 | 44 | 353 | +| src/client/views/presentationview | 2 | 272 | 31 | 24 | 327 | +| src/server/Websocket | 1 | 263 | 5 | 46 | 314 | +| src/server/DashSession/Session/utilities | 3 | 259 | 8 | 27 | 294 | +| src/debug | 3 | 244 | 0 | 28 | 272 | +| src/scraping/buxton/final | 1 | 228 | 142 | 26 | 396 | +| src/typings | 1 | 219 | 72 | 38 | 329 | +| src/server/authentication/controllers | 1 | 218 | 25 | 25 | 268 | +| src/scraping/buxton/.idea | 6 | 205 | 0 | 0 | 205 | +| src/server/apis/google | 2 | 191 | 168 | 27 | 386 | +| views/stylesheets | 1 | 185 | 4 | 34 | 223 | +| solr-8.3.1/server/scripts/cloud-scripts | 3 | 172 | 19 | 39 | 230 | +| solr-8.3.1/server/scripts | 3 | 172 | 19 | 39 | 230 | +| test | 1 | 141 | 0 | 20 | 161 | +| src/scraping/acm | 4 | 139 | 185 | 15 | 339 | +| src/server/apis/youtube | 2 | 137 | 30 | 14 | 181 | +| src/client/util/ProsemirrorCopy | 1 | 128 | 30 | 22 | 180 | +| solr-8.3.1/example/files/browse-resources/velocity | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/example/files/browse-resources | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/contrib/prometheus-exporter/bin | 1 | 82 | 0 | 26 | 108 | +| solr-8.3.1/server/resources | 3 | 74 | 123 | 16 | 213 | +| solr-8.3.1/server/solr-webapp/webapp/WEB-INF | 1 | 62 | 42 | 11 | 115 | +| solr-8.3.1/docs | 2 | 59 | 0 | 2 | 61 | +| src/extensions | 4 | 53 | 5 | 13 | 71 | +| solr-8.3.1/server/tmp | 5 | 45 | 10 | 5 | 60 | +| solr-8.3.1/example/example-DIH/solr/atom/conf | 2 | 41 | 43 | 17 | 101 | +| solr-8.3.1/example/example-DIH/solr/atom | 3 | 41 | 43 | 19 | 103 | +| solr-8.3.1/server/solr-webapp/webapp/img | 1 | 39 | 0 | 1 | 40 | +| solr-8.3.1/docs/images | 1 | 39 | 0 | 1 | 40 | +| src/scraping/buxton/narratives | 1 | 39 | 0 | 0 | 39 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| src/server/credentials | 2 | 35 | 0 | 6 | 41 | +| solr-8.3.1/example/example-DIH/solr/tika | 3 | 34 | 41 | 16 | 91 | +| solr-8.3.1/example/example-DIH/solr/tika/conf | 2 | 34 | 41 | 14 | 89 | +| deploy/debug | 3 | 32 | 0 | 9 | 41 | +| src/server/authentication/config | 1 | 23 | 2 | 4 | 29 | +| deploy/mobile | 2 | 22 | 0 | 6 | 28 | +| src/extensions/General | 2 | 14 | 0 | 3 | 17 | +| build | 1 | 9 | 0 | 3 | 12 | +| solr-8.3.1/server/contexts | 1 | 8 | 0 | 1 | 9 | +| src/scraping/buxton/.idea/inspectionProfiles | 1 | 6 | 0 | 0 | 6 | ++-------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ + +Files ++-------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ +| filename | language | code | comment | blank | total | ++-------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ +| README.md | Markdown | 6 | 0 | 3 | 9 | +| build/index.html | HTML | 9 | 0 | 3 | 12 | +| dash.bat | Batch | 2 | 0 | 1 | 3 | +| deploy/assets/env.json | JSON | 15 | 0 | 0 | 15 | +| deploy/assets/pdf.worker.js | JavaScript | 55,662 | 174 | 686 | 56,522 | +| deploy/debug/repl.html | HTML | 11 | 0 | 3 | 14 | +| deploy/debug/test.html | HTML | 10 | 0 | 3 | 13 | +| deploy/debug/viewer.html | HTML | 11 | 0 | 3 | 14 | +| deploy/index.html | HTML | 13 | 0 | 3 | 16 | +| deploy/mobile/image.html | HTML | 12 | 0 | 3 | 15 | +| deploy/mobile/ink.html | HTML | 10 | 0 | 3 | 13 | +| package-lock.json | JSON | 18,689 | 0 | 1 | 18,690 | +| package.json | JSON | 266 | 0 | 1 | 267 | +| sentence_parser.py | Python | 6 | 0 | 1 | 7 | +| session.config.json | JSON | 12 | 0 | 1 | 13 | +| solr-8.3.1/bin/install_solr_service.sh | Shell Script | 307 | 29 | 35 | 371 | +| solr-8.3.1/bin/oom_solr.sh | Shell Script | 13 | 15 | 3 | 31 | +| solr-8.3.1/bin/solr.cmd | Batch | 1,782 | 43 | 210 | 2,035 | +| solr-8.3.1/bin/solr.in.cmd | Batch | 16 | 133 | 29 | 178 | +| solr-8.3.1/bin/solr.in.sh | Shell Script | 0 | 172 | 34 | 206 | +| solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd | Batch | 82 | 0 | 26 | 108 | +| solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json | JSON | 4,465 | 0 | 1 | 4,466 | +| solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml | XML | 1,734 | 63 | 10 | 1,807 | +| solr-8.3.1/docs/images/solr.svg | XML | 39 | 0 | 1 | 40 | +| solr-8.3.1/docs/index.html | HTML | 20 | 0 | 1 | 21 | +| solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml | XML | 21 | 6 | 9 | 36 | +| solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml | XML | 20 | 37 | 8 | 65 | +| solr-8.3.1/example/example-DIH/solr/atom/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml | XML | 26 | 0 | 4 | 30 | +| solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml | XML | 292 | 958 | 104 | 1,354 | +| solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/example/example-DIH/solr/db/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml | XML | 8 | 4 | 1 | 13 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml | XML | 294 | 958 | 105 | 1,357 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/example/example-DIH/solr/mail/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/solr.xml | XML | 2 | 0 | 1 | 3 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml | XML | 8 | 16 | 2 | 26 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml | XML | 292 | 958 | 102 | 1,352 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/example/example-DIH/solr/solr/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml | XML | 17 | 38 | 7 | 62 | +| solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml | XML | 17 | 3 | 7 | 27 | +| solr-8.3.1/example/example-DIH/solr/tika/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/exampledocs/books.json | JSON | 51 | 0 | 1 | 52 | +| solr-8.3.1/example/exampledocs/gb18030-example.xml | XML | 14 | 16 | 3 | 33 | +| solr-8.3.1/example/exampledocs/hd.xml | XML | 33 | 20 | 4 | 57 | +| solr-8.3.1/example/exampledocs/ipod_other.xml | XML | 32 | 20 | 9 | 61 | +| solr-8.3.1/example/exampledocs/ipod_video.xml | XML | 21 | 18 | 2 | 41 | +| solr-8.3.1/example/exampledocs/manufacturers.xml | XML | 57 | 16 | 3 | 76 | +| solr-8.3.1/example/exampledocs/mem.xml | XML | 45 | 24 | 9 | 78 | +| solr-8.3.1/example/exampledocs/money.xml | XML | 42 | 17 | 7 | 66 | +| solr-8.3.1/example/exampledocs/monitor.xml | XML | 14 | 18 | 3 | 35 | +| solr-8.3.1/example/exampledocs/monitor2.xml | XML | 13 | 18 | 3 | 34 | +| solr-8.3.1/example/exampledocs/mp500.xml | XML | 23 | 18 | 3 | 44 | +| solr-8.3.1/example/exampledocs/sample.html | HTML | 13 | 0 | 1 | 14 | +| solr-8.3.1/example/exampledocs/sd500.xml | XML | 19 | 18 | 2 | 39 | +| solr-8.3.1/example/exampledocs/solr.xml | XML | 20 | 16 | 3 | 39 | +| solr-8.3.1/example/exampledocs/test_utf8.sh | Shell Script | 57 | 21 | 16 | 94 | +| solr-8.3.1/example/exampledocs/utf8-example.xml | XML | 19 | 20 | 4 | 43 | +| solr-8.3.1/example/exampledocs/vidcard.xml | XML | 40 | 21 | 2 | 63 | +| solr-8.3.1/example/files/browse-resources/velocity/resources.properties | Properties | 72 | 6 | 5 | 83 | +| solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties | Properties | 18 | 0 | 1 | 19 | +| solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties | Properties | 18 | 0 | 3 | 21 | +| solr-8.3.1/example/files/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/files/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/files/conf/params.json | JSON | 34 | 0 | 1 | 35 | +| solr-8.3.1/example/files/conf/solrconfig.xml | XML | 298 | 979 | 102 | 1,379 | +| solr-8.3.1/example/files/conf/update-script.js | JavaScript | 80 | 13 | 23 | 116 | +| solr-8.3.1/example/files/conf/velocity/dropit.js | JavaScript | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js | JavaScript | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/files/conf/velocity/js/dropit.js | JavaScript | 64 | 15 | 19 | 98 | +| solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js | JavaScript | 620 | 68 | 76 | 764 | +| solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js | JavaScript | 46 | 16 | 9 | 71 | +| solr-8.3.1/example/films/film_data_generator.py | Python | 82 | 24 | 12 | 118 | +| solr-8.3.1/example/films/films.json | JSON | 15,830 | 0 | 1 | 15,831 | +| solr-8.3.1/example/films/films.xml | XML | 11,438 | 0 | 1 | 11,439 | +| solr-8.3.1/server/contexts/solr-jetty-context.xml | XML | 8 | 0 | 1 | 9 | +| solr-8.3.1/server/etc/jetty-http.xml | XML | 33 | 15 | 4 | 52 | +| solr-8.3.1/server/etc/jetty-https.xml | XML | 56 | 16 | 5 | 77 | +| solr-8.3.1/server/etc/jetty-https8.xml | XML | 34 | 32 | 4 | 70 | +| solr-8.3.1/server/etc/jetty-ssl.xml | XML | 23 | 11 | 4 | 38 | +| solr-8.3.1/server/etc/jetty.xml | XML | 110 | 94 | 18 | 222 | +| solr-8.3.1/server/etc/webdefault.xml | XML | 272 | 232 | 24 | 528 | +| solr-8.3.1/server/resources/jetty-logging.properties | Properties | 1 | 0 | 1 | 2 | +| solr-8.3.1/server/resources/log4j2-console.xml | XML | 19 | 43 | 6 | 68 | +| solr-8.3.1/server/resources/log4j2.xml | XML | 54 | 80 | 9 | 143 | +| solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh | Shell Script | 152 | 2 | 23 | 177 | +| solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat | Batch | 11 | 8 | 7 | 26 | +| solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh | Shell Script | 9 | 9 | 9 | 27 | +| solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml | XML | 62 | 42 | 11 | 115 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css | CSS | 237 | 19 | 48 | 304 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css | CSS | 402 | 55 | 9 | 466 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css | CSS | 594 | 23 | 106 | 723 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css | CSS | 296 | 18 | 65 | 379 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css | CSS | 647 | 19 | 106 | 772 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css | CSS | 171 | 18 | 37 | 226 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css | CSS | 134 | 18 | 28 | 180 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css | CSS | 292 | 18 | 61 | 371 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css | CSS | 131 | 23 | 26 | 180 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css | CSS | 29 | 18 | 7 | 54 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css | CSS | 164 | 18 | 35 | 217 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css | CSS | 24 | 18 | 6 | 48 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css | CSS | 1 | 26 | 2 | 29 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css | CSS | 1 | 22 | 2 | 25 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css | CSS | 303 | 19 | 63 | 385 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css | CSS | 80 | 18 | 12 | 110 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css | CSS | 257 | 18 | 56 | 331 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css | CSS | 20 | 18 | 5 | 43 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css | CSS | 172 | 18 | 31 | 221 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css | CSS | 120 | 18 | 25 | 163 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css | CSS | 404 | 18 | 79 | 501 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css | CSS | 596 | 20 | 112 | 728 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css | CSS | 133 | 18 | 22 | 173 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css | CSS | 178 | 22 | 34 | 234 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css | CSS | 43 | 18 | 4 | 65 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css | CSS | 119 | 18 | 24 | 161 | +| solr-8.3.1/server/solr-webapp/webapp/img/solr.svg | XML | 39 | 0 | 1 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/index.html | HTML | 203 | 16 | 38 | 257 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js | JavaScript | 512 | 19 | 31 | 562 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js | JavaScript | 8 | 16 | 4 | 28 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js | JavaScript | 161 | 18 | 23 | 202 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js | JavaScript | 847 | 51 | 124 | 1,022 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js | JavaScript | 43 | 18 | 2 | 63 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js | JavaScript | 18 | 16 | 6 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js | JavaScript | 244 | 19 | 27 | 290 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js | JavaScript | 69 | 16 | 9 | 94 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js | JavaScript | 151 | 16 | 14 | 181 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js | JavaScript | 234 | 25 | 44 | 303 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js | JavaScript | 107 | 18 | 13 | 138 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js | JavaScript | 72 | 16 | 13 | 101 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js | JavaScript | 61 | 23 | 14 | 98 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js | JavaScript | 27 | 16 | 3 | 46 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js | JavaScript | 112 | 35 | 12 | 159 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js | JavaScript | 269 | 30 | 19 | 318 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js | JavaScript | 130 | 19 | 19 | 168 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js | JavaScript | 88 | 19 | 14 | 121 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js | JavaScript | 178 | 18 | 40 | 236 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js | JavaScript | 524 | 19 | 69 | 612 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js | JavaScript | 64 | 16 | 20 | 100 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js | JavaScript | 173 | 16 | 51 | 240 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js | JavaScript | 33 | 16 | 2 | 51 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js | JavaScript | 14 | 22 | 2 | 38 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js | JavaScript | 311 | 18 | 11 | 340 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js | JavaScript | 112 | 24 | 4 | 140 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js | JavaScript | 73 | 135 | 22 | 230 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js | JavaScript | 2 | 29 | 1 | 32 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js | JavaScript | 7 | 29 | 1 | 37 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js | JavaScript | 316 | 636 | 67 | 1,019 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js | JavaScript | 9 | 29 | 1 | 39 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js | JavaScript | 311 | 328 | 65 | 704 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js | JavaScript | 10 | 29 | 1 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js | JavaScript | 157 | 48 | 13 | 218 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js | JavaScript | 1 | 42 | 3 | 46 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular.js | JavaScript | 10,845 | 13,211 | 2,038 | 26,094 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js | JavaScript | 73 | 201 | 0 | 274 | +| solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js | JavaScript | 1,151 | 36 | 8 | 1,195 | +| solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js | JavaScript | 2 | 29 | 0 | 31 | +| solr-8.3.1/server/solr-webapp/webapp/libs/d3.js | JavaScript | 7,720 | 519 | 1,135 | 9,374 | +| solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js | JavaScript | 2 | 29 | 1 | 32 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js | JavaScript | 3 | 26 | 2 | 31 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js | JavaScript | 3 | 26 | 1 | 30 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js | JavaScript | 2 | 26 | 3 | 31 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js | JavaScript | 3,222 | 228 | 85 | 3,535 | +| solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js | JavaScript | 66 | 23 | 13 | 102 | +| solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html | HTML | 21 | 16 | 10 | 47 | +| solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html | HTML | 87 | 16 | 26 | 129 | +| solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html | HTML | 263 | 16 | 24 | 303 | +| solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html | HTML | 30 | 16 | 4 | 50 | +| solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html | HTML | 48 | 16 | 22 | 86 | +| solr-8.3.1/server/solr-webapp/webapp/partials/collections.html | HTML | 301 | 16 | 79 | 396 | +| solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html | HTML | 125 | 16 | 66 | 207 | +| solr-8.3.1/server/solr-webapp/webapp/partials/cores.html | HTML | 142 | 16 | 67 | 225 | +| solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html | HTML | 142 | 16 | 52 | 210 | +| solr-8.3.1/server/solr-webapp/webapp/partials/documents.html | HTML | 83 | 20 | 9 | 112 | +| solr-8.3.1/server/solr-webapp/webapp/partials/files.html | HTML | 17 | 16 | 15 | 48 | +| solr-8.3.1/server/solr-webapp/webapp/partials/index.html | HTML | 135 | 42 | 85 | 262 | +| solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html | HTML | 10 | 16 | 2 | 28 | +| solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html | HTML | 35 | 16 | 6 | 57 | +| solr-8.3.1/server/solr-webapp/webapp/partials/logging.html | HTML | 40 | 16 | 2 | 58 | +| solr-8.3.1/server/solr-webapp/webapp/partials/login.html | HTML | 134 | 16 | 11 | 161 | +| solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html | HTML | 48 | 17 | 8 | 73 | +| solr-8.3.1/server/solr-webapp/webapp/partials/query.html | HTML | 270 | 16 | 84 | 370 | +| solr-8.3.1/server/solr-webapp/webapp/partials/replication.html | HTML | 153 | 16 | 71 | 240 | +| solr-8.3.1/server/solr-webapp/webapp/partials/schema.html | HTML | 336 | 16 | 104 | 456 | +| solr-8.3.1/server/solr-webapp/webapp/partials/segments.html | HTML | 70 | 16 | 14 | 100 | +| solr-8.3.1/server/solr-webapp/webapp/partials/stream.html | HTML | 40 | 16 | 9 | 65 | +| solr-8.3.1/server/solr-webapp/webapp/partials/threads.html | HTML | 36 | 16 | 14 | 66 | +| solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html | HTML | 5 | 16 | 3 | 24 | +| solr-8.3.1/server/solr/configsets/_default/conf/params.json | JSON | 20 | 0 | 1 | 21 | +| solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml | XML | 296 | 976 | 98 | 1,370 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json | JSON | 1 | 0 | 1 | 2 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json | JSON | 38 | 0 | 1 | 39 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json | JSON | 11 | 0 | 1 | 12 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json | JSON | 11 | 0 | 1 | 12 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml | XML | 410 | 1,097 | 124 | 1,631 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css | CSS | 34 | 9 | 6 | 49 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js | JavaScript | 620 | 68 | 76 | 764 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css | CSS | 185 | 0 | 47 | 232 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/server/solr/dash/conf/params.json | JSON | 20 | 0 | 0 | 20 | +| solr-8.3.1/server/solr/dash/conf/schema.xml | XML | 51 | 4 | 7 | 62 | +| solr-8.3.1/server/solr/dash/conf/solrconfig.xml | XML | 270 | 962 | 97 | 1,329 | +| solr-8.3.1/server/solr/dash/core.properties | Properties | 4 | 2 | 1 | 7 | +| solr-8.3.1/server/solr/solr.xml | XML | 21 | 25 | 11 | 57 | +| solr-8.3.1/server/solr/zoo.cfg | Properties | 3 | 25 | 4 | 32 | +| solr-8.3.1/server/tmp/start_3204295554151338130.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_5812170489311981381.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_6476327636763392575.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_7329004517204835686.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_9067375725008958788.properties | Properties | 9 | 2 | 1 | 12 | +| src/Utils.ts | TypeScript | 441 | 23 | 81 | 545 | +| src/client/ClientRecommender.scss | SCSS | 9 | 1 | 2 | 12 | +| src/client/ClientRecommender.tsx | TypeScript React | 320 | 61 | 44 | 425 | +| src/client/DocServer.ts | TypeScript | 279 | 136 | 65 | 480 | +| src/client/Network.ts | TypeScript | 34 | 0 | 5 | 39 | +| src/client/apis/GoogleAuthenticationManager.scss | SCSS | 16 | 0 | 3 | 19 | +| src/client/apis/GoogleAuthenticationManager.tsx | TypeScript React | 115 | 2 | 11 | 128 | +| src/client/apis/IBM_Recommender.ts | TypeScript | 0 | 33 | 7 | 40 | +| src/client/apis/google_docs/GoogleApiClientUtils.ts | TypeScript | 225 | 9 | 27 | 261 | +| src/client/apis/google_docs/GooglePhotosClientUtils.ts | TypeScript | 318 | 0 | 46 | 364 | +| src/client/apis/youtube/YoutubeBox.scss | SCSS | 105 | 5 | 16 | 126 | +| src/client/apis/youtube/YoutubeBox.tsx | TypeScript React | 274 | 48 | 40 | 362 | +| src/client/cognitive_services/CognitiveServices.ts | TypeScript | 342 | 10 | 57 | 409 | +| src/client/documents/DocumentTypes.ts | TypeScript | 32 | 2 | 3 | 37 | +| src/client/documents/Documents.ts | TypeScript | 807 | 141 | 87 | 1,035 | +| src/client/goldenLayout.d.ts | TypeScript | 2 | 0 | 1 | 3 | +| src/client/goldenLayout.js | JavaScript | 3,084 | 1,571 | 720 | 5,375 | +| src/client/util/DictationManager.ts | TypeScript | 312 | 25 | 51 | 388 | +| src/client/util/DocumentManager.ts | TypeScript | 207 | 16 | 21 | 244 | +| src/client/util/DragManager.ts | TypeScript | 504 | 11 | 35 | 550 | +| src/client/util/DropConverter.ts | TypeScript | 70 | 6 | 2 | 78 | +| src/client/util/History.ts | TypeScript | 166 | 13 | 27 | 206 | +| src/client/util/Import & Export/DirectoryImportBox.scss | SCSS | 6 | 0 | 0 | 6 | +| src/client/util/Import & Export/DirectoryImportBox.tsx | TypeScript React | 393 | 0 | 31 | 424 | +| src/client/util/Import & Export/ImageUtils.ts | TypeScript | 35 | 0 | 4 | 39 | +| src/client/util/Import & Export/ImportMetadataEntry.tsx | TypeScript React | 132 | 0 | 17 | 149 | +| src/client/util/InteractionUtils.tsx | TypeScript React | 122 | 112 | 21 | 255 | +| src/client/util/KeyCodes.ts | TypeScript | 100 | 36 | 0 | 136 | +| src/client/util/LinkManager.ts | TypeScript | 161 | 30 | 23 | 214 | +| src/client/util/ProsemirrorCopy/prompt.js | JavaScript | 128 | 30 | 22 | 180 | +| src/client/util/Scripting.ts | TypeScript | 247 | 15 | 30 | 292 | +| src/client/util/ScrollBox.tsx | TypeScript React | 19 | 0 | 2 | 21 | +| src/client/util/SearchUtil.ts | TypeScript | 123 | 4 | 18 | 145 | +| src/client/util/SelectionManager.ts | TypeScript | 69 | 5 | 15 | 89 | +| src/client/util/SerializationHelper.ts | TypeScript | 113 | 15 | 15 | 143 | +| src/client/util/SettingsManager.scss | SCSS | 111 | 0 | 25 | 136 | +| src/client/util/SettingsManager.tsx | TypeScript React | 114 | 0 | 17 | 131 | +| src/client/util/SharingManager.scss | SCSS | 122 | 0 | 18 | 140 | +| src/client/util/SharingManager.tsx | TypeScript React | 273 | 0 | 25 | 298 | +| src/client/util/Transform.ts | TypeScript | 76 | 0 | 23 | 99 | +| src/client/util/TypedEvent.ts | TypeScript | 29 | 3 | 8 | 40 | +| src/client/util/UndoManager.ts | TypeScript | 167 | 1 | 27 | 195 | +| src/client/util/clamp.js | JavaScript | 14 | 0 | 1 | 15 | +| src/client/util/convertToCSSPTValue.js | JavaScript | 31 | 4 | 8 | 43 | +| src/client/util/jsx-decl.d.ts | TypeScript | 1 | 0 | 1 | 2 | +| src/client/util/request-image-size.js | JavaScript | 51 | 10 | 14 | 75 | +| src/client/util/toCSSLineSpacing.js | JavaScript | 35 | 15 | 14 | 64 | +| src/client/views/AntimodeMenu.scss | SCSS | 35 | 1 | 6 | 42 | +| src/client/views/AntimodeMenu.tsx | TypeScript React | 121 | 14 | 22 | 157 | +| src/client/views/ContextMenu.scss | SCSS | 130 | 17 | 14 | 161 | +| src/client/views/ContextMenu.tsx | TypeScript React | 258 | 3 | 33 | 294 | +| src/client/views/ContextMenuItem.tsx | TypeScript React | 107 | 0 | 10 | 117 | +| src/client/views/DictationOverlay.tsx | TypeScript React | 64 | 0 | 7 | 71 | +| src/client/views/DocComponent.tsx | TypeScript React | 87 | 20 | 15 | 122 | +| src/client/views/DocumentButtonBar.scss | SCSS | 89 | 0 | 16 | 105 | +| src/client/views/DocumentButtonBar.tsx | TypeScript React | 284 | 4 | 27 | 315 | +| src/client/views/DocumentDecorations.scss | SCSS | 319 | 0 | 46 | 365 | +| src/client/views/DocumentDecorations.tsx | TypeScript React | 479 | 2 | 26 | 507 | +| src/client/views/EditableView.scss | SCSS | 22 | 0 | 3 | 25 | +| src/client/views/EditableView.tsx | TypeScript React | 149 | 19 | 16 | 184 | +| src/client/views/GestureOverlay.scss | SCSS | 56 | 0 | 8 | 64 | +| src/client/views/GestureOverlay.tsx | TypeScript React | 711 | 45 | 67 | 823 | +| src/client/views/GlobalKeyHandler.ts | TypeScript | 232 | 10 | 27 | 269 | +| src/client/views/InkingControl.scss | SCSS | 127 | 4 | 0 | 131 | +| src/client/views/InkingControl.tsx | TypeScript React | 78 | 3 | 10 | 91 | +| src/client/views/InkingStroke.scss | SCSS | 7 | 0 | 0 | 7 | +| src/client/views/InkingStroke.tsx | TypeScript React | 63 | 0 | 5 | 68 | +| src/client/views/KeyphraseQueryView.scss | SCSS | 7 | 0 | 1 | 8 | +| src/client/views/KeyphraseQueryView.tsx | TypeScript React | 30 | 2 | 3 | 35 | +| src/client/views/Main.scss | SCSS | 51 | 8 | 10 | 69 | +| src/client/views/Main.tsx | TypeScript React | 22 | 1 | 2 | 25 | +| src/client/views/MainView.scss | SCSS | 138 | 1 | 21 | 160 | +| src/client/views/MainView.tsx | TypeScript React | 553 | 13 | 36 | 602 | +| src/client/views/MainViewModal.scss | SCSS | 24 | 0 | 1 | 25 | +| src/client/views/MainViewModal.tsx | TypeScript React | 39 | 0 | 5 | 44 | +| src/client/views/MainViewNotifs.scss | SCSS | 17 | 0 | 1 | 18 | +| src/client/views/MainViewNotifs.tsx | TypeScript React | 29 | 0 | 4 | 33 | +| src/client/views/MetadataEntryMenu.scss | SCSS | 79 | 0 | 14 | 93 | +| src/client/views/MetadataEntryMenu.tsx | TypeScript React | 207 | 0 | 16 | 223 | +| src/client/views/OCRUtils.ts | TypeScript | 2 | 2 | 4 | 8 | +| src/client/views/OverlayView.scss | SCSS | 41 | 0 | 6 | 47 | +| src/client/views/OverlayView.tsx | TypeScript React | 194 | 4 | 19 | 217 | +| src/client/views/Palette.scss | SCSS | 26 | 0 | 4 | 30 | +| src/client/views/Palette.tsx | TypeScript React | 65 | 0 | 5 | 70 | +| src/client/views/PreviewCursor.scss | SCSS | 9 | 0 | 1 | 10 | +| src/client/views/PreviewCursor.tsx | TypeScript React | 115 | 8 | 9 | 132 | +| src/client/views/RecommendationsBox.scss | SCSS | 52 | 10 | 8 | 70 | +| src/client/views/RecommendationsBox.tsx | TypeScript React | 116 | 67 | 17 | 200 | +| src/client/views/ScriptBox.scss | SCSS | 15 | 0 | 2 | 17 | +| src/client/views/ScriptBox.tsx | TypeScript React | 112 | 2 | 12 | 126 | +| src/client/views/ScriptingRepl.scss | SCSS | 42 | 0 | 9 | 51 | +| src/client/views/ScriptingRepl.tsx | TypeScript React | 220 | 1 | 24 | 245 | +| src/client/views/SearchDocBox.tsx | TypeScript React | 359 | 17 | 55 | 431 | +| src/client/views/TemplateMenu.scss | SCSS | 46 | 0 | 5 | 51 | +| src/client/views/TemplateMenu.tsx | TypeScript React | 167 | 1 | 14 | 182 | +| src/client/views/Templates.tsx | TypeScript React | 34 | 0 | 8 | 42 | +| src/client/views/TouchScrollableMenu.tsx | TypeScript React | 52 | 0 | 7 | 59 | +| src/client/views/Touchable.tsx | TypeScript React | 172 | 28 | 39 | 239 | +| src/client/views/_nodeModuleOverrides.scss | SCSS | 9 | 9 | 4 | 22 | +| src/client/views/animationtimeline/Keyframe.scss | SCSS | 83 | 5 | 17 | 105 | +| src/client/views/animationtimeline/Keyframe.tsx | TypeScript React | 468 | 50 | 42 | 560 | +| src/client/views/animationtimeline/Timeline.scss | SCSS | 264 | 14 | 44 | 322 | +| src/client/views/animationtimeline/Timeline.tsx | TypeScript React | 467 | 97 | 58 | 622 | +| src/client/views/animationtimeline/TimelineMenu.scss | SCSS | 75 | 2 | 17 | 94 | +| src/client/views/animationtimeline/TimelineMenu.tsx | TypeScript React | 68 | 0 | 10 | 78 | +| src/client/views/animationtimeline/TimelineOverview.scss | SCSS | 89 | 6 | 12 | 107 | +| src/client/views/animationtimeline/TimelineOverview.tsx | TypeScript React | 155 | 0 | 27 | 182 | +| src/client/views/animationtimeline/Track.scss | SCSS | 13 | 0 | 2 | 15 | +| src/client/views/animationtimeline/Track.tsx | TypeScript React | 284 | 63 | 33 | 380 | +| src/client/views/collections/CollectionCarouselView.scss | SCSS | 37 | 0 | 1 | 38 | +| src/client/views/collections/CollectionCarouselView.tsx | TypeScript React | 110 | 1 | 10 | 121 | +| src/client/views/collections/CollectionDockingView.scss | SCSS | 391 | 7 | 60 | 458 | +| src/client/views/collections/CollectionDockingView.tsx | TypeScript React | 707 | 61 | 65 | 833 | +| src/client/views/collections/CollectionLinearView.scss | SCSS | 68 | 0 | 10 | 78 | +| src/client/views/collections/CollectionLinearView.tsx | TypeScript React | 125 | 1 | 9 | 135 | +| src/client/views/collections/CollectionMapView.scss | SCSS | 27 | 0 | 3 | 30 | +| src/client/views/collections/CollectionMapView.tsx | TypeScript React | 235 | 10 | 18 | 263 | +| src/client/views/collections/CollectionMasonryViewFieldRow.tsx | TypeScript React | 308 | 0 | 24 | 332 | +| src/client/views/collections/CollectionPileView.scss | SCSS | 8 | 0 | 1 | 9 | +| src/client/views/collections/CollectionPileView.tsx | TypeScript React | 112 | 5 | 11 | 128 | +| src/client/views/collections/CollectionSchemaCells.tsx | TypeScript React | 274 | 17 | 38 | 329 | +| src/client/views/collections/CollectionSchemaHeaders.tsx | TypeScript React | 318 | 5 | 41 | 364 | +| src/client/views/collections/CollectionSchemaMovableTableHOC.tsx | TypeScript React | 216 | 0 | 27 | 243 | +| src/client/views/collections/CollectionSchemaView.scss | SCSS | 406 | 4 | 88 | 498 | +| src/client/views/collections/CollectionSchemaView.tsx | TypeScript React | 651 | 20 | 82 | 753 | +| src/client/views/collections/CollectionStackingView.scss | SCSS | 353 | 1 | 50 | 404 | +| src/client/views/collections/CollectionStackingView.tsx | TypeScript React | 430 | 5 | 25 | 460 | +| src/client/views/collections/CollectionStackingViewFieldColumn.tsx | TypeScript React | 367 | 0 | 27 | 394 | +| src/client/views/collections/CollectionStaffView.scss | SCSS | 12 | 0 | 1 | 13 | +| src/client/views/collections/CollectionStaffView.tsx | TypeScript React | 46 | 0 | 7 | 53 | +| src/client/views/collections/CollectionSubView.tsx | TypeScript React | 390 | 7 | 25 | 422 | +| src/client/views/collections/CollectionTimeView.scss | SCSS | 80 | 0 | 13 | 93 | +| src/client/views/collections/CollectionTimeView.tsx | TypeScript React | 177 | 0 | 15 | 192 | +| src/client/views/collections/CollectionTreeView.scss | SCSS | 125 | 4 | 23 | 152 | +| src/client/views/collections/CollectionTreeView.tsx | TypeScript React | 801 | 19 | 40 | 860 | +| src/client/views/collections/CollectionView.scss | SCSS | 70 | 0 | 8 | 78 | +| src/client/views/collections/CollectionView.tsx | TypeScript React | 457 | 13 | 34 | 504 | +| src/client/views/collections/CollectionViewChromes.scss | SCSS | 308 | 4 | 45 | 357 | +| src/client/views/collections/CollectionViewChromes.tsx | TypeScript React | 393 | 67 | 47 | 507 | +| src/client/views/collections/KeyRestrictionRow.tsx | TypeScript React | 49 | 2 | 4 | 55 | +| src/client/views/collections/ParentDocumentSelector.scss | SCSS | 54 | 0 | 2 | 56 | +| src/client/views/collections/ParentDocumentSelector.tsx | TypeScript React | 120 | 0 | 11 | 131 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | TypeScript React | 422 | 10 | 27 | 459 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss | SCSS | 19 | 0 | 1 | 20 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | TypeScript React | 110 | 5 | 4 | 119 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss | SCSS | 11 | 0 | 0 | 11 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx | TypeScript React | 44 | 0 | 2 | 46 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss | SCSS | 20 | 1 | 3 | 24 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx | TypeScript React | 67 | 0 | 12 | 79 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss | SCSS | 95 | 9 | 17 | 121 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | TypeScript React | 1,186 | 47 | 97 | 1,330 | +| src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx | TypeScript React | 52 | 0 | 5 | 57 | +| src/client/views/collections/collectionFreeForm/MarqueeView.scss | SCSS | 30 | 0 | 2 | 32 | +| src/client/views/collections/collectionFreeForm/MarqueeView.tsx | TypeScript React | 484 | 105 | 33 | 622 | +| src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss | SCSS | 28 | 0 | 6 | 34 | +| src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx | TypeScript React | 202 | 72 | 23 | 297 | +| src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss | SCSS | 29 | 0 | 6 | 35 | +| src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx | TypeScript React | 204 | 72 | 22 | 298 | +| src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx | TypeScript React | 94 | 0 | 9 | 103 | +| src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx | TypeScript React | 51 | 0 | 5 | 56 | +| src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx | TypeScript React | 51 | 0 | 5 | 56 | +| src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx | TypeScript React | 92 | 0 | 9 | 101 | +| src/client/views/globalCssVariables.scss | SCSS | 30 | 10 | 3 | 43 | +| src/client/views/globalCssVariables.scss.d.ts | TypeScript | 9 | 0 | 2 | 11 | +| src/client/views/linking/LinkEditor.scss | SCSS | 124 | 1 | 25 | 150 | +| src/client/views/linking/LinkEditor.tsx | TypeScript React | 252 | 7 | 50 | 309 | +| src/client/views/linking/LinkMenu.scss | SCSS | 40 | 0 | 13 | 53 | +| src/client/views/linking/LinkMenu.tsx | TypeScript React | 65 | 1 | 10 | 76 | +| src/client/views/linking/LinkMenuGroup.tsx | TypeScript React | 84 | 0 | 10 | 94 | +| src/client/views/linking/LinkMenuItem.scss | SCSS | 75 | 1 | 11 | 87 | +| src/client/views/linking/LinkMenuItem.tsx | TypeScript React | 107 | 0 | 19 | 126 | +| src/client/views/nodes/AudioBox.scss | SCSS | 146 | 0 | 0 | 146 | +| src/client/views/nodes/AudioBox.tsx | TypeScript React | 261 | 2 | 24 | 287 | +| src/client/views/nodes/CollectionFreeFormDocumentView.scss | SCSS | 8 | 0 | 0 | 8 | +| src/client/views/nodes/CollectionFreeFormDocumentView.tsx | TypeScript React | 124 | 0 | 7 | 131 | +| src/client/views/nodes/ColorBox.scss | SCSS | 22 | 0 | 1 | 23 | +| src/client/views/nodes/ColorBox.tsx | TypeScript React | 28 | 0 | 4 | 32 | +| src/client/views/nodes/ContentFittingDocumentView.scss | SCSS | 20 | 0 | 4 | 24 | +| src/client/views/nodes/ContentFittingDocumentView.tsx | TypeScript React | 117 | 0 | 7 | 124 | +| src/client/views/nodes/DocumentBox.scss | SCSS | 14 | 0 | 0 | 14 | +| src/client/views/nodes/DocumentBox.tsx | TypeScript React | 154 | 0 | 4 | 158 | +| src/client/views/nodes/DocumentContentsView.tsx | TypeScript React | 183 | 10 | 17 | 210 | +| src/client/views/nodes/DocumentIcon.tsx | TypeScript React | 60 | 0 | 5 | 65 | +| src/client/views/nodes/DocumentView.scss | SCSS | 110 | 1 | 15 | 126 | +| src/client/views/nodes/DocumentView.tsx | TypeScript React | 1,041 | 54 | 94 | 1,189 | +| src/client/views/nodes/FaceRectangle.tsx | TypeScript React | 25 | 0 | 4 | 29 | +| src/client/views/nodes/FaceRectangles.tsx | TypeScript React | 41 | 0 | 5 | 46 | +| src/client/views/nodes/FieldTextBox.scss | SCSS | 12 | 0 | 3 | 15 | +| src/client/views/nodes/FieldView.tsx | TypeScript React | 89 | 43 | 4 | 136 | +| src/client/views/nodes/FontIconBox.scss | SCSS | 25 | 0 | 2 | 27 | +| src/client/views/nodes/FontIconBox.tsx | TypeScript React | 58 | 0 | 5 | 63 | +| src/client/views/nodes/ImageBox.scss | SCSS | 135 | 0 | 17 | 152 | +| src/client/views/nodes/ImageBox.tsx | TypeScript React | 430 | 10 | 35 | 475 | +| src/client/views/nodes/KeyValueBox.scss | SCSS | 120 | 0 | 3 | 123 | +| src/client/views/nodes/KeyValueBox.tsx | TypeScript React | 244 | 1 | 26 | 271 | +| src/client/views/nodes/KeyValuePair.scss | SCSS | 55 | 1 | 4 | 60 | +| src/client/views/nodes/KeyValuePair.tsx | TypeScript React | 125 | 2 | 8 | 135 | +| src/client/views/nodes/LabelBox.scss | SCSS | 31 | 0 | 4 | 35 | +| src/client/views/nodes/LabelBox.tsx | TypeScript React | 86 | 1 | 9 | 96 | +| src/client/views/nodes/LinkAnchorBox.scss | SCSS | 27 | 0 | 2 | 29 | +| src/client/views/nodes/LinkAnchorBox.tsx | TypeScript React | 141 | 0 | 9 | 150 | +| src/client/views/nodes/LinkBox.scss | SCSS | 3 | 0 | 0 | 3 | +| src/client/views/nodes/LinkBox.tsx | TypeScript React | 33 | 0 | 3 | 36 | +| src/client/views/nodes/PDFBox.scss | SCSS | 200 | 0 | 19 | 219 | +| src/client/views/nodes/PDFBox.tsx | TypeScript React | 246 | 0 | 19 | 265 | +| src/client/views/nodes/PresBox.scss | SCSS | 52 | 0 | 1 | 53 | +| src/client/views/nodes/PresBox.tsx | TypeScript React | 268 | 31 | 32 | 331 | +| src/client/views/nodes/QueryBox.scss | SCSS | 5 | 0 | 0 | 5 | +| src/client/views/nodes/QueryBox.tsx | TypeScript React | 37 | 0 | 4 | 41 | +| src/client/views/nodes/RadialMenu.scss | SCSS | 60 | 3 | 7 | 70 | +| src/client/views/nodes/RadialMenu.tsx | TypeScript React | 174 | 26 | 36 | 236 | +| src/client/views/nodes/RadialMenuItem.tsx | TypeScript React | 101 | 0 | 16 | 117 | +| src/client/views/nodes/ScreenshotBox.scss | SCSS | 40 | 6 | 5 | 51 | +| src/client/views/nodes/ScreenshotBox.tsx | TypeScript React | 174 | 2 | 18 | 194 | +| src/client/views/nodes/ScriptingBox.scss | SCSS | 33 | 0 | 3 | 36 | +| src/client/views/nodes/ScriptingBox.tsx | TypeScript React | 87 | 0 | 12 | 99 | +| src/client/views/nodes/SliderBox-components.tsx | TypeScript React | 220 | 12 | 25 | 257 | +| src/client/views/nodes/SliderBox-tooltip.css | CSS | 30 | 0 | 3 | 33 | +| src/client/views/nodes/SliderBox.scss | SCSS | 7 | 0 | 0 | 7 | +| src/client/views/nodes/SliderBox.tsx | TypeScript React | 117 | 0 | 8 | 125 | +| src/client/views/nodes/VideoBox.scss | SCSS | 65 | 3 | 6 | 74 | +| src/client/views/nodes/VideoBox.tsx | TypeScript React | 343 | 2 | 34 | 379 | +| src/client/views/nodes/WebBox.scss | SCSS | 109 | 0 | 18 | 127 | +| src/client/views/nodes/WebBox.tsx | TypeScript React | 345 | 15 | 35 | 395 | +| src/client/views/nodes/formattedText/DashDocCommentView.tsx | TypeScript React | 82 | 0 | 13 | 95 | +| src/client/views/nodes/formattedText/DashDocView.tsx | TypeScript React | 223 | 9 | 37 | 269 | +| src/client/views/nodes/formattedText/DashFieldView.scss | SCSS | 34 | 0 | 2 | 36 | +| src/client/views/nodes/formattedText/DashFieldView.tsx | TypeScript React | 178 | 13 | 20 | 211 | +| src/client/views/nodes/formattedText/FootnoteView.tsx | TypeScript React | 131 | 13 | 19 | 163 | +| src/client/views/nodes/formattedText/FormattedTextBox.scss | SCSS | 220 | 11 | 34 | 265 | +| src/client/views/nodes/formattedText/FormattedTextBox.tsx | TypeScript React | 1,169 | 68 | 93 | 1,330 | +| src/client/views/nodes/formattedText/FormattedTextBoxComment.scss | SCSS | 33 | 0 | 0 | 33 | +| src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | TypeScript React | 218 | 11 | 8 | 237 | +| src/client/views/nodes/formattedText/ImageResizeView.tsx | TypeScript React | 113 | 0 | 25 | 138 | +| src/client/views/nodes/formattedText/ParagraphNodeSpec.ts | TypeScript | 108 | 9 | 26 | 143 | +| src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | TypeScript | 231 | 0 | 11 | 242 | +| src/client/views/nodes/formattedText/RichTextMenu.scss | SCSS | 100 | 2 | 19 | 121 | +| src/client/views/nodes/formattedText/RichTextMenu.tsx | TypeScript React | 754 | 12 | 109 | 875 | +| src/client/views/nodes/formattedText/RichTextRules.ts | TypeScript | 308 | 7 | 5 | 320 | +| src/client/views/nodes/formattedText/RichTextSchema.tsx | TypeScript React | 465 | 33 | 39 | 537 | +| src/client/views/nodes/formattedText/SummaryView.tsx | TypeScript React | 67 | 0 | 14 | 81 | +| src/client/views/nodes/formattedText/TooltipTextMenu.scss | SCSS | 306 | 6 | 61 | 373 | +| src/client/views/nodes/formattedText/marks_rts.ts | TypeScript | 259 | 15 | 23 | 297 | +| src/client/views/nodes/formattedText/nodes_rts.ts | TypeScript | 224 | 21 | 19 | 264 | +| src/client/views/nodes/formattedText/prosemirrorPatches.js | JavaScript | 118 | 12 | 9 | 139 | +| src/client/views/nodes/formattedText/schema_rts.ts | TypeScript | 12 | 8 | 6 | 26 | +| src/client/views/pdf/Annotation.scss | SCSS | 6 | 0 | 0 | 6 | +| src/client/views/pdf/Annotation.tsx | TypeScript React | 114 | 0 | 16 | 130 | +| src/client/views/pdf/PDFMenu.scss | SCSS | 6 | 0 | 0 | 6 | +| src/client/views/pdf/PDFMenu.tsx | TypeScript React | 102 | 0 | 21 | 123 | +| src/client/views/pdf/PDFViewer.scss | SCSS | 74 | 4 | 10 | 88 | +| src/client/views/pdf/PDFViewer.tsx | TypeScript React | 662 | 23 | 46 | 731 | +| src/client/views/presentationview/PresElementBox.scss | SCSS | 93 | 0 | 10 | 103 | +| src/client/views/presentationview/PresElementBox.tsx | TypeScript React | 179 | 31 | 14 | 224 | +| src/client/views/search/CheckBox.scss | SCSS | 50 | 1 | 8 | 59 | +| src/client/views/search/CheckBox.tsx | TypeScript React | 42 | 74 | 15 | 131 | +| src/client/views/search/CollectionFilters.scss | SCSS | 17 | 0 | 3 | 20 | +| src/client/views/search/CollectionFilters.tsx | TypeScript React | 69 | 0 | 14 | 83 | +| src/client/views/search/FieldFilters.scss | SCSS | 10 | 1 | 1 | 12 | +| src/client/views/search/FieldFilters.tsx | TypeScript React | 34 | 0 | 7 | 41 | +| src/client/views/search/FilterBox.scss | SCSS | 153 | 0 | 25 | 178 | +| src/client/views/search/FilterBox.tsx | TypeScript React | 354 | 24 | 54 | 432 | +| src/client/views/search/IconBar.scss | SCSS | 9 | 0 | 1 | 10 | +| src/client/views/search/IconBar.tsx | TypeScript React | 69 | 1 | 17 | 87 | +| src/client/views/search/IconButton.scss | SCSS | 46 | 1 | 6 | 53 | +| src/client/views/search/IconButton.tsx | TypeScript React | 170 | 2 | 19 | 191 | +| src/client/views/search/NaviconButton.scss | SCSS | 58 | 0 | 11 | 69 | +| src/client/views/search/NaviconButton.tsx | TypeScript React | 32 | 0 | 5 | 37 | +| src/client/views/search/SearchBox.scss | SCSS | 203 | 82 | 51 | 336 | +| src/client/views/search/SearchBox.tsx | TypeScript React | 530 | 47 | 94 | 671 | +| src/client/views/search/SearchItem.scss | SCSS | 138 | 0 | 25 | 163 | +| src/client/views/search/SearchItem.tsx | TypeScript React | 272 | 2 | 29 | 303 | +| src/client/views/search/SelectorContextMenu.scss | SCSS | 12 | 1 | 3 | 16 | +| src/client/views/search/ToggleBar.scss | SCSS | 35 | 2 | 4 | 41 | +| src/client/views/search/ToggleBar.tsx | TypeScript React | 77 | 0 | 9 | 86 | +| src/client/views/webcam/DashWebRTCVideo.scss | SCSS | 70 | 4 | 9 | 83 | +| src/client/views/webcam/DashWebRTCVideo.tsx | TypeScript React | 67 | 6 | 16 | 89 | +| src/client/views/webcam/WebCamLogic.js | JavaScript | 234 | 7 | 51 | 292 | +| src/debug/Repl.tsx | TypeScript React | 59 | 0 | 7 | 66 | +| src/debug/Test.tsx | TypeScript React | 12 | 0 | 2 | 14 | +| src/debug/Viewer.tsx | TypeScript React | 173 | 0 | 19 | 192 | +| src/extensions/ArrayExtensions.ts | TypeScript | 26 | 5 | 6 | 37 | +| src/extensions/General/Extensions.ts | TypeScript | 7 | 0 | 2 | 9 | +| src/extensions/General/ExtensionsTypings.ts | TypeScript | 7 | 0 | 1 | 8 | +| src/extensions/StringExtensions.ts | TypeScript | 13 | 0 | 4 | 17 | +| src/mobile/ImageUpload.scss | SCSS | 30 | 0 | 4 | 34 | +| src/mobile/ImageUpload.tsx | TypeScript React | 78 | 41 | 12 | 131 | +| src/mobile/InkControls.tsx | TypeScript React | 0 | 0 | 1 | 1 | +| src/mobile/MobileInkOverlay.scss | SCSS | 33 | 1 | 5 | 39 | +| src/mobile/MobileInkOverlay.tsx | TypeScript React | 162 | 3 | 26 | 191 | +| src/mobile/MobileInterface.scss | SCSS | 17 | 0 | 2 | 19 | +| src/mobile/MobileInterface.tsx | TypeScript React | 297 | 15 | 32 | 344 | +| src/new_fields/CursorField.ts | TypeScript | 54 | 0 | 12 | 66 | +| src/new_fields/DateField.ts | TypeScript | 30 | 0 | 7 | 37 | +| src/new_fields/Doc.ts | TypeScript | 897 | 85 | 76 | 1,058 | +| src/new_fields/FieldSymbols.ts | TypeScript | 11 | 0 | 2 | 13 | +| src/new_fields/HtmlField.ts | TypeScript | 22 | 0 | 5 | 27 | +| src/new_fields/IconField.ts | TypeScript | 22 | 0 | 5 | 27 | +| src/new_fields/InkField.ts | TypeScript | 41 | 0 | 10 | 51 | +| src/new_fields/List.ts | TypeScript | 244 | 38 | 20 | 302 | +| src/new_fields/ListSpec.ts | TypeScript | 0 | 0 | 1 | 1 | +| src/new_fields/ObjectField.ts | TypeScript | 16 | 0 | 4 | 20 | +| src/new_fields/PresField.ts | TypeScript | 3 | 1 | 2 | 6 | +| src/new_fields/Proxy.ts | TypeScript | 95 | 2 | 14 | 111 | +| src/new_fields/RefField.ts | TypeScript | 17 | 0 | 5 | 22 | +| src/new_fields/RichTextField.ts | TypeScript | 33 | 0 | 8 | 41 | +| src/new_fields/RichTextUtils.ts | TypeScript | 455 | 8 | 56 | 519 | +| src/new_fields/Schema.ts | TypeScript | 107 | 5 | 8 | 120 | +| src/new_fields/SchemaHeaderField.ts | TypeScript | 104 | 4 | 14 | 122 | +| src/new_fields/ScriptField.ts | TypeScript | 137 | 21 | 19 | 177 | +| src/new_fields/Types.ts | TypeScript | 86 | 5 | 17 | 108 | +| src/new_fields/URLField.ts | TypeScript | 45 | 0 | 9 | 54 | +| src/new_fields/documentSchemas.ts | TypeScript | 87 | 0 | 6 | 93 | +| src/new_fields/util.ts | TypeScript | 176 | 3 | 15 | 194 | +| src/pen-gestures/GestureUtils.ts | TypeScript | 41 | 0 | 5 | 46 | +| src/pen-gestures/ndollar.ts | TypeScript | 356 | 172 | 22 | 550 | +| src/scraping/acm/.gitignore | Ignore | 2 | 0 | 0 | 2 | +| src/scraping/acm/debug.log | log | 38 | 0 | 1 | 39 | +| src/scraping/acm/index.js | JavaScript | 82 | 185 | 13 | 280 | +| src/scraping/acm/package.json | JSON | 17 | 0 | 1 | 18 | +| src/scraping/buxton/.idea/buxton.iml | XML | 8 | 0 | 0 | 8 | +| src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml | XML | 6 | 0 | 0 | 6 | +| src/scraping/buxton/.idea/misc.xml | XML | 4 | 0 | 0 | 4 | +| src/scraping/buxton/.idea/modules.xml | XML | 8 | 0 | 0 | 8 | +| src/scraping/buxton/.idea/vcs.xml | XML | 6 | 0 | 0 | 6 | +| src/scraping/buxton/.idea/workspace.xml | XML | 173 | 0 | 0 | 173 | +| src/scraping/buxton/final/BuxtonImporter.ts | TypeScript | 228 | 142 | 26 | 396 | +| src/scraping/buxton/jsonifier.py | Python | 183 | 1 | 48 | 232 | +| src/scraping/buxton/narratives.py | Python | 11 | 19 | 9 | 39 | +| src/scraping/buxton/narratives/chord_keyboards.json | JSON | 39 | 0 | 0 | 39 | +| src/scraping/buxton/scraper.py | Python | 350 | 5 | 78 | 433 | +| src/server/ActionUtilities.ts | TypeScript | 136 | 1 | 23 | 160 | +| src/server/ApiManagers/ApiManager.ts | TypeScript | 8 | 0 | 3 | 11 | +| src/server/ApiManagers/DeleteManager.ts | TypeScript | 71 | 0 | 11 | 82 | +| src/server/ApiManagers/DownloadManager.ts | TypeScript | 173 | 80 | 16 | 269 | +| src/server/ApiManagers/GeneralGoogleManager.ts | TypeScript | 53 | 0 | 8 | 61 | +| src/server/ApiManagers/GooglePhotosManager.ts | TypeScript | 190 | 119 | 22 | 331 | +| src/server/ApiManagers/PDFManager.ts | TypeScript | 103 | 0 | 12 | 115 | +| src/server/ApiManagers/SearchManager.ts | TypeScript | 200 | 0 | 15 | 215 | +| src/server/ApiManagers/SessionManager.ts | TypeScript | 56 | 0 | 11 | 67 | +| src/server/ApiManagers/UploadManager.ts | TypeScript | 226 | 1 | 20 | 247 | +| src/server/ApiManagers/UserManager.ts | TypeScript | 101 | 4 | 21 | 126 | +| src/server/ApiManagers/UtilManager.ts | TypeScript | 42 | 22 | 10 | 74 | +| src/server/Client.ts | TypeScript | 8 | 0 | 3 | 11 | +| src/server/DashSession/DashSessionAgent.ts | TypeScript | 155 | 52 | 23 | 230 | +| src/server/DashSession/Session/agents/applied_session_agent.ts | TypeScript | 47 | 2 | 9 | 58 | +| src/server/DashSession/Session/agents/monitor.ts | TypeScript | 213 | 59 | 26 | 298 | +| src/server/DashSession/Session/agents/process_message_router.ts | TypeScript | 24 | 10 | 7 | 41 | +| src/server/DashSession/Session/agents/promisified_ipc_manager.ts | TypeScript | 106 | 52 | 15 | 173 | +| src/server/DashSession/Session/agents/server_worker.ts | TypeScript | 99 | 46 | 15 | 160 | +| src/server/DashSession/Session/utilities/repl.ts | TypeScript | 116 | 0 | 12 | 128 | +| src/server/DashSession/Session/utilities/session_config.ts | TypeScript | 119 | 0 | 10 | 129 | +| src/server/DashSession/Session/utilities/utilities.ts | TypeScript | 24 | 8 | 5 | 37 | +| src/server/DashUploadUtils.ts | TypeScript | 285 | 52 | 30 | 367 | +| src/server/GarbageCollector.ts | TypeScript | 138 | 2 | 11 | 151 | +| src/server/IDatabase.ts | TypeScript | 17 | 0 | 8 | 25 | +| src/server/MemoryDatabase.ts | TypeScript | 87 | 0 | 14 | 101 | +| src/server/Message.ts | TypeScript | 86 | 0 | 18 | 104 | +| src/server/PdfTypes.ts | TypeScript | 19 | 0 | 2 | 21 | +| src/server/ProcessFactory.ts | TypeScript | 34 | 0 | 10 | 44 | +| src/server/Recommender.ts | TypeScript | 0 | 120 | 18 | 138 | +| src/server/RouteManager.ts | TypeScript | 187 | 4 | 19 | 210 | +| src/server/RouteSubscriber.ts | TypeScript | 21 | 0 | 5 | 26 | +| src/server/Search.ts | TypeScript | 71 | 2 | 8 | 81 | +| src/server/SharedMediaTypes.ts | TypeScript | 41 | 0 | 10 | 51 | +| src/server/Websocket/Websocket.ts | TypeScript | 263 | 5 | 46 | 314 | +| src/server/apis/google/GoogleApiServerUtils.ts | TypeScript | 172 | 168 | 25 | 365 | +| src/server/apis/google/SharedTypes.ts | TypeScript | 19 | 0 | 2 | 21 | +| src/server/apis/youtube/youtubeApiSample.d.ts | TypeScript | 2 | 0 | 0 | 2 | +| src/server/apis/youtube/youtubeApiSample.js | JavaScript | 135 | 30 | 14 | 179 | +| src/server/authentication/config/passport.ts | TypeScript | 23 | 2 | 4 | 29 | +| src/server/authentication/controllers/user_controller.ts | TypeScript | 218 | 25 | 25 | 268 | +| src/server/authentication/models/current_user_utils.ts | TypeScript | 586 | 30 | 57 | 673 | +| src/server/authentication/models/user_model.ts | TypeScript | 63 | 9 | 14 | 86 | +| src/server/credentials/CredentialsLoader.ts | TypeScript | 24 | 0 | 6 | 30 | +| src/server/credentials/google_project_credentials.json | JSON | 11 | 0 | 0 | 11 | +| src/server/database.ts | TypeScript | 312 | 0 | 38 | 350 | +| src/server/downsize.ts | TypeScript | 34 | 5 | 1 | 40 | +| src/server/index.ts | TypeScript | 107 | 36 | 16 | 159 | +| src/server/remapUrl.ts | TypeScript | 53 | 4 | 6 | 63 | +| src/server/server_Initialization.ts | TypeScript | 138 | 6 | 24 | 168 | +| src/server/slides.json | JSON | 10,820 | 0 | 0 | 10,820 | +| src/server/updateProtos.ts | TypeScript | 11 | 0 | 3 | 14 | +| src/typings/index.d.ts | TypeScript | 219 | 72 | 38 | 329 | +| test/test.ts | TypeScript | 141 | 0 | 20 | 161 | +| tsconfig.json | JSON | 21 | 5 | 0 | 26 | +| tslint.json | JSON | 30 | 32 | 1 | 63 | +| views/forgot.pug | Pug | 19 | 1 | 2 | 22 | +| views/layout.pug | Pug | 13 | 0 | 1 | 14 | +| views/login.pug | Pug | 24 | 0 | 2 | 26 | +| views/reset.pug | Pug | 20 | 0 | 2 | 22 | +| views/signup.pug | Pug | 25 | 0 | 2 | 27 | +| views/stylesheets/authentication.css | CSS | 185 | 4 | 34 | 223 | +| views/user_activity.pug | Pug | 18 | 0 | 1 | 19 | +| webpack.config.js | JavaScript | 116 | 0 | 5 | 121 | +| Total | | 224,911 | 32,987 | 15,880 | 273,778 | ++-------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bc8ca01f5..959fc41ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2723,6 +2723,43 @@ } } }, + "canvas": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz", + "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==", + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.11.0", + "simple-get": "^3.0.3" + }, + "dependencies": { + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -2848,8 +2885,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -2867,13 +2903,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2886,18 +2920,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3000,8 +3031,7 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3011,7 +3041,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3024,20 +3053,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.9.0", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3054,7 +3080,6 @@ "mkdirp": { "version": "0.5.3", "bundled": true, - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -3110,8 +3135,7 @@ }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "npm-packlist": { "version": "1.4.8", @@ -3136,8 +3160,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -3147,7 +3170,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3216,8 +3238,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -3247,7 +3268,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3265,7 +3285,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3304,13 +3323,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.1.1", - "bundled": true, - "optional": true + "bundled": true } } } diff --git a/src/Utils.ts b/src/Utils.ts index ad12c68a1..23b59ac9d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -4,7 +4,7 @@ import { Socket, Room } from 'socket.io'; import { Message } from './server/Message'; export namespace Utils { - export const DRAG_THRESHOLD = 4; + export let DRAG_THRESHOLD = 4; export function GenerateGuid(): string { return v4(); @@ -512,7 +512,7 @@ export function setupMoveUpEvents( (target as any)._downY = (target as any)._lastY = e.clientY; const _moveEvent = (e: PointerEvent): void => { - if (Math.abs(e.clientX - (target as any)._downX) > 4 || Math.abs(e.clientY - (target as any)._downY) > 4) { + if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) { if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) { document.removeEventListener("pointermove", _moveEvent); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c03d9ea1b..c48611eff 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,5 +1,5 @@ import { Doc, Field, DocListCast } from "../../new_fields/Doc"; -import { Cast, ScriptCast, StrCast } from "../../new_fields/Types"; +import { Cast, ScriptCast, StrCast, NumCast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import * as globalCssVariables from "../views/globalCssVariables.scss"; @@ -305,33 +305,20 @@ export namespace DragManager { } export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { - let thisX = e.pageX; - let thisY = e.pageY; - const currLeft = e.pageX - xFromLeft; - const currTop = e.pageY - yFromTop; - const currRight = e.pageX + xFromRight; - const currBottom = e.pageY + yFromBottom; - const closestLeft = vertSnapLines.length ? vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev) : currLeft; - const closestTop = horizSnapLines.length ? horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev) : currTop; - const closestRight = vertSnapLines.length ? vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev) : currRight; - const closestBottom = horizSnapLines.length ? horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev) : currBottom; - const distFromClosestLeft = Math.abs(e.pageX - xFromLeft - closestLeft); - const distFromClosestTop = Math.abs(e.pageY - yFromTop - closestTop); - const distFromClosestRight = Math.abs(e.pageX + xFromRight - closestRight); - const distFromClosestBottom = Math.abs(e.pageY + yFromBottom - closestBottom); - if (distFromClosestLeft < 10 && distFromClosestLeft < distFromClosestRight) { - thisX = closestLeft + xFromLeft; - } - else if (distFromClosestRight < 10) { - thisX = closestRight - xFromRight; - } - if (distFromClosestTop < 10 && distFromClosestTop < distFromClosestBottom) { - thisY = closestTop + yFromTop; - } - else if (distFromClosestBottom < 10) { - thisY = closestBottom - yFromBottom; + const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); + const snapVal = (pts: number[], drag: number, snapLines: number[]) => { + if (snapLines.length) { + const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt + const rangePts = [drag - offs[0], drag - offs[1], drag - offs[2]]; // left, mid, right or top, mid, bottom pts to try to snap to snaplines + const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest)); + const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i])); + const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2; + return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag; + } + return drag; } - return { thisX, thisY }; + + return { thisX: snapVal([xFromLeft, xFromRight], e.pageX, vertSnapLines), thisY: snapVal([yFromTop, yFromBottom], e.pageY, horizSnapLines) }; } export let docsBeingDragged: Doc[] = []; function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e5a8ebcb5..72dfdf75c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -588,12 +588,12 @@ export class MainView extends React.Component { {// TO VIEW SNAP LINES - /*
+
- {this._hLines?.map(l => )} - {this._vLines?.map(l => )} + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} -
*/} +
}
); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 11d0f298d..763a6c605 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -869,6 +869,7 @@ export class CollectionFreeFormView extends CollectionSubView { + const size = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); + const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; + const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); + const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { + if (this.intersectRect(docDims(doc), rect)) { + snappableDocs.push(doc); + } + } + const snappableDocs: Doc[] = []; // the set of documents in the visible viewport that we will try to snap to; + const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; + this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to + !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs + + const horizLines: number[] = []; + const vertLines: number[] = []; + snappableDocs.filter(doc => !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { + const { left, top, width, height } = docDims(doc); + const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); + const docSize = this.getTransform().inverse().transformDirection(width, height); + + horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line + vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line + }); + DragManager.SetSnapLines(horizLines, vertLines); + } onPointerOver = (e: React.PointerEvent) => { if (SelectionManager.GetIsDragging()) { - const size = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); - const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; - const selection: Doc[] = []; - const docDims = (doc: Doc, layoutDoc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) }); - const compareDoc = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { - if (this.intersectRect(docDims(doc, Doc.Layout(doc)), rect)) { - selection.push(doc); - } - } - const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => compareDoc(doc, selRect)); // first try foreground docs - !selection.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => compareDoc(doc, selRect)); // then background docs - !selection.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => compareDoc(doc, otherBounds)); // then floating docs - - const horizLines: number[] = []; - const vertLines: number[] = []; - selection.filter(doc => !DragManager.docsBeingDragged.includes(doc)).forEach(doc => { - const { left, top, width, height } = docDims(doc, Doc.Layout(doc)); - const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); - const docSize = this.getTransform().inverse().transformDirection(width, height); - - horizLines.push(topLeftInScreen[1]); // top line - horizLines.push(topLeftInScreen[1] + docSize[1]); // bottom line - horizLines.push(topLeftInScreen[1] + docSize[1] / 2); // horiz center line - vertLines.push(topLeftInScreen[0]);//left line - vertLines.push(topLeftInScreen[0] + docSize[0]);// right line - vertLines.push(topLeftInScreen[0] + docSize[0] / 2);// vert center line - }); - DragManager.SetSnapLines(horizLines, vertLines); + this.setupDragLines(e); } e.stopPropagation(); } @@ -1273,12 +1273,12 @@ export class CollectionFreeFormView extends CollectionSubView
{// uncomment to show snap lines - /*
- - {this._hLines?.map(l => )} - {this._vLines?.map(l => )} - -
*/} +
+ + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} + +
}
; } } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 422710c3e..1b22ed4cd 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -34,8 +34,6 @@ export class DashFieldView { docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} - view={view} - getPos={getPos} tbox={tbox} />, this._fieldWrapper); (this as any).dom = this._fieldWrapper; @@ -49,8 +47,6 @@ export class DashFieldView { interface IDashFieldViewInternal { fieldKey: string; docid: string; - view: any; - getPos: any; tbox: FormattedTextBox; width: number; height: number; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index e49cc4804..663343f47 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,5 +1,6 @@ import { action, computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; +import { Utils } from "../../../Utils"; import { DocServer } from "../../../client/DocServer"; import { Docs, DocumentOptions } from "../../../client/documents/Documents"; import { UndoManager } from "../../../client/util/UndoManager"; @@ -7,7 +8,7 @@ import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, PromiseValue, StrCast } from "../../../new_fields/Types"; +import { Cast, PromiseValue, StrCast, NumCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { nullAudio, ImageField } from "../../../new_fields/URLField"; import { DragManager } from "../../../client/util/DragManager"; @@ -628,6 +629,9 @@ export class CurrentUserUtils { new InkingControl(); doc.title = Doc.CurrentUserEmail; doc.activePen = doc; + 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.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing -- cgit v1.2.3-70-g09d2 From 8dcc7bff9430fe3cca8271501595ff89b36f7005 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 23:20:13 -0400 Subject: cleaned up presbox and preselementbox --- src/client/views/MainView.tsx | 9 +- src/client/views/nodes/PresBox.scss | 29 +++- src/client/views/nodes/PresBox.tsx | 167 ++++++++++----------- .../views/nodes/formattedText/RichTextMenu.scss | 40 ++--- .../views/presentationview/PresElementBox.scss | 43 +++--- .../views/presentationview/PresElementBox.tsx | 106 ++++++------- 6 files changed, 200 insertions(+), 194 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 72dfdf75c..65e4eb036 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTerminal, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; +import { faTerminal, faFile as fileSolid, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -104,6 +104,11 @@ export class MainView extends React.Component { } library.add(faTerminal); + library.add(faLocationArrow); + library.add(faSearch); + library.add(fileSolid); + library.add(faFileDownload); + library.add(faStop); library.add(faCalculator); library.add(faWindowMaximize); library.add(faFileAlt); @@ -142,6 +147,8 @@ export class MainView extends React.Component { library.add(faCaretUp); library.add(faFilter); library.add(faBullseye); + library.add(faArrowLeft); + library.add(faArrowRight); library.add(faArrowDown); library.add(faArrowUp); library.add(faCloudUploadAlt); diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 78c19f351..d48000e16 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -1,10 +1,10 @@ .presBox-cont { position: absolute; + pointer-events: inherit; z-index: 2; box-shadow: #AAAAAA .2vw .2vw .4vw; - bottom: 0; width: 100%; - min-width: 120px; + min-width: 20px; height: 100%; min-height: 41px; letter-spacing: 2px; @@ -17,17 +17,36 @@ width: 100%; } .presBox-buttons { - padding: 10px; width: 100%; background: gray; - padding-right: 10px; padding-top: 5px; padding-bottom: 5px; + display: grid; + grid-column-end: 4; + grid-column-start: 1; + .presBox-viewPicker { + height: 25; + position: relative; + display: inline-block; + grid-column: 1/2; + min-width: 15px; + } + select { + background: #323232; + color: white; + } .presBox-button { margin-right: 2.5%; margin-left: 2.5%; - width: 20%; + height: 25px; border-radius: 5px; + display: flex; + align-items: center; + background: #323232; + color: white; + svg { + margin: auto; + } } .collectionViewBaseChrome-viewPicker { min-width: 50; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 80d043db1..f91a809bb 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,12 +1,10 @@ import React = require("react"); -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faHandPointLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, DocCastAsync } from "../../../new_fields/Doc"; import { InkTool } from "../../../new_fields/InkField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { returnFalse } from "../../../Utils"; import { documentSchema } from "../../../new_fields/documentSchemas"; import { DocumentManager } from "../../util/DocumentManager"; @@ -19,38 +17,30 @@ import "./PresBox.scss"; import { ViewBoxBaseComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -library.add(faArrowLeft); -library.add(faArrowRight); -library.add(faPlay); -library.add(faStop); -library.add(faHandPointLeft); -library.add(faPlus); -library.add(faTimes); -library.add(faMinus); -library.add(faEdit); - type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @observer export class PresBox extends ViewBoxBaseComponent(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } - _childReaction: IReactionDisposer | undefined; + private _childReaction: IReactionDisposer | undefined; @observable _isChildActive = false; + @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } + @computed get currentIndex() { return NumCast(this.rootDoc._itemIndex); } + componentDidMount() { - this.layoutDoc._forceRenderEngine = "timeline"; - this.layoutDoc._replacedChrome = "replaced"; + this.rootDoc._forceRenderEngine = "timeline"; + this.rootDoc._replacedChrome = "replaced"; this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true }); } componentWillUnmount() { this._childReaction?.(); } - @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } - @computed get currentIndex() { return NumCast(this.layoutDoc._itemIndex); } - - updateCurrentPresentation = action(() => Doc.UserDoc().activePresentation = this.rootDoc); + updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc; + @undoBatch + @action next = () => { this.updateCurrentPresentation(); if (this.childDocs[this.currentIndex + 1] !== undefined) { @@ -66,6 +56,9 @@ export class PresBox extends ViewBoxBaseComponent } } } + + @undoBatch + @action back = () => { this.updateCurrentPresentation(); const docAtCurrent = this.childDocs[this.currentIndex]; @@ -83,10 +76,6 @@ export class PresBox extends ViewBoxBaseComponent } } - whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); - active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) && - (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) - /** * This is the method that checks for the actions that need to be performed * after the document has been presented, which involves 3 button options: @@ -95,15 +84,16 @@ export class PresBox extends ViewBoxBaseComponent showAfterPresented = (index: number) => { this.updateCurrentPresentation(); this.childDocs.forEach((doc, ind) => { + const presTargetDoc = doc.presentationTargetDoc as Doc; //the order of cases is aligned based on priority - if (doc.hideTillShownButton && ind <= index) { - (doc.presentationTargetDoc as Doc).opacity = 1; + if (doc.presHideTillShownButton && ind <= index) { + presTargetDoc.opacity = 1; } - if (doc.hideAfterButton && ind < index) { - (doc.presentationTargetDoc as Doc).opacity = 0; + if (doc.presHideAfterButton && ind < index) { + presTargetDoc.opacity = 0; } - if (doc.fadeButton && ind < index) { - (doc.presentationTargetDoc as Doc).opacity = 0.5; + if (doc.presFadeButton && ind < index) { + presTargetDoc.opacity = 0.5; } }); } @@ -117,15 +107,15 @@ export class PresBox extends ViewBoxBaseComponent this.updateCurrentPresentation(); this.childDocs.forEach((key, ind) => { //the order of cases is aligned based on priority - + const presTargetDoc = key.presentationTargetDoc as Doc; if (key.hideAfterButton && ind >= index) { - (key.presentationTargetDoc as Doc).opacity = 1; + presTargetDoc.opacity = 1; } if (key.fadeButton && ind >= index) { - (key.presentationTargetDoc as Doc).opacity = 1; + presTargetDoc.opacity = 1; } if (key.hideTillShownButton && ind > index) { - (key.presentationTargetDoc as Doc).opacity = 0; + presTargetDoc.opacity = 0; } }); } @@ -151,11 +141,11 @@ export class PresBox extends ViewBoxBaseComponent } currentDocGroups.forEach((doc: Doc, index: number) => { - if (doc.navButton) { + if (doc.presNavButton) { docToJump = doc; willZoom = false; } - if (doc.zoomButton) { + if (doc.presZoomButton) { docToJump = doc; willZoom = true; } @@ -167,9 +157,9 @@ export class PresBox extends ViewBoxBaseComponent if (docToJump === curDoc) { //checking if curDoc has navigation open const target = await DocCastAsync(curDoc.presentationTargetDoc); - if (curDoc.navButton && target) { + if (curDoc.presNavButton && target) { DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext); - } else if (curDoc.zoomButton && target) { + } else if (curDoc.presZoomButton && target) { //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext); } @@ -181,18 +171,13 @@ export class PresBox extends ViewBoxBaseComponent } - @undoBatch - public removeDocument = (doc: Doc) => { - return Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); - } - //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. public gotoDocument = (index: number, fromDoc: number) => { this.updateCurrentPresentation(); Doc.UnBrushAllDocs(); if (index >= 0 && index < this.childDocs.length) { - this.layoutDoc._itemIndex = index; + this.rootDoc._itemIndex = index; if (!this.layoutDoc.presStatus) { this.layoutDoc.presStatus = true; @@ -217,19 +202,12 @@ export class PresBox extends ViewBoxBaseComponent } } - addDocument = (doc: Doc) => { - const newPinDoc = Doc.MakeAlias(doc); - newPinDoc.presentationTargetDoc = doc; - return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc); - } - - //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { this.updateCurrentPresentation(); this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1); - this.layoutDoc._itemIndex = 0; + this.rootDoc._itemIndex = 0; this.layoutDoc.presStatus = false; } @@ -238,84 +216,99 @@ export class PresBox extends ViewBoxBaseComponent startPresentation = (startIndex: number) => { this.updateCurrentPresentation(); this.childDocs.map(doc => { - if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) { - (doc.presentationTargetDoc as Doc).opacity = 0; + const presTargetDoc = doc.presentationTargetDoc as Doc; + if (doc.presHideTillShownButton && this.childDocs.indexOf(doc) > startIndex) { + presTargetDoc.opacity = 0; } - if (doc.hideAfterButton && this.childDocs.indexOf(doc) < startIndex) { - (doc.presentationTargetDoc as Doc).opacity = 0; + if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) { + presTargetDoc.opacity = 0; } - if (doc.fadeButton && this.childDocs.indexOf(doc) < startIndex) { - (doc.presentationTargetDoc as Doc).opacity = 0.5; + if (doc.presFadeButton && this.childDocs.indexOf(doc) < startIndex) { + presTargetDoc.opacity = 0.5; } }); } - updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: CollectionViewType) => { + updateMinimize = action((e: React.ChangeEvent, mode: CollectionViewType) => { if (BoolCast(this.layoutDoc.inOverlay) !== (mode === CollectionViewType.Invalid)) { if (this.layoutDoc.inOverlay) { Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); CollectionDockingView.AddRightSplit(this.rootDoc); this.layoutDoc.inOverlay = false; } else { - this.layoutDoc.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25; - this.layoutDoc.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25; + const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + this.rootDoc.x = pt[0];// 500;//e.clientX + 25; + this.rootDoc.y = pt[1];////e.clientY - 25; this.props.addDocTab?.(this.rootDoc, "close"); Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); } } - })); + }); initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => { const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; docList.forEach(doc => { doc.presBox = this.rootDoc; // give contained documents a reference to the presentation - doc.collapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din + doc.presCollapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din }); } - selectElement = (doc: Doc) => { - this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.layoutDoc._itemIndex)); + addDocument = (doc: Doc) => { + const newPinDoc = Doc.MakeAlias(doc); + newPinDoc.presentationTargetDoc = doc; + return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc); } - getTransform = () => { - return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight - } - panelHeight = () => { - return this.props.PanelHeight() - 20; - } + removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); + + selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.rootDoc._itemIndex)); + + getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight + + panelHeight = () => this.props.PanelHeight() - 20; + + active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) && + (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false); + + whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore - this.layoutDoc._viewType = e.target.selectedOptions[0].value; - this.layoutDoc._viewType === CollectionViewType.Stacking && (this.layoutDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here - this.updateMinimize(e, StrCast(this.layoutDoc._viewType)); + const viewType = e.target.selectedOptions[0].value as CollectionViewType; + viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here + this.updateMinimize(e, this.rootDoc._viewType = viewType); }); - childLayoutTemplate = () => this.layoutDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined; + childLayoutTemplate = () => this.rootDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined; render() { - const mode = StrCast(this.layoutDoc._viewType) as CollectionViewType; + const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; this.initializeViewAliases(this.childDocs, mode); - return
-
- e.stopPropagation()} onChange={this.viewChanged} value={mode}> - - - - + + + + - - - +
+
+ +
{mode !== CollectionViewType.Invalid ? ; @@ -48,16 +38,14 @@ export class PresElementBox extends ViewBoxBaseComponent [this.presElementDoc.expandInlineButton, this.presElementDoc.collapsedHeight], - params => this.presLayoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); + this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.rootDoc.presCollapsedHeight], + params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { this._heightDisposer?.(); @@ -70,8 +58,8 @@ export class PresElementBox extends ViewBoxBaseComponent { e.stopPropagation(); - this.presElementDoc.hideTillShownButton = !this.presElementDoc.hideTillShownButton; - if (!this.presElementDoc.hideTillShownButton) { + this.rootDoc.presHideTillShownButton = !this.rootDoc.presHideTillShownButton; + if (!this.rootDoc.presHideTillShownButton) { if (this.indexInPres >= this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 1; } @@ -90,13 +78,13 @@ export class PresElementBox extends ViewBoxBaseComponent { e.stopPropagation(); - this.presElementDoc.hideAfterButton = !this.presElementDoc.hideAfterButton; - if (!this.presElementDoc.hideAfterButton) { + this.rootDoc.presHideAfterButton = !this.rootDoc.presHideAfterButton; + if (!this.rootDoc.presHideAfterButton) { if (this.indexInPres <= this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 1; } } else { - if (this.presElementDoc.fadeButton) this.presElementDoc.fadeButton = false; + if (this.rootDoc.presFadeButton) this.rootDoc.presFadeButton = false; if (this.presBoxDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 0; } @@ -111,13 +99,13 @@ export class PresElementBox extends ViewBoxBaseComponent { e.stopPropagation(); - this.presElementDoc.fadeButton = !this.presElementDoc.fadeButton; - if (!this.presElementDoc.fadeButton) { + this.rootDoc.presFadeButton = !this.rootDoc.presFadeButton; + if (!this.rootDoc.presFadeButton) { if (this.indexInPres <= this.currentIndex && this.targetDoc) { this.targetDoc.opacity = 1; } } else { - this.presElementDoc.hideAfterButton = false; + this.rootDoc.presHideAfterButton = false; if (this.presBoxDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) { this.targetDoc.opacity = 0.5; } @@ -130,11 +118,11 @@ export class PresElementBox extends ViewBoxBaseComponent { e.stopPropagation(); - this.presElementDoc.navButton = !this.presElementDoc.navButton; - if (this.presElementDoc.navButton) { - this.presElementDoc.zoomButton = false; + this.rootDoc.presNavButton = !this.rootDoc.presNavButton; + if (this.rootDoc.presNavButton) { + this.rootDoc.presZoomButton = false; if (this.currentIndex === this.indexInPres) { - this.props.focus(this.presElementDoc); + this.props.focus(this.rootDoc); } } } @@ -146,13 +134,11 @@ export class PresElementBox extends ViewBoxBaseComponent { e.stopPropagation(); - this.presElementDoc.zoomButton = !this.presElementDoc.zoomButton; - if (!this.presElementDoc.zoomButton) { - this.presElementDoc.viewScale = 1; - } else { - this.presElementDoc.navButton = false; + this.rootDoc.presZoomButton = !this.rootDoc.presZoomButton; + if (this.rootDoc.presZoomButton) { + this.rootDoc.presNavButton = false; if (this.currentIndex === this.indexInPres) { - this.props.focus(this.presElementDoc); + this.props.focus(this.rootDoc); } } } @@ -161,15 +147,15 @@ export class PresElementBox extends ViewBoxBaseComponent [xCord, yCord]; - embedHeight = () => this.props.PanelHeight() - NumCast(this.presElementDoc.collapsedHeight); + embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - NumCast(this.rootDoc.presCollapsedHeight)); embedWidth = () => this.props.PanelWidth() - 20; /** * The function that is responsible for rendering the a preview or not for this * presentation element. */ - renderEmbeddedInline = () => { - return !this.presElementDoc.expandInlineButton || !this.targetDoc ? (null) : -
+ @computed get renderEmbeddedInline() { + return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) : +
{ this.props.focus(this.presElementDoc); e.stopPropagation(); }}> + onClick={e => { this.props.focus(this.rootDoc); e.stopPropagation(); }}> {treecontainer ? (null) : <> {`${this.indexInPres + 1}. ${this.targetDoc?.title}`} - +
} - - - - - - - - -
- {this.renderEmbeddedInline()} +
+ + + + + + + +
+ {this.renderEmbeddedInline}
); } -- cgit v1.2.3-70-g09d2 From ff7c7d40b1fcdf74b539c7d97f36707ff1521d2e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 1 May 2020 01:46:07 -0400 Subject: fixed presentations to allow drag and drop. fixed pres box to use RenderData instead of modifying presentation elements with unnecessary info like their containing PresBox and their presentation index position. COnverted COntentFIttingDocumentView to use DocumentView's props --- src/client/documents/Documents.ts | 1 + src/client/views/MainView.tsx | 4 +- src/client/views/SearchDocBox.tsx | 10 ++++- .../views/collections/CollectionCarouselView.tsx | 8 +++- .../views/collections/CollectionSchemaView.tsx | 14 ++++--- .../views/collections/CollectionStackingView.tsx | 15 ++++--- .../views/collections/CollectionTreeView.tsx | 20 +++++---- src/client/views/collections/CollectionView.tsx | 14 ++++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- .../CollectionMulticolumnView.tsx | 14 ++++--- .../CollectionMultirowView.tsx | 14 ++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 8 ++-- .../views/nodes/ContentFittingDocumentView.tsx | 49 ++++------------------ src/client/views/nodes/DocumentBox.tsx | 15 ++++--- src/client/views/nodes/DocumentView.tsx | 20 ++++----- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/PresBox.tsx | 21 ++++------ .../formattedText/FormattedTextBoxComment.tsx | 12 ++++-- .../views/nodes/formattedText/RichTextMenu.scss | 3 ++ .../views/presentationview/PresElementBox.tsx | 25 +++++++---- src/client/views/search/SearchItem.tsx | 12 ++++-- src/new_fields/documentSchemas.ts | 2 +- .../authentication/models/current_user_utils.ts | 2 +- 23 files changed, 150 insertions(+), 137 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2e81d5fa6..228a6af97 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -81,6 +81,7 @@ export interface DocumentOptions { author?: string; dropAction?: dropActionType; childDropAction?: dropActionType; + targetDropAction?: dropActionType; layoutKey?: string; type?: string; title?: string; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 65e4eb036..a29a6baac 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -597,8 +597,8 @@ export class MainView extends React.Component { {// TO VIEW SNAP LINES
- {this._hLines?.map(l => )} - {this._vLines?.map(l => )} + {this._hLines?.map((l: any) => )} + {this._vLines?.map((l: any) => )}
} diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx index 799fa9d85..7bd689b19 100644 --- a/src/client/views/SearchDocBox.tsx +++ b/src/client/views/SearchDocBox.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { returnFalse } from "../../Utils"; +import { returnFalse, returnZero } from "../../Utils"; import { Docs } from "../documents/Documents"; import { SearchUtil } from "../util/SearchUtil"; import { EditableView } from "./EditableView"; @@ -399,7 +399,13 @@ export class SearchDocBox extends React.Component { + bringToFront={returnFalse} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + NativeWidth={returnZero} + NativeHeight={returnZero} + parentActive={this.props.active} + ScreenToLocalTransform={this.props.ScreenToLocalTransform}>
; const CarouselDocument = makeInterface(documentSchema); @@ -49,9 +50,12 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + bringToFront={returnFalse} + parentActive={this.props.active} + />
doc) { {!this.previewDocument ? (null) : doc) { rootSelected={this.rootSelected} PanelWidth={this.previewWidth} PanelHeight={this.previewHeight} - getTransform={this.getPreviewTransform} - CollectionDoc={this.props.CollectionView?.props.Document} - CollectionView={this.props.CollectionView} + ScreenToLocalTransform={this.getPreviewTransform} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} moveDocument={this.props.moveDocument} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} - active={this.props.active} + parentActive={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} />}
; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 556d7df5c..6c230d5b1 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; -import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils"; +import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils"; import { DragManager, dropActionType } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; @@ -165,12 +165,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const height = () => this.getDocHeight(doc); return doc) { dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} - getTransform={dxf} + ScreenToLocalTransform={dxf} focus={this.props.focus} - CollectionDoc={this.props.CollectionView?.props.Document} - CollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} - active={this.props.active} + parentActive={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.addDocTab} + bringToFront={returnFalse} + ContentScaling={returnOne} pinToPres={this.props.pinToPres} />; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index d938bd7ad..71358a8ec 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -353,27 +353,31 @@ class TreeView extends React.Component { return
+ pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} + />
; } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 8d8c321e8..561226de5 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -113,12 +113,16 @@ export class CollectionView extends Touchable { @action.bound addDocument(doc: Doc): boolean { - const targetDataDoc = this.props.Document[DataSym]; - const docList = DocListCast(targetDataDoc[this.props.fieldKey]); - !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there - // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + if (this.props.addDocument) { + this.props.addDocument(doc); + } else { + const targetDataDoc = this.props.Document[DataSym]; + const docList = DocListCast(targetDataDoc[this.props.fieldKey]); + !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there + // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + } doc.context = this.props.Document; - targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); Doc.GetProto(doc).lastOpened = new DateField; return true; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 763a6c605..b4eb22444 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -877,6 +877,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (SelectionManager.GetIsDragging()) { - this.setupDragLines(e); + this.setupDragLines(); } e.stopPropagation(); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 66d441115..b3a6a9deb 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -14,7 +14,7 @@ import "./collectionMulticolumnView.scss"; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; import { List } from '../../../../new_fields/List'; -import { returnZero } from '../../../../Utils'; +import { returnZero, returnFalse, returnOne } from '../../../../Utils'; type MulticolumnDocument = makeInterface<[typeof documentSchema]>; const MulticolumnDocument = makeInterface(documentSchema); @@ -216,7 +216,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ; } /** diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 615efdb39..0fb29ca61 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -6,7 +6,7 @@ import * as React from "react"; import { Doc } from '../../../../new_fields/Doc'; import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types'; import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; -import { Utils, returnZero } from '../../../../Utils'; +import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils'; import "./collectionMultirowView.scss"; import { computed, trace, observable, action } from 'mobx'; import { Transform } from '../../../util/Transform'; @@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ; } /** diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 1c7d116c5..24468dcc1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -115,13 +115,11 @@ export class CollectionFreeFormDocumentView extends DocComponent : } diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index d0b0c8ee6..637fd5acc 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -3,51 +3,16 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import "react-table/react-table.css"; import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; import { emptyFunction, returnOne } from "../../../Utils"; -import { Transform } from "../../util/Transform"; -import { CollectionView } from "../collections/CollectionView"; import '../DocumentDecorations.scss'; -import { DocumentView } from "../nodes/DocumentView"; +import { DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import "./ContentFittingDocumentView.scss"; -import { dropActionType } from "../../util/DragManager"; -interface ContentFittingDocumentViewProps { - Document: Doc; - DataDocument?: Doc; - LayoutDoc?: () => Opt; - NativeWidth?: () => number; - NativeHeight?: () => number; - FreezeDimensions?: boolean; - LibraryPath: Doc[]; - renderDepth: number; - fitToBox?: boolean; - layoutKey?: string; - dropAction?: dropActionType; - PanelWidth: () => number; - PanelHeight: () => number; - focus?: (doc: Doc) => void; - CollectionView?: CollectionView; - CollectionDoc?: Doc; - onClick?: ScriptField; - onDoubleClick?: ScriptField; - backgroundColor?: (doc: Doc) => string | undefined; - getTransform: () => Transform; - addDocument?: (document: Doc) => boolean; - moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean; - removeDocument?: (document: Doc) => boolean; - active: (outsideReaction: boolean) => boolean; - whenActiveChanged: (isActive: boolean) => void; - addDocTab: (document: Doc, where: string) => boolean; - pinToPres: (document: Doc) => void; - dontRegisterView?: boolean; - rootSelected: (outsideReaction?: boolean) => boolean; -} @observer -export class ContentFittingDocumentView extends React.Component{ +export class ContentFittingDocumentView extends React.Component{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); } @computed get freezeDimensions() { return this.props.FreezeDimensions; } @@ -68,7 +33,7 @@ export class ContentFittingDocumentView extends React.Component this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); + private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; } private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; } @@ -90,7 +55,7 @@ export class ContentFittingDocumentView extends React.Component ; return contents; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 085637440..f555d6eef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -77,6 +77,7 @@ export interface DocumentViewProps { setupDragLines?: () => void; renderDepth: number; ContentScaling: () => number; + RenderData?: () => Doc; PanelWidth: () => number; PanelHeight: () => number; pointerEvents?: boolean; @@ -992,6 +993,7 @@ export class DocumentView extends DocComponent(Docu LayoutDoc={this.props.LayoutDoc} makeLink={this.makeLink} rootSelected={this.rootSelected} + RenderData={this.props.RenderData} dontRegisterView={this.props.dontRegisterView} fitToBox={this.props.fitToBox} LibraryPath={this.props.LibraryPath} @@ -1112,17 +1114,13 @@ export class DocumentView extends DocComponent(Docu Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); } } - @observable _animate = 0; + @observable _animateScalingTo = 0; switchViews = action((custom: boolean, view: string) => { - SelectionManager.SetIsDragging(true); - this._animate = 0.1; + this._animateScalingTo = 0.1; // shrink doc setTimeout(action(() => { this.setCustomView(custom, view); - this._animate = 1; - setTimeout(action(() => { - this._animate = 0; - SelectionManager.SetIsDragging(false); - }), 400); + this._animateScalingTo = 1; // expand it + setTimeout(action(() => this._animateScalingTo = 0), 400); }), 400); }); @@ -1156,9 +1154,9 @@ export class DocumentView extends DocComponent(Docu !entered && Doc.UnBrushDoc(this.props.Document); })} style={{ - transformOrigin: this._animate ? "center center" : undefined, - transform: this._animate ? `scale(${this._animate})` : undefined, - transition: !this._animate ? StrCast(this.Document.transition) : this._animate < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out", + transformOrigin: this._animateScalingTo ? "center center" : undefined, + transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined, + transition: !this._animateScalingTo ? StrCast(this.Document.transition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out", pointerEvents: this.ignorePointerEvents ? "none" : undefined, color: StrCast(this.layoutDoc.color, "inherit"), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 0b9edbcd3..1efee4f5a 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -56,6 +56,7 @@ export interface FieldViewProps { width?: number; background?: string; color?: string; + RenderData?: () => Doc; } @observer diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index f91a809bb..3fcc97473 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -16,6 +16,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; import { ViewBoxBaseComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; +import { List } from "../../../new_fields/List"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -23,20 +24,15 @@ const PresBoxDocument = makeInterface(documentSchema); @observer export class PresBox extends ViewBoxBaseComponent(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } - private _childReaction: IReactionDisposer | undefined; @observable _isChildActive = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @computed get currentIndex() { return NumCast(this.rootDoc._itemIndex); } componentDidMount() { + this.rootDoc.presBox = this.rootDoc; this.rootDoc._forceRenderEngine = "timeline"; this.rootDoc._replacedChrome = "replaced"; - this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true }); } - componentWillUnmount() { - this._childReaction?.(); - } - updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc; @undoBatch @@ -247,16 +243,12 @@ export class PresBox extends ViewBoxBaseComponent initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => { const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; - docList.forEach(doc => { - doc.presBox = this.rootDoc; // give contained documents a reference to the presentation - doc.presCollapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din - }); + this.rootDoc.presCollapsedHeight = hgt; } addDocument = (doc: Doc) => { - const newPinDoc = Doc.MakeAlias(doc); - newPinDoc.presentationTargetDoc = doc; - return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc); + doc.presentationTargetDoc = doc.aliasOf; + return Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); } removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); @@ -280,8 +272,10 @@ export class PresBox extends ViewBoxBaseComponent this.updateMinimize(e, this.rootDoc._viewType = viewType); }); + returnSelf = () => this.rootDoc; childLayoutTemplate = () => this.rootDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined; render() { + this.rootDoc.presOrderedDocs = new List(this.childDocs.map((child, i) => child)); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; this.initializeViewAliases(this.childDocs, mode); return
@@ -314,6 +308,7 @@ export class PresBox extends ViewBoxBaseComponent childLayoutTemplate={this.childLayoutTemplate} addDocument={this.addDocument} removeDocument={returnFalse} + RenderData={this.returnSelf} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> : (null) diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index f9e4c5210..9ad5aafb8 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; import { Doc, DocCastAsync } from "../../../../new_fields/Doc"; import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import { schema } from "./schema_rts"; @@ -192,18 +192,24 @@ export class FormattedTextBoxComment { fitToBox={true} moveDocument={returnFalse} rootSelected={returnFalse} - getTransform={Transform.Identity} - active={returnFalse} + ScreenToLocalTransform={Transform.Identity} + parentActive={returnFalse} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} dontRegisterView={true} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} renderDepth={1} PanelWidth={() => Math.min(350, NumCast(target._width, 350))} PanelHeight={() => Math.min(250, NumCast(target._height, 250))} focus={emptyFunction} whenActiveChanged={returnFalse} + bringToFront={returnFalse} + ContentScaling={returnOne} + NativeWidth={returnZero} + NativeHeight={returnZero} />, FormattedTextBoxComment.tooltipText); FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss index 3a16171de..7a0718c16 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.scss +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -55,6 +55,9 @@ color: black; } +} + +.richTextMenu { select { background-color: #323232; color: white; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 66f251b93..1887c8d45 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DataSym } from "../../../new_fields/Doc"; +import { Doc, DataSym, DocListCast } from "../../../new_fields/Doc"; import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; import { createSchema, makeInterface } from '../../../new_fields/Schema'; import { Cast, NumCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionView'; import { ViewBoxBaseComponent } from '../DocComponent'; @@ -38,13 +38,14 @@ export class PresElementBox extends ViewBoxBaseComponent d === this.rootDoc); } + @computed get presBoxDoc() { return Cast(this.props.RenderData?.().presBox, Doc) as Doc; } @computed get targetDoc() { return this.rootDoc.presentationTargetDoc as Doc; } @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); } + @computed get collapsedHeight() { return NumCast(this.presBoxDoc?.presCollapsedHeight); } componentDidMount() { - this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.rootDoc.presCollapsedHeight], + this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { @@ -147,7 +148,7 @@ export class PresElementBox extends ViewBoxBaseComponent [xCord, yCord]; - embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - NumCast(this.rootDoc.presCollapsedHeight)); + embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); embedWidth = () => this.props.PanelWidth() - 20; /** * The function that is responsible for rendering the a preview or not for this @@ -158,7 +159,7 @@ export class PresElementBox extends ViewBoxBaseComponent
; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index fe2000700..96f43e931 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, SetupDrag } from "../../util/DragManager"; @@ -164,14 +164,20 @@ export class SearchItem extends React.Component { removeDocument={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} - getTransform={Transform.Identity} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + ScreenToLocalTransform={Transform.Identity} renderDepth={1} PanelWidth={returnXDimension} PanelHeight={returnYDimension} + NativeWidth={returnZero} + NativeHeight={returnZero} focus={emptyFunction} moveDocument={returnFalse} - active={returnFalse} + parentActive={returnFalse} whenActiveChanged={returnFalse} + bringToFront={returnFalse} + ContentScaling={returnOne} />
; return docview; diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 5ca0d681e..cd4b9d591 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -9,7 +9,7 @@ export const documentSchema = createSchema({ layoutKey: "string", // holds the field key for the field that actually holds the current lyoat title: "string", // document title (can be on either data document or layout) 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") + targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy") _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents _nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index d7cc1e6bf..f37538252 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -578,7 +578,7 @@ export class CurrentUserUtils { } if (doc.activePresentation === undefined) { doc.activePresentation = Docs.Create.PresDocument(new List(), { - title: "Presentation", _viewType: CollectionViewType.Stacking, + title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); } -- cgit v1.2.3-70-g09d2 From 3d5bc514936ba804eaa24e9ca1f4619bcaf81eb5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 3 May 2020 23:59:35 -0400 Subject: extended documentBox's to support drag and drop of style. reorganized imports to avoid some cycles --- src/client/documents/Documents.ts | 10 +- src/client/util/DragManager.ts | 112 +++++++-------------- src/client/util/DropConverter.ts | 2 + src/client/util/SelectionManager.ts | 5 - src/client/views/DocumentDecorations.tsx | 5 +- src/client/views/MainView.tsx | 8 +- .../views/collections/CollectionDockingView.tsx | 5 +- .../collections/CollectionMasonryViewFieldRow.tsx | 2 +- .../views/collections/CollectionPileView.tsx | 5 +- .../views/collections/CollectionSchemaCells.tsx | 2 +- .../CollectionSchemaMovableTableHOC.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 4 +- .../CollectionStackingViewFieldColumn.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 4 +- .../CollectionFreeFormLinksView.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- src/client/views/linking/LinkMenuGroup.tsx | 4 +- src/client/views/linking/LinkMenuItem.tsx | 39 ++++++- src/client/views/nodes/AudioBox.tsx | 10 +- .../views/nodes/ContentFittingDocumentView.tsx | 8 +- src/client/views/nodes/DocumentBox.scss | 3 +- src/client/views/nodes/DocumentBox.tsx | 57 +++++++---- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/QueryBox.tsx | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 13 ++- .../authentication/models/current_user_utils.ts | 27 +++-- 26 files changed, 189 insertions(+), 158 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8ea8ded0f..45687f597 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -139,6 +139,7 @@ export interface DocumentOptions { ischecked?: ScriptField; // returns whether a font icon box is checked activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; + onDoubleClick?: ScriptField; onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked onPointerDown?: ScriptField; @@ -493,7 +494,7 @@ export namespace Docs { const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey); const viewDoc = Doc.MakeDelegate(dataDoc, delegId); - viewDoc.type !== DocumentType.LINK && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "audio timeline")); + viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc); return Doc.assign(viewDoc, delegateProps, true); } @@ -645,7 +646,7 @@ export namespace Docs { } export function DocumentDocument(document?: Doc, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options }); } export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { @@ -1005,6 +1006,11 @@ export namespace DocUtils { }); } + export let ActiveRecordings: Doc[] = []; + + export function MakeLinkToActiveAudio(doc: Doc) { + DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline")); + } export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 041f2fc2c..d91eb60ef 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,26 +1,16 @@ -import { Doc, Field, DocListCast } from "../../new_fields/Doc"; -import { Cast, ScriptCast, StrCast, NumCast } from "../../new_fields/Types"; -import { emptyFunction } from "../../Utils"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import * as globalCssVariables from "../views/globalCssVariables.scss"; -import { DocumentManager } from "./DocumentManager"; -import { LinkManager } from "./LinkManager"; -import { SelectionManager } from "./SelectionManager"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { Docs, DocUtils } from "../documents/Documents"; -import { ScriptField } from "../../new_fields/ScriptField"; +import { action, observable, runInAction } from "mobx"; +import { DateField } from "../../new_fields/DateField"; +import { Doc, Field, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { PrefetchProxy } from "../../new_fields/Proxy"; import { listSpec } from "../../new_fields/Schema"; -import { Scripting } from "./Scripting"; -import { convertDropDataToButtons } from "./DropConverter"; -import { AudioBox } from "../views/nodes/AudioBox"; -import { DateField } from "../../new_fields/DateField"; -import { DocumentView } from "../views/nodes/DocumentView"; +import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; +import { ScriptField } from "../../new_fields/ScriptField"; +import { Cast, NumCast, ScriptCast, StrCast } from "../../new_fields/Types"; +import { emptyFunction } from "../../Utils"; +import { Docs, DocUtils } from "../documents/Documents"; +import * as globalCssVariables from "../views/globalCssVariables.scss"; import { UndoManager } from "./UndoManager"; -import { PointData } from "../../new_fields/InkField"; -import { MainView } from "../views/MainView"; -import { action } from "mobx"; export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move export function SetupDrag( @@ -56,10 +46,10 @@ export function SetupDrag( const onItemDown = async (e: React.PointerEvent) => { if (e.button === 0) { e.stopPropagation(); - if (e.shiftKey && CollectionDockingView.Instance) { + if (e.shiftKey) { e.persist(); const dragDoc = await docFunc(); - dragDoc && CollectionDockingView.Instance.StartOtherDrag({ + dragDoc && DragManager.Vals.Instance.StartWindowDrag?.({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, @@ -76,8 +66,19 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; - export let horizSnapLines: number[] = []; - export let vertSnapLines: number[] = []; + export class Vals { + static Instance: Vals = new Vals(); + @observable public IsDragging: boolean = false; + @observable public horizSnapLines: number[] = []; + @observable public vertSnapLines: number[] = []; + public StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined; + public SetIsDragging(dragging: boolean) { runInAction(() => this.IsDragging = dragging); } + public GetIsDragging() { return this.IsDragging; } + public clearSnapLines() { + this.vertSnapLines.length = 0; + this.horizSnapLines.length = 0; + } + } export function Root() { const root = document.getElementById("root"); @@ -215,7 +216,7 @@ export namespace DragManager { export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); - dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline")); + dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(dropDoc); return dropDoc; }; const batch = UndoManager.StartBatch("dragging"); @@ -246,40 +247,6 @@ export namespace DragManager { StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag); } - // drag links and drop link targets (aliasing them if needed) - export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) { - const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; - - if (draggedDocs.length) { - const moddrag: Doc[] = []; - for (const draggedDoc of draggedDocs) { - const doc = await Cast(draggedDoc.annotationOn, Doc); - if (doc) moddrag.push(doc); - } - - const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); - dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => { - docView.props.removeDocument?.(doc); - addDocument(doc); - return true; - }; - const containingView = docView.props.ContainingCollectionView; - const finishDrag = (e: DragCompleteEvent) => - e.docDragData && (e.docDragData.droppedDocuments = - dragData.draggedDocuments.reduce((droppedDocs, d) => { - const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); - if (dvs.length) { - dvs.forEach(dv => droppedDocs.push(dv.props.Document)); - } else { - droppedDocs.push(Doc.MakeAlias(d)); - } - return droppedDocs; - }, [] as Doc[])); - - StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); - } - } - // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) { StartDrag(eles, dragData, downX, downY, options); @@ -300,10 +267,8 @@ export namespace DragManager { } export function SetSnapLines(horizLines: number[], vertLines: number[]) { - horizSnapLines = horizLines; - vertSnapLines = vertLines; - MainView.Instance._hLines = horizLines; - MainView.Instance._vLines = vertLines; + DragManager.Vals.Instance.horizSnapLines.push(...horizLines); + DragManager.Vals.Instance.vertSnapLines.push(...vertLines); } export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { @@ -320,10 +285,13 @@ export namespace DragManager { return drag; }; - return { thisX: snapVal([xFromLeft, xFromRight], e.pageX, vertSnapLines), thisY: snapVal([yFromTop, yFromBottom], e.pageY, horizSnapLines) }; + return { + thisX: snapVal([xFromLeft, xFromRight], e.pageX, DragManager.Vals.Instance.vertSnapLines), + thisY: snapVal([yFromTop, yFromBottom], e.pageY, DragManager.Vals.Instance.horizSnapLines) + }; } export let docsBeingDragged: Doc[] = []; - function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { + export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { eles = eles.filter(e => e); if (!dragDiv) { dragDiv = document.createElement("div"); @@ -331,7 +299,7 @@ export namespace DragManager { dragDiv.style.pointerEvents = "none"; DragManager.Root().appendChild(dragDiv); } - SelectionManager.SetIsDragging(true); + DragManager.Vals.Instance.SetIsDragging(true); const scaleXs: number[] = []; const scaleYs: number[] = []; const xs: number[] = []; @@ -413,14 +381,14 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined; } - if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) { + if (e.shiftKey && dragData.droppedDocuments.length === 1) { !dragData.dropAction && (dragData.dropAction = alias); if (dragData.dropAction === "move") { dragData.removeDocument?.(dragData.draggedDocuments[0]); } AbortDrag(); finishDrag?.(new DragCompleteEvent(true, dragData)); - CollectionDockingView.Instance.StartOtherDrag({ + DragManager.Vals.Instance.StartWindowDrag?.({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, @@ -447,22 +415,19 @@ export namespace DragManager { const endDrag = action(() => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - MainView.Instance._hLines = []; - MainView.Instance._vLines = []; - vertSnapLines.length = 0; - horizSnapLines.length = 0; + DragManager.Vals.Instance.clearSnapLines(); }); AbortDrag = () => { hideDragShowOriginalElements(); - SelectionManager.SetIsDragging(false); + DragManager.Vals.Instance.SetIsDragging(false); options?.dragComplete?.(new DragCompleteEvent(true, dragData)); endDrag(); }; const upHandler = (e: PointerEvent) => { hideDragShowOriginalElements(); dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag); - SelectionManager.SetIsDragging(false); + DragManager.Vals.Instance.SetIsDragging(false); endDrag(); options?.dragComplete?.(new DragCompleteEvent(false, dragData)); }; @@ -520,4 +485,3 @@ export namespace DragManager { } } } -Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index b1993d401..d6db882b8 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -7,6 +7,7 @@ import { Docs } from "../documents/Documents"; import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; import { RichTextField } from "../../new_fields/RichTextField"; import { ImageField } from "../../new_fields/URLField"; +import { Scripting } from "./Scripting"; // // converts 'doc' into a template that can be used to render other documents. @@ -75,3 +76,4 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { data.droppedDocuments[i] = dbox; }); } +Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }); \ No newline at end of file diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index a49977c42..d509168b6 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -8,10 +8,8 @@ export namespace SelectionManager { class Manager { - @observable IsDragging: boolean = false; SelectedDocuments: ObservableMap = new ObservableMap(); - @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it @@ -78,9 +76,6 @@ export namespace SelectionManager { if (found) manager.SelectDoc(found, false); } - export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } - export function GetIsDragging() { return manager.IsDragging; } - export function SelectedDocuments(): Array { return Array.from(manager.SelectedDocuments.keys()); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 396fe12b5..f8e77d90a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -364,8 +364,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> this.Interacting = false; (e.button === 0) && this._resizeUndo?.end(); this._resizeUndo = undefined; - MainView.Instance._hLines = []; - MainView.Instance._vLines = []; + DragManager.Vals.Instance.clearSnapLines(); } @computed @@ -404,7 +403,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined; const bounds = this.Bounds; const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; - if (SelectionManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { + if (DragManager.Vals.Instance.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } const minimal = bounds.r - bounds.x < 100 ? true : false; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a29a6baac..71c2bf245 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -43,6 +43,7 @@ import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { ScriptField } from '../../new_fields/ScriptField'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; +import { DragManager } from '../util/DragManager'; @observer export class MainView extends React.Component { @@ -574,9 +575,6 @@ export class MainView extends React.Component { return this._mainViewRef; } - @observable public _hLines: any; - @observable public _vLines: any; - render() { return (
@@ -597,8 +595,8 @@ export class MainView extends React.Component { {// TO VIEW SNAP LINES
- {this._hLines?.map((l: any) => )} - {this._vLines?.map((l: any) => )} + {DragManager.Vals.Instance.horizSnapLines.map((l: any) => )} + {DragManager.Vals.Instance.vertSnapLines.map((l: any) => )}
} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 822f44f5a..33ece13cc 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -68,10 +68,11 @@ export class CollectionDockingView extends React.Component { let config: any; if (dragDocs.length === 1) { config = CollectionDockingView.makeDocumentConfig(dragDocs[0]); @@ -499,7 +500,7 @@ export class CollectionDockingView extends React.Component { - if (!this._isPointerDown || !SelectionManager.GetIsDragging()) return; + if (!this._isPointerDown || !DragManager.Vals.Instance.GetIsDragging()) return; const activeContentItem = tab.header.parent.getActiveContentItem(); if (tab.contentItem !== activeContentItem) { tab.header.parent.setActiveContentItem(tab.contentItem); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 7ad15ef41..c74cfbcf4 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -132,7 +132,7 @@ export class CollectionMasonryViewFieldRow extends React.Component SelectionManager.GetIsDragging() && (this._background = "#b4b4b4")); + pointerEnteredRow = action(() => DragManager.Vals.Instance.GetIsDragging() && (this._background = "#b4b4b4")); @action pointerLeaveRow = () => { diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 8b8cbc6e8..0a10c24b3 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -12,6 +12,7 @@ import React = require("react"); import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; import { SelectionManager } from "../../util/SelectionManager"; import { UndoManager } from "../../util/UndoManager"; +import { DragManager } from "../../util/DragManager"; @observer export class CollectionPileView extends CollectionSubView(doc => doc) { @@ -78,7 +79,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { _undoBatch: UndoManager.Batch | undefined; pointerDown = (e: React.PointerEvent) => { let dist = 0; - SelectionManager.SetIsDragging(true); + DragManager.Vals.Instance.SetIsDragging(true); // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) { @@ -98,7 +99,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { }, () => { this._undoBatch?.end(); this._undoBatch = undefined; - SelectionManager.SetIsDragging(false); + DragManager.Vals.Instance.SetIsDragging(false); if (!this.childDocs.length) { this.props.ContainingCollectionView?.removeDocument(this.props.Document); } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 82204ca7b..0e6489947 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -189,7 +189,7 @@ export class CollectionSchemaCell extends React.Component { this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); }; const onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) { + if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging() && (type === "document" || type === undefined)) { dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; } }; diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index 972714e34..f9cd9a628 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -32,7 +32,7 @@ export class MovableColumn extends React.Component { private _dragRef: React.RefObject = React.createRef(); onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && SelectionManager.GetIsDragging()) { + if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) { this._header!.current!.className = "collectionSchema-col-wrapper"; document.addEventListener("pointermove", this.onDragMove, true); } @@ -143,7 +143,7 @@ export class MovableRow extends React.Component { private _rowDropDisposer?: DragManager.DragDropDisposer; onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && SelectionManager.GetIsDragging()) { + if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) { this._header!.current!.className = "collectionSchema-row-wrapper"; document.addEventListener("pointermove", this.onDragMove, true); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 1fd5c3f44..f6cdebc9b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -312,7 +312,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) this.refList.push(ref); const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) { + if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) { Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); } })); @@ -359,7 +359,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) this.refList.push(ref); const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) { + if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) { Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); } })); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 8a9539eb0..1d16a5478 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -120,7 +120,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { - if (SelectionManager.GetIsDragging()) { + if (DragManager.Vals.Instance.GetIsDragging()) { this._background = "#b4b4b4"; } } @@ -355,7 +355,7 @@ export class CollectionStackingViewFieldColumn extends React.Component diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 6d6f8d316..288fa8794 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -147,7 +147,7 @@ class TreeView extends React.Component { onPointerEnter = (e: React.PointerEvent): void => { this.props.active(true) && Doc.BrushDoc(this.dataDoc); - if (e.buttons === 1 && SelectionManager.GetIsDragging()) { + if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); } @@ -451,7 +451,7 @@ class TreeView extends React.Component { fontWeight: this.props.document.searchMatch ? "bold" : undefined, textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, - pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? undefined : "none" + pointerEvents: this.props.active() || DragManager.Vals.Instance.GetIsDragging() ? undefined : "none" }} > {Doc.GetT(this.props.document, "editTitle", "boolean", true) ? this.editableView("title") : diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 7df1e909f..0873fd1bb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -10,6 +10,7 @@ import React = require("react"); import { Utils, emptyFunction } from "../../../../Utils"; import { SelectionManager } from "../../../util/SelectionManager"; import { DocumentType } from "../../../documents/DocumentTypes"; +import { DragManager } from "../../../util/DragManager"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -36,7 +37,7 @@ export class CollectionFreeFormLinksView extends React.Component { } render() { - return SelectionManager.GetIsDragging() ? (null) :
+ return DragManager.Vals.Instance.GetIsDragging() ? (null) :
{this.uniqueConnections} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 411fbd9e2..b2401e69e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1174,7 +1174,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (SelectionManager.GetIsDragging()) { + if (DragManager.Vals.Instance.GetIsDragging()) { this.setupDragLines(); } e.stopPropagation(); @@ -1233,7 +1233,7 @@ export class CollectionFreeFormView extends CollectionSubView { document.removeEventListener("pointerup", this.onLinkButtonUp); const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; - DragManager.StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets); + StartLinkTargetsDrag(this._drag.current, this.props.docView, e.x, e.y, this.props.sourceDoc, targets); } e.stopPropagation(); } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index d091e06ef..d4e58fa8e 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -3,7 +3,7 @@ import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { Doc } from '../../../new_fields/Doc'; +import { Doc, DocListCast } from '../../../new_fields/Doc'; import { Cast, StrCast } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; @@ -26,6 +26,41 @@ interface LinkMenuItemProps { addDocTab: (document: Doc, where: string) => boolean; } +// drag links and drop link targets (aliasing them if needed) +export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) { + const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; + + if (draggedDocs.length) { + const moddrag: Doc[] = []; + for (const draggedDoc of draggedDocs) { + const doc = await Cast(draggedDoc.annotationOn, Doc); + if (doc) moddrag.push(doc); + } + + const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); + dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => { + docView.props.removeDocument?.(doc); + addDocument(doc); + return true; + }; + const containingView = docView.props.ContainingCollectionView; + const finishDrag = (e: DragManager.DragCompleteEvent) => + e.docDragData && (e.docDragData.droppedDocuments = + dragData.draggedDocuments.reduce((droppedDocs, d) => { + const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); + if (dvs.length) { + dvs.forEach(dv => droppedDocs.push(dv.props.Document)); + } else { + droppedDocs.push(Doc.MakeAlias(d)); + } + return droppedDocs; + }, [] as Doc[])); + + DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); + } +} + + @observer export class LinkMenuItem extends React.Component { private _drag = React.createRef(); @@ -83,7 +118,7 @@ export class LinkMenuItem extends React.Component { document.removeEventListener("pointerup", this.onLinkButtonUp); this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`; - DragManager.StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]); + StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]); } e.stopPropagation(); } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 3feb533a0..1c5e13620 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -17,10 +17,9 @@ import { ContextMenu } from "../ContextMenu"; import { Id } from "../../../new_fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { DocumentView } from "./DocumentView"; -import { Docs } from "../../documents/Documents"; +import { Docs, DocUtils } from "../../documents/Documents"; import { ComputedField } from "../../../new_fields/ScriptField"; import { Networking } from "../../Network"; -import { Upload } from "../../../server/SharedMediaTypes"; import { LinkAnchorBox } from "./LinkAnchorBox"; // testing testing @@ -57,7 +56,6 @@ export class AudioBox extends ViewBoxBaseComponent { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); }; - public static ActiveRecordings: Doc[] = []; @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); } @@ -145,7 +143,7 @@ export class AudioBox extends ViewBoxBaseComponent { const [{ result }] = await Networking.UploadFilesToServer(e.data); if (!(result instanceof Error)) { @@ -172,8 +170,8 @@ export class AudioBox extends ViewBoxBaseComponent { diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 3c2c6c87e..1c6250b94 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -3,7 +3,7 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import "react-table/react-table.css"; import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; -import { NumCast, StrCast } from "../../../new_fields/Types"; +import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; import { emptyFunction, returnOne } from "../../../Utils"; import '../DocumentDecorations.scss'; @@ -14,7 +14,11 @@ import "./ContentFittingDocumentView.scss"; @observer export class ContentFittingDocumentView extends React.Component{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive - private get layoutDoc() { return this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document); } + private get layoutDoc() { + return this.props.LayoutTemplate?.() || + (this.props.layoutKey && Doc.Layout(this.props.Document, Cast(this.props.Document[this.props.layoutKey], Doc, null))) || + Doc.Layout(this.props.Document); + } @computed get freezeDimensions() { return this.props.FreezeDimensions; } nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())); nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())); diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss index ce21391ce..3a27c16c1 100644 --- a/src/client/views/nodes/DocumentBox.scss +++ b/src/client/views/nodes/DocumentBox.scss @@ -2,7 +2,8 @@ width: 100%; height: 100%; pointer-events: all; - background: gray; + background: rgb(241, 239, 235); + position: absolute; .documentBox-lock { margin: auto; color: white; diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx index 8d422fe67..b53c7cfe6 100644 --- a/src/client/views/nodes/DocumentBox.tsx +++ b/src/client/views/nodes/DocumentBox.tsx @@ -1,23 +1,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { IReactionDisposer, reaction, computed } from "mobx"; +import { action, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, Field } from "../../../new_fields/Doc"; -import { documentSchema, collectionSchema } from "../../../new_fields/documentSchemas"; +import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas"; import { makeInterface } from "../../../new_fields/Schema"; import { ComputedField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { TraceMobx } from "../../../new_fields/util"; import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { DragManager } from "../../util/DragManager"; +import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import "./DocumentBox.scss"; +import { DocumentView } from "./DocumentView"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); -import { TraceMobx } from "../../../new_fields/util"; -import { Docs } from "../../documents/Documents"; -import { KeyValueBox } from "./KeyValueBox"; -import { DocumentView } from "./DocumentView"; type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>; const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema); @@ -26,7 +27,9 @@ const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema); export class DocHolderBox extends ViewBoxAnnotatableComponent(DocHolderBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); } _prevSelectionDisposer: IReactionDisposer | undefined; + _dropDisposer?: DragManager.DragDropDisposer; _selections: Doc[] = []; + _contRef = React.createRef(); _curSelection = -1; componentDidMount() { this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => { @@ -101,21 +104,15 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent(); pwidth = () => this.props.PanelWidth() - 2 * this.xPad; pheight = () => this.props.PanelHeight() - 2 * this.yPad; getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); isActive = () => this.active() || !this.props.renderDepth; + layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null); get renderContents() { const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null); - const childTemplateName = StrCast(this.layoutDoc.childTemplateName); - if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) { - setTimeout(() => { - Doc.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName); - Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined); - }, 0); - } - const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString ? + const layoutTemplate = StrCast(this.layoutDoc.childLayoutString); + const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ? : @@ -150,8 +147,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent {this.renderContents} -
; } + + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) { + const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null); + this.props.Document.childLayoutTemplate = doc; + } + } + } + protected createDropTarget = (ele: HTMLDivElement) => { + this._dropDisposer?.(); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + } + } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7a16e8836..c4cd5978a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1102,7 +1102,7 @@ export class DocumentView extends DocComponent(Docu } @computed get ignorePointerEvents() { return this.props.pointerEvents === false || - (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || + (this.Document.isBackground && !this.isSelected() && !DragManager.Vals.Instance.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); } @undoBatch diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index 76885eada..d248b098c 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -11,6 +11,7 @@ import { SearchBox } from "../search/SearchBox"; import { FieldView, FieldViewProps } from './FieldView'; import "./QueryBox.scss"; import { List } from "../../../new_fields/List"; +import { DragManager } from "../../util/DragManager"; type QueryDocument = makeInterface<[typeof documentSchema]>; const QueryDocument = makeInterface(documentSchema); @@ -27,7 +28,7 @@ export class QueryBox extends ViewBoxAnnotatableComponent e.stopPropagation()} > m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); - const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); + const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); // the actual text in the text box + const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype + const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - if ((!curTemp && !curProto) || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + if ((!curTemp && !curProto) || curText) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) + if (curText !== curLayout?.Text) { + this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + } } else { // if we've deleted all the text in a note driven by a template, then restore the template data this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index b63a3dbbf..bf99f449f 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -217,28 +217,29 @@ export class CurrentUserUtils { static setupDefaultIconTemplates(doc: Doc) { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.TextDocument("", { - title: "icon", _width: 150, _height: 30, isTemplateDoc: true, - onClick: ScriptField.MakeScript("deiconifyView(self)") + 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", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") }); + const iconRtfView = Docs.Create.LabelDocument({ + title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); } 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, - onClick: ScriptField.MakeScript("deiconifyView(self)") + title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") }); 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, onClick: ScriptField.MakeScript("deiconifyView(self)") }); + const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } @@ -265,9 +266,17 @@ export class CurrentUserUtils { doc.emptyCollection = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" }); } + 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" }); + } + if (doc.emptyWebpage === undefined) { + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 }) + } return [ { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, - { title: "Drag a web page", label: "Web", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("", { title: "New Webpage" })' }, + { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc }, { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: '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" })' }, { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' }, { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, @@ -284,7 +293,7 @@ export class CurrentUserUtils { // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, - { title: "Drag a document previewer", label: "Prev", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' }, + { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, ]; @@ -308,7 +317,7 @@ export class CurrentUserUtils { title: data.title, label: data.label, ignoreClick: data.ignoreClick, - dropAction: data.click ? "copy" : undefined, + dropAction: "copy", onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, -- cgit v1.2.3-70-g09d2 From d9227908ba8e8db5084c468a242b2839ab11a33d Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 4 May 2020 12:50:46 -0400 Subject: fixed links and snap lines broken by moving things into DragManager. --- src/client/documents/Documents.ts | 2 +- src/client/util/DragManager.ts | 34 ++-- src/client/util/SelectionManager.ts | 3 + src/client/util/SnappingManager.ts | 29 +++ src/client/views/DocumentDecorations.tsx | 5 +- src/client/views/MainView.scss | 9 + src/client/views/MainView.tsx | 18 +- .../views/collections/CollectionDockingView.tsx | 5 +- .../collections/CollectionMasonryViewFieldRow.tsx | 4 +- .../views/collections/CollectionPileView.tsx | 6 +- .../views/collections/CollectionSchemaCells.tsx | 4 +- .../CollectionSchemaMovableTableHOC.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 7 +- .../CollectionStackingViewFieldColumn.tsx | 6 +- .../views/collections/CollectionTreeView.tsx | 7 +- .../CollectionFreeFormLinkView.tsx | 9 +- .../CollectionFreeFormLinksView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +- src/client/views/nodes/DocHolderBox.scss | 15 ++ src/client/views/nodes/DocHolderBox.tsx | 207 +++++++++++++++++++++ src/client/views/nodes/DocumentBox.scss | 15 -- src/client/views/nodes/DocumentBox.tsx | 207 --------------------- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/QueryBox.tsx | 5 +- 25 files changed, 328 insertions(+), 291 deletions(-) create mode 100644 src/client/util/SnappingManager.ts create mode 100644 src/client/views/nodes/DocHolderBox.scss create mode 100644 src/client/views/nodes/DocHolderBox.tsx delete mode 100644 src/client/views/nodes/DocumentBox.scss delete mode 100644 src/client/views/nodes/DocumentBox.tsx (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 45687f597..f5d6cd7f6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -37,7 +37,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { QueryBox } from "../views/nodes/QueryBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox"; -import { DocHolderBox } from "../views/nodes/DocumentBox"; +import { DocHolderBox } from "../views/nodes/DocHolderBox"; import { InkingStroke } from "../views/InkingStroke"; import { InkField } from "../../new_fields/InkField"; import { InkingControl } from "../views/InkingControl"; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 348aba588..0d208cf1b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -11,6 +11,7 @@ import { emptyFunction } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import * as globalCssVariables from "../views/globalCssVariables.scss"; import { UndoManager } from "./UndoManager"; +import { SnappingManager } from "./SnappingManager"; export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move export function SetupDrag( @@ -49,7 +50,7 @@ export function SetupDrag( if (e.shiftKey) { e.persist(); const dragDoc = await docFunc(); - dragDoc && DragManager.Vals.Instance.StartWindowDrag?.({ + dragDoc && DragManager.StartWindowDrag?.({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, @@ -66,19 +67,7 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; - export class Vals { - static Instance: Vals = new Vals(); - @observable public IsDragging: boolean = false; - @observable public horizSnapLines: number[] = []; - @observable public vertSnapLines: number[] = []; - public StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined; - public SetIsDragging(dragging: boolean) { runInAction(() => this.IsDragging = dragging); } - public GetIsDragging() { return this.IsDragging; } - @action public clearSnapLines() { - this.vertSnapLines.length = 0; - this.horizSnapLines.length = 0; - } - } + export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined; export function Root() { const root = document.getElementById("root"); @@ -267,8 +256,7 @@ export namespace DragManager { } export function SetSnapLines(horizLines: number[], vertLines: number[]) { - DragManager.Vals.Instance.horizSnapLines.push(...horizLines); - DragManager.Vals.Instance.vertSnapLines.push(...vertLines); + SnappingManager.setSnapLines(horizLines, vertLines); } export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { @@ -286,8 +274,8 @@ export namespace DragManager { }; return { - thisX: snapVal([xFromLeft, xFromRight], e.pageX, DragManager.Vals.Instance.vertSnapLines), - thisY: snapVal([yFromTop, yFromBottom], e.pageY, DragManager.Vals.Instance.horizSnapLines) + thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()), + thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()) }; } export let docsBeingDragged: Doc[] = []; @@ -299,7 +287,7 @@ export namespace DragManager { dragDiv.style.pointerEvents = "none"; DragManager.Root().appendChild(dragDiv); } - DragManager.Vals.Instance.SetIsDragging(true); + SnappingManager.SetIsDragging(true); const scaleXs: number[] = []; const scaleYs: number[] = []; const xs: number[] = []; @@ -388,7 +376,7 @@ export namespace DragManager { } AbortDrag(); finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.Vals.Instance.StartWindowDrag?.({ + DragManager.StartWindowDrag?.({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, @@ -415,19 +403,19 @@ export namespace DragManager { const endDrag = action(() => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - DragManager.Vals.Instance.clearSnapLines(); + SnappingManager.clearSnapLines(); }); AbortDrag = () => { hideDragShowOriginalElements(); - DragManager.Vals.Instance.SetIsDragging(false); + SnappingManager.SetIsDragging(false); options?.dragComplete?.(new DragCompleteEvent(true, dragData)); endDrag(); }; const upHandler = (e: PointerEvent) => { hideDragShowOriginalElements(); dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag); - DragManager.Vals.Instance.SetIsDragging(false); + SnappingManager.SetIsDragging(false); endDrag(); options?.dragComplete?.(new DragCompleteEvent(false, dragData)); }; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index d509168b6..11d2cafb2 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -8,6 +8,7 @@ export namespace SelectionManager { class Manager { + @observable IsDragging: boolean = false; SelectedDocuments: ObservableMap = new ObservableMap(); @action @@ -53,6 +54,8 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } + export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } + export function GetIsDragging() { return manager.IsDragging; } // computed functions, such as used in IsSelected generate errors if they're called outside of a // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature // to avoid unnecessary mobx invalidations when running inside a reaction. diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts new file mode 100644 index 000000000..fc07e8ab4 --- /dev/null +++ b/src/client/util/SnappingManager.ts @@ -0,0 +1,29 @@ +import { observable, action, runInAction } from "mobx"; + +export namespace SnappingManager { + + class Manager { + @observable IsDragging: boolean = false; + @observable public horizSnapLines: number[] = []; + @observable public vertSnapLines: number[] = []; + @action public clearSnapLines() { + this.vertSnapLines = []; + this.horizSnapLines = []; + } + @action public setSnapLines(horizLines: number[], vertLines: number[]) { + this.horizSnapLines = horizLines; + this.vertSnapLines = vertLines; + } + } + + const manager = new Manager(); + + export function clearSnapLines() { manager.clearSnapLines(); } + export function setSnapLines(horizLines: number[], vertLines: number[]) { manager.setSnapLines(horizLines, vertLines); } + export function horizSnapLines() { return manager.horizSnapLines; } + export function vertSnapLines() { return manager.vertSnapLines; } + + export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } + export function GetIsDragging() { return manager.IsDragging; } +} + diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index f8e77d90a..b89806656 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -21,6 +21,7 @@ import { Id } from '../../new_fields/FieldSymbols'; import e = require('express'); import { CollectionDockingView } from './collections/CollectionDockingView'; import { MainView } from './MainView'; +import { SnappingManager } from '../util/SnappingManager'; library.add(faCaretUp); library.add(faObjectGroup); @@ -364,7 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> this.Interacting = false; (e.button === 0) && this._resizeUndo?.end(); this._resizeUndo = undefined; - DragManager.Vals.Instance.clearSnapLines(); + SnappingManager.clearSnapLines(); } @computed @@ -403,7 +404,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const darkScheme = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimgray" : undefined; const bounds = this.Bounds; const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; - if (DragManager.Vals.Instance.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { + if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } const minimal = bounds.r - bounds.x < 100 ? true : false; diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 04288a9e1..753ba700c 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -22,6 +22,15 @@ z-index: 1; } +.mainView-snapLines { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events:none; +} + .mainView-container, .mainView-container-dark { input { color: unset !important; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 71c2bf245..9ebd60c52 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -44,6 +44,7 @@ import { PreviewCursor } from './PreviewCursor'; import { ScriptField } from '../../new_fields/ScriptField'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; import { DragManager } from '../util/DragManager'; +import { SnappingManager } from '../util/SnappingManager'; @observer export class MainView extends React.Component { @@ -575,6 +576,15 @@ export class MainView extends React.Component { return this._mainViewRef; } + @computed get snapLines() { + return
+ + {SnappingManager.horizSnapLines().map(l => )} + {SnappingManager.vertSnapLines().map(l => )} + +
+ } + render() { return (
@@ -592,14 +602,8 @@ export class MainView extends React.Component { - {// TO VIEW SNAP LINES -
- - {DragManager.Vals.Instance.horizSnapLines.map((l: any) => )} - {DragManager.Vals.Instance.vertSnapLines.map((l: any) => )} - -
} + {this.snapLines}
); } } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 33ece13cc..6cd5d1b1b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -30,6 +30,7 @@ import { SubCollectionViewProps } from "./CollectionSubView"; import { DockingViewButtonSelector } from './ParentDocumentSelector'; import React = require("react"); import { CollectionViewType } from './CollectionView'; +import { SnappingManager } from '../../util/SnappingManager'; library.add(faFile); const _global = (window /* browser */ || global /* node */) as any; @@ -68,7 +69,7 @@ export class CollectionDockingView extends React.Component { - if (!this._isPointerDown || !DragManager.Vals.Instance.GetIsDragging()) return; + if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return; const activeContentItem = tab.header.parent.getActiveContentItem(); if (tab.contentItem !== activeContentItem) { tab.header.parent.setActiveContentItem(tab.contentItem); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index c74cfbcf4..95c7643c9 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -12,12 +12,12 @@ import { numberRange, setupMoveUpEvents, emptyFunction } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; -import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; import { CollectionStackingView } from "./CollectionStackingView"; import "./CollectionStackingView.scss"; +import { SnappingManager } from "../../util/SnappingManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -132,7 +132,7 @@ export class CollectionMasonryViewFieldRow extends React.Component DragManager.Vals.Instance.GetIsDragging() && (this._background = "#b4b4b4")); + pointerEnteredRow = action(() => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4")); @action pointerLeaveRow = () => { diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 0a10c24b3..d3ae21f3a 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -12,7 +12,7 @@ import React = require("react"); import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; import { SelectionManager } from "../../util/SelectionManager"; import { UndoManager } from "../../util/UndoManager"; -import { DragManager } from "../../util/DragManager"; +import { SnappingManager } from "../../util/SnappingManager"; @observer export class CollectionPileView extends CollectionSubView(doc => doc) { @@ -79,7 +79,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { _undoBatch: UndoManager.Batch | undefined; pointerDown = (e: React.PointerEvent) => { let dist = 0; - DragManager.Vals.Instance.SetIsDragging(true); + SnappingManager.SetIsDragging(true); // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) { @@ -99,7 +99,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { }, () => { this._undoBatch?.end(); this._undoBatch = undefined; - DragManager.Vals.Instance.SetIsDragging(false); + SnappingManager.SetIsDragging(false); if (!this.childDocs.length) { this.props.ContainingCollectionView?.removeDocument(this.props.Document); } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 0e6489947..5253ee0b9 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -18,11 +18,11 @@ import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../new_fields/Types"; import { Docs } from "../../documents/Documents"; -import { SelectionManager } from "../../util/SelectionManager"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faExpand } from '@fortawesome/free-solid-svg-icons'; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; +import { SnappingManager } from "../../util/SnappingManager"; library.add(faExpand); @@ -189,7 +189,7 @@ export class CollectionSchemaCell extends React.Component { this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); }; const onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging() && (type === "document" || type === undefined)) { + if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) { dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; } }; diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index f9cd9a628..8636e3eb5 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -4,7 +4,6 @@ import "./CollectionSchemaView.scss"; import { Transform } from "../../util/Transform"; import { Doc } from "../../../new_fields/Doc"; import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager"; -import { SelectionManager } from "../../util/SelectionManager"; import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; import { ContextMenu } from "../ContextMenu"; import { action } from "mobx"; @@ -14,6 +13,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { DocumentManager } from "../../util/DocumentManager"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; +import { SnappingManager } from "../../util/SnappingManager"; library.add(faGripVertical, faTrash); @@ -32,7 +32,7 @@ export class MovableColumn extends React.Component { private _dragRef: React.RefObject = React.createRef(); onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) { + if (e.buttons === 1 && SnappingManager.GetIsDragging()) { this._header!.current!.className = "collectionSchema-col-wrapper"; document.addEventListener("pointermove", this.onDragMove, true); } @@ -143,7 +143,7 @@ export class MovableRow extends React.Component { private _rowDropDisposer?: DragManager.DragDropDisposer; onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) { + if (e.buttons === 1 && SnappingManager.GetIsDragging()) { this._header!.current!.className = "collectionSchema-row-wrapper"; document.addEventListener("pointermove", this.onDragMove, true); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index f6cdebc9b..b6faea845 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -14,7 +14,6 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_field import { TraceMobx } from "../../../new_fields/util"; import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../Utils"; import { DragManager, dropActionType } from "../../util/DragManager"; -import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; @@ -26,7 +25,7 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { SnappingManager } from "../../util/SnappingManager"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -312,7 +311,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) this.refList.push(ref); const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) { + if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); } })); @@ -359,7 +358,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) this.refList.push(ref); const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.props.Document._autoHeight && ref && this.refList.length && !DragManager.Vals.Instance.GetIsDragging()) { + if (this.props.Document._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); } })); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 1d16a5478..dccef7983 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -13,7 +13,6 @@ import { ImageField } from "../../../new_fields/URLField"; import { TraceMobx } from "../../../new_fields/util"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; -import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; @@ -23,6 +22,7 @@ import { CollectionStackingView } from "./CollectionStackingView"; import { setupMoveUpEvents, emptyFunction } from "../../../Utils"; import "./CollectionStackingView.scss"; import { listSpec } from "../../../new_fields/Schema"; +import { SnappingManager } from "../../util/SnappingManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -120,7 +120,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { - if (DragManager.Vals.Instance.GetIsDragging()) { + if (SnappingManager.GetIsDragging()) { this._background = "#b4b4b4"; } } @@ -355,7 +355,7 @@ export class CollectionStackingViewFieldColumn extends React.Component diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 288fa8794..f2b0e3155 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -6,6 +6,7 @@ import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; +import { PrefetchProxy } from '../../../new_fields/Proxy'; import { Document, listSpec } from '../../../new_fields/Schema'; import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types'; @@ -13,6 +14,7 @@ import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZer import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from '../../util/DocumentManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; @@ -31,7 +33,6 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import { CollectionViewType } from './CollectionView'; import React = require("react"); -import { PrefetchProxy } from '../../../new_fields/Proxy'; export interface TreeViewProps { @@ -147,7 +148,7 @@ class TreeView extends React.Component { onPointerEnter = (e: React.PointerEvent): void => { this.props.active(true) && Doc.BrushDoc(this.dataDoc); - if (e.buttons === 1 && DragManager.Vals.Instance.GetIsDragging()) { + if (e.buttons === 1 && SnappingManager.GetIsDragging()) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); } @@ -451,7 +452,7 @@ class TreeView extends React.Component { fontWeight: this.props.document.searchMatch ? "bold" : undefined, textDecoration: Doc.GetT(this.props.document, "title", "string", true) ? "underline" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, - pointerEvents: this.props.active() || DragManager.Vals.Instance.GetIsDragging() ? undefined : "none" + pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none" }} > {Doc.GetT(this.props.document, "editTitle", "boolean", true) ? this.editableView("title") : diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 0c9a1aa9a..695898ca9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -9,7 +9,7 @@ import { DocumentType } from "../../../documents/DocumentTypes"; import { observable, action, reaction, IReactionDisposer } from "mobx"; import { StrCast } from "../../../../new_fields/Types"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { DragManager } from "../../../util/DragManager"; +import { SnappingManager } from "../../../util/SnappingManager"; export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -25,7 +25,7 @@ export class CollectionFreeFormLinkView extends React.Component [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)], action(() => { - if (DragManager.Vals.Instance.GetIsDragging()) return; + if (SnappingManager.GetIsDragging()) return; setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; @@ -83,7 +83,7 @@ export class CollectionFreeFormLinkView extends React.Component - + {text !== "-ungrouped-" ? text : ""} + return SnappingManager.GetIsDragging() ? (null) :
{this.uniqueConnections} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2aa9b1f5f..37957b0f6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -45,6 +45,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { CollectionViewType } from "../CollectionView"; import { Timeline } from "../../animationtimeline/Timeline"; +import { SnappingManager } from "../../../util/SnappingManager"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -1174,7 +1175,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (DragManager.Vals.Instance.GetIsDragging()) { + if (SnappingManager.GetIsDragging()) { this.setupDragLines(); } e.stopPropagation(); @@ -1233,7 +1234,7 @@ export class CollectionFreeFormView extends CollectionSubView; +const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema); + +@observer +export class DocHolderBox extends ViewBoxAnnotatableComponent(DocHolderBoxDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); } + _prevSelectionDisposer: IReactionDisposer | undefined; + _dropDisposer?: DragManager.DragDropDisposer; + _selections: Doc[] = []; + _contRef = React.createRef(); + _curSelection = -1; + componentDidMount() { + this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => { + if (data instanceof Doc && !this.isSelectionLocked()) { + this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1); + this._selections.push(data); + this._curSelection = this._selections.length - 1; + } + }); + } + componentWillUnmount() { + this._prevSelectionDisposer?.(); + } + specificContextMenu = (e: React.MouseEvent): void => { + const funcs: ContextMenuProps[] = []; + funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" }); + funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" }); + funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" }); + funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "", icon: "project-diagram" }); + + ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + } + lockSelection = () => { + this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey]; + } + showSelection = () => { + this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); + } + isSelectionLocked = () => { + const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey); + return !kvpstring || kvpstring.includes("DOC"); + } + toggleLockSelection = () => { + !this.isSelectionLocked() ? this.lockSelection() : this.showSelection(); + return true; + } + prevSelection = () => { + this.lockSelection(); + if (this._curSelection > 0) { + this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection]; + return true; + } + } + nextSelection = () => { + if (this._curSelection < this._selections.length - 1 && this._selections.length) { + this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection]; + return true; + } + } + onPointerDown = (e: React.PointerEvent) => { + if (this.active() && e.button === 0 && !e.ctrlKey) { + e.stopPropagation(); + } + } + onLockClick = (e: React.MouseEvent) => { + this.toggleLockSelection(); + (e.nativeEvent as any).formattedHandled = true; + e.stopPropagation(); + } + get xPad() { return NumCast(this.props.Document._xPadding); } + get yPad() { return NumCast(this.props.Document._yPadding); } + onClick = (e: React.MouseEvent) => { + let hitWidget: boolean | undefined = false; + if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); + else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); + else { + if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection(); + if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection(); + } + if (hitWidget) { + (e.nativeEvent as any).formattedHandled = true; + e.stopPropagation(); + } + } + pwidth = () => this.props.PanelWidth() - 2 * this.xPad; + pheight = () => this.props.PanelHeight() - 2 * this.yPad; + getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); + isActive = () => this.active() || !this.props.renderDepth; + layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null); + get renderContents() { + const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null); + const layoutTemplate = StrCast(this.layoutDoc.childLayoutString); + const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ? + : + ; + return contents; + } + render() { + TraceMobx(); + return
+ {this.renderContents} +
+ +
+
; + } + + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) { + const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null); + this.props.Document.childLayoutTemplate = doc; + } + } + } + protected createDropTarget = (ele: HTMLDivElement) => { + this._dropDisposer?.(); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + } + +} diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss deleted file mode 100644 index 3a27c16c1..000000000 --- a/src/client/views/nodes/DocumentBox.scss +++ /dev/null @@ -1,15 +0,0 @@ -.documentBox-container { - width: 100%; - height: 100%; - pointer-events: all; - background: rgb(241, 239, 235); - position: absolute; - .documentBox-lock { - margin: auto; - color: white; - position: absolute; - } - .contentFittingDocumentView { - position: absolute; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx deleted file mode 100644 index b53c7cfe6..000000000 --- a/src/client/views/nodes/DocumentBox.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, IReactionDisposer, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Field } from "../../../new_fields/Doc"; -import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface } from "../../../new_fields/Schema"; -import { ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { TraceMobx } from "../../../new_fields/util"; -import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DragManager } from "../../util/DragManager"; -import { undoBatch } from "../../util/UndoManager"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; -import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; -import "./DocumentBox.scss"; -import { DocumentView } from "./DocumentView"; -import { FieldView, FieldViewProps } from "./FieldView"; -import React = require("react"); - -type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>; -const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema); - -@observer -export class DocHolderBox extends ViewBoxAnnotatableComponent(DocHolderBoxDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); } - _prevSelectionDisposer: IReactionDisposer | undefined; - _dropDisposer?: DragManager.DragDropDisposer; - _selections: Doc[] = []; - _contRef = React.createRef(); - _curSelection = -1; - componentDidMount() { - this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => { - if (data instanceof Doc && !this.isSelectionLocked()) { - this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1); - this._selections.push(data); - this._curSelection = this._selections.length - 1; - } - }); - } - componentWillUnmount() { - this._prevSelectionDisposer?.(); - } - specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" }); - funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" }); - funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" }); - funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "", icon: "project-diagram" }); - - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - } - lockSelection = () => { - this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey]; - } - showSelection = () => { - this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); - } - isSelectionLocked = () => { - const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey); - return !kvpstring || kvpstring.includes("DOC"); - } - toggleLockSelection = () => { - !this.isSelectionLocked() ? this.lockSelection() : this.showSelection(); - return true; - } - prevSelection = () => { - this.lockSelection(); - if (this._curSelection > 0) { - this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection]; - return true; - } - } - nextSelection = () => { - if (this._curSelection < this._selections.length - 1 && this._selections.length) { - this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection]; - return true; - } - } - onPointerDown = (e: React.PointerEvent) => { - if (this.active() && e.button === 0 && !e.ctrlKey) { - e.stopPropagation(); - } - } - onLockClick = (e: React.MouseEvent) => { - this.toggleLockSelection(); - (e.nativeEvent as any).formattedHandled = true; - e.stopPropagation(); - } - get xPad() { return NumCast(this.props.Document._xPadding); } - get yPad() { return NumCast(this.props.Document._yPadding); } - onClick = (e: React.MouseEvent) => { - let hitWidget: boolean | undefined = false; - if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); - else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); - else { - if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection(); - if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection(); - } - if (hitWidget) { - (e.nativeEvent as any).formattedHandled = true; - e.stopPropagation(); - } - } - pwidth = () => this.props.PanelWidth() - 2 * this.xPad; - pheight = () => this.props.PanelHeight() - 2 * this.yPad; - getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); - isActive = () => this.active() || !this.props.renderDepth; - layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null); - get renderContents() { - const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null); - const layoutTemplate = StrCast(this.layoutDoc.childLayoutString); - const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ? - : - ; - return contents; - } - render() { - TraceMobx(); - return
- {this.renderContents} -
- -
-
; - } - - @undoBatch - @action - drop = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) { - const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null); - this.props.Document.childLayoutTemplate = doc; - } - } - } - protected createDropTarget = (ele: HTMLDivElement) => { - this._dropDisposer?.(); - ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); - } - -} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 81667e549..395a6e0b2 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -14,7 +14,7 @@ import { LabelBox } from "./LabelBox"; import { SliderBox } from "./SliderBox"; import { LinkBox } from "./LinkBox"; import { ScriptingBox } from "./ScriptingBox"; -import { DocHolderBox } from "./DocumentBox"; +import { DocHolderBox } from "./DocHolderBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7a81801e1..86e58ca7a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -23,6 +23,7 @@ import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DocumentManager } from "../../util/DocumentManager"; +import { SnappingManager } from '../../util/SnappingManager'; import { DragManager, dropActionType } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; import { Scripting } from '../../util/Scripting'; @@ -1105,7 +1106,7 @@ export class DocumentView extends DocComponent(Docu } @computed get ignorePointerEvents() { return this.props.pointerEvents === false || - (this.Document.isBackground && !this.isSelected() && !DragManager.Vals.Instance.GetIsDragging()) || + (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); } @undoBatch diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index d248b098c..2a21a380d 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -5,13 +5,12 @@ import { documentSchema } from "../../../new_fields/documentSchemas"; import { Id } from '../../../new_fields/FieldSymbols'; import { makeInterface, listSpec } from "../../../new_fields/Schema"; import { StrCast, Cast } from "../../../new_fields/Types"; -import { SelectionManager } from "../../util/SelectionManager"; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { SearchBox } from "../search/SearchBox"; import { FieldView, FieldViewProps } from './FieldView'; import "./QueryBox.scss"; import { List } from "../../../new_fields/List"; -import { DragManager } from "../../util/DragManager"; +import { SnappingManager } from "../../util/SnappingManager"; type QueryDocument = makeInterface<[typeof documentSchema]>; const QueryDocument = makeInterface(documentSchema); @@ -28,7 +27,7 @@ export class QueryBox extends ViewBoxAnnotatableComponent e.stopPropagation()} > Date: Mon, 4 May 2020 16:10:19 -0400 Subject: added toggle switch to default templates --- src/client/views/MainView.tsx | 3 ++- src/client/views/nodes/FontIconBox.tsx | 19 +++++++++++----- .../views/nodes/formattedText/RichTextSchema.tsx | 1 + .../authentication/models/current_user_utils.ts | 25 ++++++++++++++++++++-- 4 files changed, 40 insertions(+), 8 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9ebd60c52..d60a9d64a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTerminal, faFile as fileSolid, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; +import { faTerminal, faToggleOn, faFile as fileSolid, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -106,6 +106,7 @@ export class MainView extends React.Component { } library.add(faTerminal); + library.add(faToggleOn); library.add(faLocationArrow); library.add(faSearch); library.add(fileSolid); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index c6ea6a882..0d597b3a8 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -10,6 +10,7 @@ import { Utils } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; import { Doc } from '../../../new_fields/Doc'; import { ContextMenu } from '../ContextMenu'; +import { ScriptField } from '../../../new_fields/ScriptField'; const FontIconSchema = createSchema({ icon: "string" }); @@ -23,7 +24,7 @@ export class FontIconBox extends DocComponent( _ref: React.RefObject = React.createRef(); _backgroundReaction: IReactionDisposer | undefined; componentDidMount() { - this._backgroundReaction = reaction(() => this.props.Document.backgroundColor, + this._backgroundReaction = reaction(() => this.layoutDoc.backgroundColor, () => { if (this._ref && this._ref.current) { const col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor); @@ -35,13 +36,21 @@ export class FontIconBox extends DocComponent( } showTemplate = (): void => { - const dragFactory = Cast(this.props.Document.dragFactory, Doc, null); + const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); dragFactory && this.props.addDocTab(dragFactory, "onRight"); } + dragAsTemplate = (): void => { + this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); + } + useAsPrototype = (): void => { + this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); + } specificContextMenu = (): void => { const cm = ContextMenu.Instance; cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" }); + cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" }); + cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" }); } componentWillUnmount() { @@ -49,12 +58,12 @@ export class FontIconBox extends DocComponent( } render() { - const referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); + const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc); const referenceLayout = Doc.Layout(referenceDoc); - return : (null)} - {this.clickedState ? this.authenticationCode = e.currentTarget.value)} placeholder={prompt} /> : (null)} - {this.avatar ? : (null)} - {this.username ? Welcome to Dash, {this.username} - : (null)} + {this.credentials ? + <> + + Welcome to Dash, {this.credentials.userInfo.name} + +
{ + await Networking.FetchFromServer("/revokeGoogleAccessToken"); + this.resetState(0, 0); + }} + >Disconnect Account
+ : (null)}
); } @@ -125,4 +162,6 @@ export default class GoogleAuthenticationManager extends React.Component<{}> { ); } -} \ No newline at end of file +} + +Scripting.addGlobal("GoogleAuthenticationManager", GoogleAuthenticationManager); \ No newline at end of file diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index fa67ddbef..2f3cac8d3 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -95,7 +95,7 @@ export namespace GoogleApiClientUtils { export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] }; export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => { const paragraphs = extractParagraphs(document); - let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => run as docs_v1.Schema$TextRun).join("")).join(""); + let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => (run as docs_v1.Schema$TextRun).content).join("")).join(""); text = text.substring(0, text.length - 1); removeNewlines && text.replace(/\n/g, ""); return { text, paragraphs }; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 185222541..6cca4d69f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -12,6 +12,7 @@ import { ScriptField } from "../../new_fields/ScriptField"; import { InkingControl } from "./InkingControl"; import { InkTool } from "../../new_fields/InkField"; import { DocumentView } from "./nodes/DocumentView"; +import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -79,6 +80,7 @@ export default class KeyManager { SelectionManager.DeselectAll(); DictationManager.Controls.stop(); // RecommendationsBox.Instance.closeMenu(); + GoogleAuthenticationManager.Instance.cancel(); SharingManager.Instance.close(); break; case "delete": diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d60a9d64a..1a285d4ec 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTerminal, faToggleOn, faFile as fileSolid, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; +import { faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -163,6 +163,7 @@ export class MainView extends React.Component { library.add(faPhone); library.add(faClipboard); library.add(faStamp); + library.add(faExternalLinkAlt); this.initEventListeners(); this.initAuthenticationRouters(); } @@ -583,7 +584,7 @@ export class MainView extends React.Component { {SnappingManager.horizSnapLines().map(l => )} {SnappingManager.vertSnapLines().map(l => )} -
+
; } render() { diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 9198fe3e3..a7bd5882d 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -4,7 +4,7 @@ import "./MainViewModal.scss"; export interface MainViewOverlayProps { isDisplayed: boolean; interactive: boolean; - contents: string | JSX.Element; + contents: string | JSX.Element | null; dialogueBoxStyle?: React.CSSProperties; overlayStyle?: React.CSSProperties; dialogueBoxDisplayedOpacity?: number; diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 20aa14f84..afb6bfb7d 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,7 +1,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc, DocListCast } from "../../new_fields/Doc"; +import { Doc, DocListCast, Opt } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; import { NumCast } from "../../new_fields/Types"; import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils"; @@ -214,4 +214,6 @@ export class OverlayView extends React.Component { } } // bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error -Scripting.addGlobal(function addOverlayWindow(Tag: string, options: OverlayElementOptions) { const x = ; OverlayView.Instance.addWindow(x, options); }); \ No newline at end of file +Scripting.addGlobal(function addOverlayWindow(type: string, options: OverlayElementOptions) { + OverlayView.Instance.addWindow(, options); +}); \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 658a55f51..180cb043b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -59,6 +59,7 @@ import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); import { ScriptField } from '../../../../new_fields/ScriptField'; +import GoogleAuthenticationManager from '../../../apis/GoogleAuthenticationManager'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -784,7 +785,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let pullSuccess = false; if (exportState !== undefined) { pullSuccess = true; - dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); + dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON())); setTimeout(() => { if (this._editorView) { const state = this._editorView.state; @@ -802,13 +803,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } checkState = (exportState: Opt, dataDoc: Doc) => { - if (exportState && this._editorView) { - const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); - const equalTitles = dataDoc.title === exportState.title; - const unchanged = equalContent && equalTitles; - dataDoc.unchanged = unchanged; - DocumentButtonBar.Instance.setPullState(unchanged); - } + GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken().then(() => { + if (exportState && this._editorView) { + const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); + const equalTitles = dataDoc.title === exportState.title; + const unchanged = equalContent && equalTitles; + dataDoc.unchanged = unchanged; + DocumentButtonBar.Instance.setPullState(unchanged); + } + }); } clipboardTextSerializer = (slice: Slice): string => { diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 1441a8621..ed122e544 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -16,7 +16,7 @@ filesPath = "../../server/public/files" image_dist = filesPath + "/images/buxton" db = MongoClient("localhost", 27017)["Dash"] -target_collection = db.newDocuments +target_collection = db.documents target_doc_title = "Collection 1" schema_guids = [] common_proto_id = "" diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index 9e70af2eb..bd80d6500 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -1,12 +1,12 @@ import ApiManager, { Registration } from "./ApiManager"; -import { Method, _permission_denied, PublicHandler } from "../RouteManager"; +import { Method, _permission_denied } from "../RouteManager"; import { WebSocket } from "../Websocket/Websocket"; import { Database } from "../database"; import rimraf = require("rimraf"); -import { pathToDirectory, Directory } from "./UploadManager"; import { filesDirectory } from ".."; import { DashUploadUtils } from "../DashUploadUtils"; import { mkdirSync } from "fs"; +import RouteSubscriber from "../RouteSubscriber"; export default class DeleteManager extends ApiManager { @@ -14,68 +14,39 @@ export default class DeleteManager extends ApiManager { register({ method: Method.GET, - subscription: "/delete", - secureHandler: async ({ res, isRelease }) => { + subscription: new RouteSubscriber("delete").add("target?"), + secureHandler: async ({ req, res, isRelease }) => { if (isRelease) { - return _permission_denied(res, deletionPermissionError); + return _permission_denied(res, "Cannot perform a delete operation outside of the development environment!"); } - await WebSocket.deleteFields(); - res.redirect("/home"); - } - }); - register({ - method: Method.GET, - subscription: "/deleteAll", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); + const { target } = req.params; + const { doDelete } = WebSocket; + + if (!target) { + await doDelete(); + } else { + let all = false; + switch (target) { + case "all": + all = true; + case "database": + await doDelete(false); + if (!all) break; + case "files": + rimraf.sync(filesDirectory); + mkdirSync(filesDirectory); + await DashUploadUtils.buildFileDirectories(); + break; + default: + await Database.Instance.dropSchema(target); + } } - await WebSocket.deleteAll(); - res.redirect("/home"); - } - }); - register({ - method: Method.GET, - subscription: "/deleteAssets", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - rimraf.sync(filesDirectory); - mkdirSync(filesDirectory); - await DashUploadUtils.buildFileDirectories(); - res.redirect("/delete"); - } - }); - - register({ - method: Method.GET, - subscription: "/deleteWithAux", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - await Database.Auxiliary.DeleteAll(); - res.redirect("/delete"); - } - }); - - register({ - method: Method.GET, - subscription: "/deleteWithGoogleCredentials", - secureHandler: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll(); - res.redirect("/delete"); + res.redirect("/home"); } }); } -} - -const deletionPermissionError = "Cannot perform a delete operation outside of the development environment!"; +} \ No newline at end of file diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index a5240edbc..17968cc7d 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -1,10 +1,8 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied } from "../RouteManager"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; -import { Database } from "../database"; import RouteSubscriber from "../RouteSubscriber"; - -const deletionPermissionError = "Cannot perform specialized delete outside of the development environment!"; +import { Database } from "../database"; const EndpointHandlerMap = new Map([ ["create", (api, params) => api.create(params)], @@ -20,11 +18,11 @@ export default class GeneralGoogleManager extends ApiManager { method: Method.GET, subscription: "/readGoogleAccessToken", secureHandler: async ({ user, res }) => { - const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); - if (!token) { + const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id)); + if (!credentials?.access_token) { return res.send(GoogleApiServerUtils.generateAuthenticationUrl()); } - return res.send(token); + return res.send(credentials); } }); @@ -36,6 +34,15 @@ export default class GeneralGoogleManager extends ApiManager { } }); + register({ + method: Method.GET, + subscription: "/revokeGoogleAccessToken", + secureHandler: async ({ user, res }) => { + await Database.Auxiliary.GoogleAuthenticationToken.Revoke(user.id); + res.send(); + } + }); + register({ method: Method.POST, subscription: new RouteSubscriber("googleDocs").add("sector", "action"), diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 88219423d..11841a603 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -56,7 +56,7 @@ export default class GooglePhotosManager extends ApiManager { const { media } = req.body; // first we need to ensure that we know the google account to which these photos will be uploaded - const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); + const token = (await GoogleApiServerUtils.retrieveCredentials(user.id))?.credentials?.access_token; if (!token) { return _error(res, authenticationError); } diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts index bcaa6598f..fa2f6002a 100644 --- a/src/server/ApiManagers/SessionManager.ts +++ b/src/server/ApiManagers/SessionManager.ts @@ -55,7 +55,7 @@ export default class SessionManager extends ApiManager { register({ method: Method.GET, - subscription: this.secureSubscriber("delete"), + subscription: this.secureSubscriber("deleteSession"), secureHandler: this.authorizedAction(async ({ res }) => { const { error } = await sessionAgent.serverWorker.emit("delete"); res.send(error ? error.message : "Your request was successful: the server successfully deleted the database. Return to /home."); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 98f029c7d..b185d3b55 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -171,7 +171,7 @@ export default class UploadManager extends ApiManager { await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => { err && console.log(err); res(); - }, true, "newDocuments")))); + }, true)))); } catch (e) { console.log(e); } unlink(path_2, () => { }); } diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index d9d346cc1..68b3107ae 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -89,8 +89,6 @@ export default class UserManager extends ApiManager { } }); - - register({ method: Method.GET, subscription: "/activity", diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts index 5cbba13de..ef9b88541 100644 --- a/src/server/DashSession/DashSessionAgent.ts +++ b/src/server/DashSession/DashSessionAgent.ts @@ -37,7 +37,7 @@ export class DashSessionAgent extends AppliedSessionAgent { monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to)); monitor.on("backup", this.backup); monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to)); - monitor.on("delete", WebSocket.deleteFields); + monitor.on("delete", WebSocket.doDelete); monitor.coreHooks.onCrashDetected(this.dispatchCrashReport); return sessionKey; } diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts index 5729c3ee5..24745cbb4 100644 --- a/src/server/GarbageCollector.ts +++ b/src/server/GarbageCollector.ts @@ -76,7 +76,7 @@ async function GarbageCollect(full: boolean = true) { if (!fetchIds.length) { continue; } - const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res, "newDocuments")); + const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res)); for (const doc of docs) { const id = doc.id; if (doc === undefined) { @@ -116,10 +116,10 @@ async function GarbageCollect(full: boolean = true) { const count = Math.min(toDelete.length, 5000); const toDeleteDocs = toDelete.slice(i, i + count); i += count; - const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }, "newDocuments"); + const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }); deleted += result.deletedCount || 0; } - // const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments"); + // const result = await Database.Instance.delete({ _id: { $in: toDelete } }); console.log(`${deleted} documents deleted`); await Search.deleteDocuments(toDelete); diff --git a/src/server/IDatabase.ts b/src/server/IDatabase.ts index 6a63df485..dd4968579 100644 --- a/src/server/IDatabase.ts +++ b/src/server/IDatabase.ts @@ -2,7 +2,6 @@ import * as mongodb from 'mongodb'; import { Transferable } from './Message'; export const DocumentsCollection = 'documents'; -export const NewDocumentsCollection = 'newDocuments'; export interface IDatabase { update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): Promise; updateMany(query: any, update: any, collectionName?: string): Promise; @@ -12,12 +11,13 @@ export interface IDatabase { delete(query: any, collectionName?: string): Promise; delete(id: string, collectionName?: string): Promise; - deleteAll(collectionName?: string, persist?: boolean): Promise; + dropSchema(...schemaNames: string[]): Promise; insert(value: any, collectionName?: string): Promise; getDocument(id: string, fn: (result?: Transferable) => void, collectionName?: string): void; getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName?: string): void; + getCollectionNames(): Promise; visit(ids: string[], fn: (result: any) => string[] | Promise, collectionName?: string): Promise; query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise; diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts index 543f96e7f..1f1d702d9 100644 --- a/src/server/MemoryDatabase.ts +++ b/src/server/MemoryDatabase.ts @@ -1,4 +1,4 @@ -import { IDatabase, DocumentsCollection, NewDocumentsCollection } from './IDatabase'; +import { IDatabase, DocumentsCollection } from './IDatabase'; import { Transferable } from './Message'; import * as mongodb from 'mongodb'; @@ -15,6 +15,10 @@ export class MemoryDatabase implements IDatabase { } } + public getCollectionNames() { + return Promise.resolve(Object.keys(this.db)); + } + public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise { const collection = this.getCollection(collectionName); const set = "$set"; @@ -41,7 +45,7 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve(undefined); } - public updateMany(query: any, update: any, collectionName = NewDocumentsCollection): Promise { + public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise { throw new Error("Can't updateMany a MemoryDatabase"); } @@ -58,8 +62,15 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve({} as any); } - public deleteAll(collectionName = DocumentsCollection, _persist = true): Promise { - delete this.db[collectionName]; + public async dropSchema(...schemaNames: string[]): Promise { + const existing = await this.getCollectionNames(); + let valid: string[]; + if (schemaNames.length) { + valid = schemaNames.filter(collection => existing.includes(collection)); + } else { + valid = existing; + } + valid.forEach(schemaName => delete this.db[schemaName]); return Promise.resolve(); } @@ -69,14 +80,14 @@ export class MemoryDatabase implements IDatabase { return Promise.resolve(); } - public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = NewDocumentsCollection): void { + public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection): void { fn(this.getCollection(collectionName)[id]); } public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection): void { fn(ids.map(id => this.getCollection(collectionName)[id])); } - public async visit(ids: string[], fn: (result: any) => string[] | Promise, collectionName = NewDocumentsCollection): Promise { + public async visit(ids: string[], fn: (result: any) => string[] | Promise, collectionName = DocumentsCollection): Promise { const visited = new Set(); while (ids.length) { const count = Math.min(ids.length, 1000); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index be895c4bc..844535056 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -12,6 +12,7 @@ import { timeMap } from "../ApiManagers/UserManager"; import { green } from "colors"; import { networkInterfaces } from "os"; import executeImport from "../../scraping/buxton/final/BuxtonImporter"; +import { DocumentsCollection } from "../IDatabase"; export namespace WebSocket { @@ -96,7 +97,7 @@ export namespace WebSocket { Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); if (isRelease) { - Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields); + Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false)); } Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField); @@ -163,24 +164,10 @@ export namespace WebSocket { } } - export async function deleteFields() { - await Database.Instance.deleteAll(); - if (process.env.DISABLE_SEARCH !== "true") { - await Search.clear(); - } - await Database.Instance.deleteAll('newDocuments'); - } - - // export async function deleteUserDocuments() { - // await Database.Instance.deleteAll(); - // await Database.Instance.deleteAll('newDocuments'); - // } - - export async function deleteAll() { - await Database.Instance.deleteAll(); - await Database.Instance.deleteAll('newDocuments'); - await Database.Instance.deleteAll('sessions'); - await Database.Instance.deleteAll('users'); + export async function doDelete(onlyFields = true) { + const target: string[] = []; + onlyFields && target.push(DocumentsCollection); + await Database.Instance.dropSchema(...target); if (process.env.DISABLE_SEARCH !== "true") { await Search.clear(); } @@ -210,11 +197,11 @@ export namespace WebSocket { } function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { - Database.Instance.getDocument(id, callback, "newDocuments"); + Database.Instance.getDocument(id, callback); } function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { - Database.Instance.getDocuments(ids, callback, "newDocuments"); + Database.Instance.getDocuments(ids, callback); } const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { @@ -271,7 +258,7 @@ export namespace WebSocket { function UpdateField(socket: Socket, diff: Diff) { Database.Instance.update(diff.id, diff.diff, - () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments"); + () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); const docfield = diff.diff.$set || diff.diff.$unset; if (!docfield) { return; @@ -296,7 +283,7 @@ export namespace WebSocket { } function DeleteField(socket: Socket, id: string) { - Database.Instance.delete({ _id: id }, "newDocuments").then(() => { + Database.Instance.delete({ _id: id }).then(() => { socket.broadcast.emit(MessageStore.DeleteField.Message, id); }); @@ -304,14 +291,14 @@ export namespace WebSocket { } function DeleteFields(socket: Socket, ids: string[]) { - Database.Instance.delete({ _id: { $in: ids } }, "newDocuments").then(() => { + Database.Instance.delete({ _id: { $in: ids } }).then(() => { socket.broadcast.emit(MessageStore.DeleteFields.Message, ids); }); Search.deleteDocuments(ids); } function CreateField(newValue: any) { - Database.Instance.insert(newValue, "newDocuments"); + Database.Instance.insert(newValue); } } diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 0f75833ee..48a8da89f 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -148,26 +148,6 @@ export namespace GoogleApiServerUtils { }); } - /** - * Returns the lengthy string or access token that can be passed into - * the headers of an API request or into the constructor of the Photos - * client API wrapper. - * @param userId the Dash user id of the user requesting his/her associated - * access_token - * @returns the current access_token associated with the requesting - * Dash user. The access_token is valid for only an hour, and - * is then refreshed. - */ - export async function retrieveAccessToken(userId: string): Promise { - return new Promise(async resolve => { - const { credentials } = await retrieveCredentials(userId); - if (!credentials) { - return resolve(); - } - resolve(credentials.access_token!); - }); - } - /** * Manipulates a mapping such that, in the limit, each Dash user has * an associated authenticated OAuth2 client at their disposal. This @@ -216,18 +196,6 @@ export namespace GoogleApiServerUtils { return worker.generateAuthUrl({ scope, access_type: 'offline' }); } - /** - * This is what we return to the server in processNewUser(), after the - * worker OAuth2Client has used the user-pasted authentication code - * to retrieve an access token and an info token. The avatar is the - * URL to the Google-hosted mono-color, single white letter profile 'image'. - */ - export interface GoogleAuthenticationResult { - access_token: string; - avatar: string; - name: string; - } - /** * This method receives the authentication code that the * user pasted into the overlay in the client side and uses the worker @@ -245,7 +213,7 @@ export namespace GoogleApiServerUtils { * and display basic user information in the overlay on successful authentication. * This can be expanded as needed by adding properties to the interface GoogleAuthenticationResult. */ - export async function processNewUser(userId: string, authenticationCode: string): Promise { + export async function processNewUser(userId: string, authenticationCode: string): Promise { const credentials = await new Promise((resolve, reject) => { worker.getToken(authenticationCode, async (err, credentials) => { if (err || !credentials) { @@ -257,12 +225,7 @@ export namespace GoogleApiServerUtils { }); const enriched = injectUserInfo(credentials); await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, enriched); - const { given_name, picture } = enriched.userInfo; - return { - access_token: enriched.access_token!, - avatar: picture, - name: given_name - }; + return enriched; } /** @@ -316,15 +279,15 @@ export namespace GoogleApiServerUtils { * @returns the credentials, or undefined if the user has no stored associated credentials, * and a flag indicating whether or not they were refreshed during retrieval */ - async function retrieveCredentials(userId: string): Promise<{ credentials: Opt, refreshed: boolean }> { - let credentials: Opt = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); + export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt, refreshed: boolean }> { + let credentials = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); let refreshed = false; if (!credentials) { return { credentials: undefined, refreshed }; } // check for token expiry if (credentials.expiry_date! <= new Date().getTime()) { - credentials = await refreshAccessToken(credentials, userId); + credentials = { ...credentials, ...(await refreshAccessToken(credentials, userId)) }; refreshed = true; } return { credentials, refreshed }; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index b10c6f119..5afb90c71 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -293,7 +293,7 @@ export class CurrentUserUtils { { _width: 250, _height: 250, title: "container" }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 }) + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 }); } return [ { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, @@ -315,7 +315,8 @@ export class CurrentUserUtils { // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, - { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, + { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, + { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } @@ -331,21 +332,22 @@ export class CurrentUserUtils { alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); } } - const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)). - map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, - icon: data.icon, - title: data.title, - label: data.label, - ignoreClick: data.ignoreClick, - dropAction: "copy", - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, - onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, - activePen: data.activePen, - backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), - dragFactory: data.dragFactory, - })); + const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); + const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({ + _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, + icon, + title, + label, + ignoreClick, + dropAction: "copy", + onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, + onClick: click ? ScriptField.MakeScript(click) : undefined, + ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, + activePen, + backgroundColor, + removeDropProperties: new List(["dropAction"]), + dragFactory, + })); if (dragCreatorSet === undefined) { doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts index 78e39dbc1..a0b688328 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/models/user_model.ts @@ -74,9 +74,9 @@ userSchema.pre("save", function save(next) { const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) { // Choose one of the following bodies for authentication logic. - // secure + // secure (expected, default) bcrypt.compare(candidatePassword, this.password, cb); - // bypass password + // bypass password (debugging) // cb(undefined, true); }; diff --git a/src/server/database.ts b/src/server/database.ts index ad285765b..9ba461b65 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -4,7 +4,7 @@ import { Opt } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { Credentials } from 'google-auth-library'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; -import { IDatabase } from './IDatabase'; +import { IDatabase, DocumentsCollection } from './IDatabase'; import { MemoryDatabase } from './MemoryDatabase'; import * as mongoose from 'mongoose'; import { Upload } from './SharedMediaTypes'; @@ -14,7 +14,7 @@ export namespace Database { export let disconnect: Function; const schema = 'Dash'; const port = 27017; - export const url = `mongodb://localhost:${port}/`; + export const url = `mongodb://localhost:${port}/${schema}`; enum ConnectionStates { disconnected = 0, @@ -47,28 +47,29 @@ export namespace Database { } export class Database implements IDatabase { - public static DocumentsCollection = 'documents'; private MongoClient = mongodb.MongoClient; private currentWrites: { [id: string]: Promise } = {}; private db?: mongodb.Db; private onConnect: (() => void)[] = []; - doConnect() { + async doConnect() { console.error(`\nConnecting to Mongo with URL : ${url}\n`); - this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => { - console.error("mongo connect response\n"); - if (!client) { - console.error("\nMongo connect failed with the error:\n"); - console.log(_err); - process.exit(0); - } - this.db = client.db(); - this.onConnect.forEach(fn => fn()); + return new Promise(resolve => { + this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => { + console.error("mongo connect response\n"); + if (!client) { + console.error("\nMongo connect failed with the error:\n"); + console.log(_err); + process.exit(0); + } + this.db = client.db(); + this.onConnect.forEach(fn => fn()); + resolve(); + }); }); } - public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) { - + public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) { if (this.db) { const collection = this.db.collection(collectionName); const prom = this.currentWrites[id]; @@ -93,7 +94,7 @@ export namespace Database { } } - public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) { + public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) { if (this.db) { const collection = this.db.collection(collectionName); const prom = this.currentWrites[id]; @@ -117,9 +118,25 @@ export namespace Database { } } + public async getCollectionNames() { + const cursor = this.db?.listCollections(); + const collectionNames: string[] = []; + if (cursor) { + while (await cursor.hasNext()) { + const collection: any = await cursor.next(); + collection && collectionNames.push(collection.name); + } + } + return collectionNames; + } + + public async clear() { + return Promise.all((await this.getCollectionNames()).map(collection => this.dropSchema(collection))); + } + public delete(query: any, collectionName?: string): Promise; public delete(id: string, collectionName?: string): Promise; - public delete(id: any, collectionName = Database.DocumentsCollection) { + public delete(id: any, collectionName = DocumentsCollection) { if (typeof id === "string") { id = { _id: id }; } @@ -131,25 +148,26 @@ export namespace Database { } } - public async deleteAll(collectionName = Database.DocumentsCollection, persist = true): Promise { - return new Promise(resolve => { - const executor = async (database: mongodb.Db) => { - if (persist) { - await database.collection(collectionName).deleteMany({}); - } else { - await database.dropCollection(collectionName); - } - resolve(); - }; - if (this.db) { - executor(this.db); + public async dropSchema(...targetSchemas: string[]): Promise { + const executor = async (database: mongodb.Db) => { + const existing = await Instance.getCollectionNames(); + let valid: string[]; + if (targetSchemas.length) { + valid = targetSchemas.filter(collection => existing.includes(collection)); } else { - this.onConnect.push(() => this.db && executor(this.db)); + valid = existing; } - }); + const pending = Promise.all(valid.map(schemaName => database.dropCollection(schemaName))); + return (await pending).every(dropOutcome => dropOutcome); + }; + if (this.db) { + return executor(this.db); + } else { + this.onConnect.push(() => this.db && executor(this.db)); + } } - public async insert(value: any, collectionName = Database.DocumentsCollection) { + public async insert(value: any, collectionName = DocumentsCollection) { if (this.db) { if ("id" in value) { value._id = value.id; @@ -177,7 +195,7 @@ export namespace Database { } } - public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = "newDocuments") { + public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) { if (this.db) { this.db.collection(collectionName).findOne({ _id: id }, (err, result) => { if (result) { @@ -193,7 +211,7 @@ export namespace Database { } } - public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) { + public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) { if (this.db) { this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => { if (err) { @@ -211,7 +229,7 @@ export namespace Database { } } - public async visit(ids: string[], fn: (result: any) => string[] | Promise, collectionName = "newDocuments"): Promise { + public async visit(ids: string[], fn: (result: any) => string[] | Promise, collectionName = DocumentsCollection): Promise { if (this.db) { const visited = new Set(); while (ids.length) { @@ -238,7 +256,7 @@ export namespace Database { } } - public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = "newDocuments"): Promise { + public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise { if (this.db) { let cursor = this.db.collection(collectionName).find(query); if (projection) { @@ -252,7 +270,7 @@ export namespace Database { } } - public updateMany(query: any, update: any, collectionName = "newDocuments") { + public updateMany(query: any, update: any, collectionName = DocumentsCollection) { if (this.db) { const db = this.db; return new Promise(res => db.collection(collectionName).update(query, update, (_, result) => res(result))); @@ -282,7 +300,8 @@ export namespace Database { export namespace Auxiliary { export enum AuxiliaryCollections { - GooglePhotosUploadHistory = "uploadedFromGooglePhotos" + GooglePhotosUploadHistory = "uploadedFromGooglePhotos", + GoogleAuthentication = "googleAuthentication" } const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => { @@ -306,27 +325,30 @@ export namespace Database { export namespace GoogleAuthenticationToken { - const GoogleAuthentication = "googleAuthentication"; - - export type StoredCredentials = Credentials & { _id: string }; + type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string }; export const Fetch = async (userId: string, removeId = true): Promise> => { - return SanitizedSingletonQuery({ userId }, GoogleAuthentication, removeId); + return SanitizedSingletonQuery({ userId }, AuxiliaryCollections.GoogleAuthentication, removeId); }; export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => { - return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, GoogleAuthentication); + return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAuthentication); }; export const Update = async (userId: string, access_token: string, expiry_date: number) => { const entry = await Fetch(userId, false); if (entry) { const parameters = { $set: { access_token, expiry_date } }; - return Instance.update(entry._id, parameters, emptyFunction, true, GoogleAuthentication); + return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAuthentication); } }; - export const DeleteAll = () => Instance.deleteAll(GoogleAuthentication, false); + export const Revoke = async (userId: string) => { + const entry = await Fetch(userId, false); + if (entry) { + Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAuthentication); + } + }; } @@ -338,12 +360,6 @@ export namespace Database { return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); }; - export const DeleteAll = async (persist = false) => { - const collectionNames = Object.values(AuxiliaryCollections); - const pendingDeletions = collectionNames.map(name => Instance.deleteAll(name, persist)); - return Promise.all(pendingDeletions); - }; - } } diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts index 45d2fdd33..91a3cb6bf 100644 --- a/src/server/remapUrl.ts +++ b/src/server/remapUrl.ts @@ -1,6 +1,4 @@ import { Database } from "./database"; -import { Search } from "./Search"; -import * as path from 'path'; //npx ts-node src/server/remapUrl.ts @@ -50,7 +48,7 @@ async function update() { return new Promise(res => Database.Instance.update(doc[0], doc[1], () => { console.log("wrote " + JSON.stringify(doc[1])); res(); - }, false, "newDocuments")); + }, false)); })); console.log("Done"); // await Promise.all(updates.map(update => { -- cgit v1.2.3-70-g09d2 From fa826d828b0fc20afde675ffb060e4f24ca310d3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 6 May 2020 02:01:08 -0400 Subject: fixed makeClone to handle text hyperlinks fixed documentBox background color. fixed formattedtextbox sidebar clicking to sto propagation. --- src/client/views/MainView.tsx | 4 +- src/client/views/nodes/DocHolderBox.scss | 1 - src/client/views/nodes/DocHolderBox.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 123 +++++++-------------- src/new_fields/Doc.ts | 72 +++++++----- src/new_fields/documentSchemas.ts | 1 + 6 files changed, 92 insertions(+), 114 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1a285d4ec..e67f2f3a2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -300,7 +300,7 @@ export class MainView extends React.Component { defaultBackgroundColors = (doc: Doc) => { if (this.darkScheme) { - switch (doc.type) { + switch (doc?.type) { case DocumentType.RTF || DocumentType.LABEL || DocumentType.BUTTON: return "#2d2d2d"; case DocumentType.LINK: case DocumentType.COL: { @@ -309,7 +309,7 @@ export class MainView extends React.Component { default: return "black"; } } else { - switch (doc.type) { + switch (doc?.type) { case DocumentType.RTF: return "#f1efeb"; case DocumentType.BUTTON: case DocumentType.LABEL: return "lightgray"; diff --git a/src/client/views/nodes/DocHolderBox.scss b/src/client/views/nodes/DocHolderBox.scss index 4949d16a3..6a9ef0b6f 100644 --- a/src/client/views/nodes/DocHolderBox.scss +++ b/src/client/views/nodes/DocHolderBox.scss @@ -2,7 +2,6 @@ width: 100%; height: 100%; pointer-events: all; - background: rgb(241, 239, 235); position: absolute; .documentBox-lock { margin: auto; diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index 17b2daabc..af8dc704c 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -122,6 +122,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent; private _applyingChange: boolean = false; private _searchIndex = 0; - private _sidebarMovement = 0; - private _lastX = 0; - private _lastY = 0; private _undoTyping?: UndoManager.Batch; private _disposers: { [name: string]: IReactionDisposer } = {}; private dropDisposer?: DragManager.DragDropDisposer; @@ -161,7 +158,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); + else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); }); }); }); @@ -199,7 +196,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); // the actual text in the text box + const curTemp = Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField); // the actual text in the text box const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); @@ -207,7 +204,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._applyingChange = true; this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (curText !== curLayout?.Text) { + if (json !== curLayout?.Data) { this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited } @@ -241,7 +238,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp res.map(r => r.map(h => flattened.push(h))); const lastSel = Math.min(flattened.length - 1, this._searchIndex); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!; + const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!; const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", location: location, linkId: alink[Id], targetId: target[Id] @@ -280,7 +277,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp protected createDropTarget = (ele: HTMLDivElement) => { this.ProseRef = ele; this.dropDisposer?.(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); } @undoBatch @@ -399,26 +396,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } sidebarDown = (e: React.PointerEvent) => { - this._lastX = e.clientX; - this._lastY = e.clientY; - this._sidebarMovement = 0; - document.addEventListener("pointermove", this.sidebarMove); - document.addEventListener("pointerup", this.sidebarUp); - e.stopPropagation(); - e.preventDefault(); // prevents text from being selected during drag + setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, + () => (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%")); } - sidebarMove = (e: PointerEvent) => { + sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this.CurrentDiv.getBoundingClientRect(); - this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY)); - this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; - } - sidebarUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.sidebarMove); - document.removeEventListener("pointerup", this.sidebarUp); + this.layoutDoc._sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; + return false; } - toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"); - public static get DefaultLayout(): Doc | string | undefined { return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); } @@ -426,12 +412,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; - this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); + this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); !this.layoutDoc.isTemplateDoc && funcs.push({ description: "Make Template", event: () => { this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); + Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); this.layoutDoc.isTemplateDoc && funcs.push({ @@ -451,9 +437,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); - funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); const highlighting: ContextMenuProps[] = []; @@ -480,7 +466,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DocListCast(noteTypesDoc?.data).forEach(note => { changeItems.push({ description: StrCast(note.title), event: undoBatch(() => { - Doc.setNativeView(this.props.Document); + Doc.setNativeView(this.rootDoc); Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); }), icon: "eye" }); @@ -517,7 +503,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action toggleMenubar = () => { - this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; + this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled"; } recordBullet = async () => { @@ -630,10 +616,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ); this._disposers.editorState = reaction( () => { - if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { + if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) { return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; } - return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; + return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; }, incomingValue => { if (incomingValue !== undefined && this._editorView && !this._applyingChange) { @@ -690,7 +676,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const nodes: Node[] = []; frag.forEach((node, index) => { const examinedNode = findLinkNode(node, editor); - if (examinedNode && examinedNode.textContent) { + if (examinedNode?.textContent) { nodes.push(examinedNode); start += index; } @@ -707,7 +693,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; }; - let start = -1; + let start = 0; if (this._editorView && scrollToLinkID) { const editor = this._editorView; const ret = findLinkFrag(editor.state.doc.content, editor); @@ -728,7 +714,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); - this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), + this._disposers.scroll = reaction(() => NumCast(this.layoutDoc.scrollPos), pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } ); @@ -849,7 +835,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp targetAnnotations?.push(pdfRegion); }); - const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); + const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted"); if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); const linkId = link[Id]; @@ -886,8 +872,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private setupEditor(config: any, fieldKey: string) { const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); - const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"]; - const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); + const useTemplate = !curText?.Text && this.layoutDoc[this.props.fieldKey + "-textTemplate"]; + const rtfField = Cast((useTemplate && this.layoutDoc[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { const self = this; this._editorView?.destroy(); @@ -983,9 +969,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { e.preventDefault(); } - if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar - //e.stopPropagation(); + if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar + e.stopPropagation(); // if the text box is selected, then it consumes all down events } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1023,7 +1009,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // jump rich text menu to this textbox const bounds = this._ref.current?.getBoundingClientRect(); - if (bounds && this.props.Document._chromeStatus !== "disabled") { + if (bounds && this.layoutDoc._chromeStatus !== "disabled") { const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { @@ -1059,43 +1045,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } (e.nativeEvent as any).formattedHandled = true; - // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) { - // let href = (e.target as any).href; - // let location: string; - // if ((e.target as any).attributes.location) { - // location = (e.target as any).attributes.location.value; - // } - // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); - // if (node) { - // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). - // href = link && link.attrs.href; - // location = link && link.attrs.location; - // } - // } - // if (href) { - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - // if (linkClicked) { - // DocServer.GetRefField(linkClicked).then(async linkDoc => { - // (linkDoc instanceof Doc) && - // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false); - // }); - // } - // } else { - // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); - // this.props.addDocument && this.props.addDocument(webDoc); - // } - // e.stopPropagation(); - // e.preventDefault(); - // } - // } if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) { this.props.select(e.ctrlKey); this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); } + if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events + e.stopPropagation(); + } } // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. @@ -1212,7 +1169,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onscrolled = (ev: React.UIEvent) => { - this.props.Document.scrollPos = this._scrollRef.current!.scrollTop; + this.layoutDoc.scrollPos = this._scrollRef.current!.scrollTop; } @action tryUpdateHeight(limitHeight?: number) { @@ -1243,7 +1200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } + @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0); @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } @@ -1306,8 +1263,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} />
- {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? -
this.toggleSidebar()} /> : + {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? +
:
-
this.toggleSidebar()} /> +
} - {!this.props.Document._showAudio ? (null) : + {!this.layoutDoc._showAudio ? (null) :
{ runInAction(() => this._recording = !this._recording); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index b41007e9c..2d4a4539e 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -6,7 +6,7 @@ import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; import { UndoManager } from "../client/util/UndoManager"; -import { intersectRect } from "../Utils"; +import { intersectRect, Utils } from "../Utils"; import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; @@ -20,6 +20,7 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u import { Docs, DocumentOptions } from "../client/documents/Documents"; import { PdfField, VideoField, AudioField, ImageField } from "./URLField"; import { LinkManager } from "../client/util/LinkManager"; +import { string32 } from "../../deploy/assets/pdf.worker"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -590,47 +591,64 @@ export namespace Doc { return copy; } - export function MakeClone(doc: Doc, cloneMap: Map = new Map()): Doc { + export function MakeClone(doc: Doc): Doc { + let cloneMap = new Map(); + let rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; + const copy = Doc.makeClone(doc, cloneMap, rtfMap); + rtfMap.map(({ copy, key, field }) => { + const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; + }; + const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return href + (mapped ? mapped[Id] : id); + }; + const regex = `(${Utils.prepend("/doc/")})([^"]*)`; + var re = new RegExp(regex, "g"); + copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); + }); + return copy; + } + + export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { if (Doc.IsBaseProto(doc)) return doc; - if (cloneMap.get(doc)) return cloneMap.get(doc)!; + if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = new Doc(undefined, true); - cloneMap.set(doc, copy); - if (LinkManager.Instance.getAllLinks().includes(doc)) LinkManager.Instance.addLink(copy); + cloneMap.set(doc[Id], copy); + if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); const exclude = Cast(doc.excludeFields, listSpec("string"), []); Object.keys(doc).forEach(key => { if (exclude.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); + const copyObjectField = (field: ObjectField) => { + const list = Cast(doc[key], listSpec(Doc)); + if (list !== undefined && !(list instanceof Promise)) { + copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); + } else if (doc[key] instanceof Doc) + copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields + else { + copy[key] = ObjectField.MakeCopy(field); + if (field instanceof RichTextField) { + if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { + rtfs.push({ copy, key, field }); + } + } + } + } if (key === "proto") { if (doc[key] instanceof Doc) { - copy[key] = Doc.MakeClone(doc[key]!, cloneMap); + copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); } } else { if (field instanceof RefField) { copy[key] = field; } else if (cfield instanceof ComputedField) { copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - if (field instanceof ObjectField) { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.MakeClone(d as Doc, cloneMap))); - } else { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? - undefined : Doc.MakeClone(doc[key] as Doc, cloneMap) : // reference documents except copy documents that are expanded teplate fields - ObjectField.MakeCopy(field); - } - } + (key === "links" && field instanceof ObjectField) && copyObjectField(field); } else if (field instanceof ObjectField) { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.MakeClone(d as Doc, cloneMap))); - } else { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? - undefined : Doc.MakeClone(doc[key] as Doc, cloneMap) : // reference documents except copy documents that are expanded teplate fields - ObjectField.MakeCopy(field); - } + copyObjectField(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... } else { @@ -640,7 +658,7 @@ export namespace Doc { }); Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); copy.cloneOf = doc; - cloneMap.set(doc, copy); + cloneMap.set(doc[Id], copy); return copy; } diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e7d27d81e..cacba43b6 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -44,6 +44,7 @@ export const documentSchema = createSchema({ _chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed' _fontSize: "number", _fontFamily: "string", + _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar // appearance properties on the data document backgroundColor: "string", // background color of document -- cgit v1.2.3-70-g09d2 From fcd0895e5933270718b9c1a262e7e377eaabb024 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 7 May 2020 19:18:52 -0400 Subject: fixed most add/remove/move documents to support doc[]'s --- src/client/util/DragManager.ts | 4 +- src/client/views/DocComponent.tsx | 37 +++++++++---- src/client/views/MainView.tsx | 6 +- src/client/views/TemplateMenu.tsx | 6 +- src/client/views/collections/CollectionSubView.tsx | 14 ++--- .../views/collections/CollectionTreeView.tsx | 64 +++++++++++++--------- src/client/views/collections/CollectionView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 12 ++-- src/client/views/nodes/FieldView.tsx | 6 +- src/client/views/nodes/PresBox.tsx | 9 ++- src/client/views/nodes/VideoBox.tsx | 9 ++- 13 files changed, 107 insertions(+), 72 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0d208cf1b..cc8cc38dd 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -77,8 +77,8 @@ export namespace DragManager { return root; } export let AbortDrag: () => void = emptyFunction; - export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; - export type RemoveFunction = (document: Doc) => boolean; + export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; + export type RemoveFunction = (document: Doc | Doc[]) => boolean; export interface DragDropDisposer { (): void; } export interface DragOptions { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 11866aebe..f602cbb15 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,4 +1,4 @@ -import { Doc, Opt, DataSym } from '../../new_fields/Doc'; +import { Doc, Opt, DataSym, DocListCast } from '../../new_fields/Doc'; import { Touchable } from './Touchable'; import { computed, action, observable } from 'mobx'; import { Cast, BoolCast, ScriptCast } from '../../new_fields/Types'; @@ -6,6 +6,8 @@ import { listSpec } from '../../new_fields/Schema'; import { InkingControl } from './InkingControl'; import { InkTool } from '../../new_fields/InkField'; import { InteractionUtils } from '../util/InteractionUtils'; +import { List } from '../../new_fields/List'; +import { DateField } from '../../new_fields/DateField'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) @@ -99,22 +101,37 @@ export function ViewBoxAnnotatableComponent

d as Doc), true) : -1; - return index !== -1 && value && value.splice(index, 1) ? true : false; + removeDocument(doc: Doc | Doc[]): boolean { + const docs = doc instanceof Doc ? [doc] : doc; + docs.map(doc => doc.annotationOn = undefined); + const targetDataDoc = this.dataDoc; + const value = DocListCast(targetDataDoc[this.props.fieldKey + "-" + this._annotationKey]); + const result = value.filter(v => !docs.includes(v)); + if (result.length !== value.length) { + targetDataDoc[this.props.fieldKey] = new List(result); + return true; + } + return false; } // if the moved document is already in this overlay collection nothing needs to be done. // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. @action.bound - moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean { + moveDocument(doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean { return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false; } @action.bound - addDocument(doc: Doc): boolean { - doc.context = Doc.GetProto(doc).annotationOn = this.props.Document; - return Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-" + this._annotationKey, doc) ? true : false; + addDocument(doc: Doc | Doc[]): boolean { + const docs = doc instanceof Doc ? [doc] : doc; + docs.map(doc => doc.context = Doc.GetProto(doc).annotationOn = this.props.Document); + const targetDataDoc = this.props.Document[DataSym]; + const docList = DocListCast(targetDataDoc[this.props.fieldKey + "-" + this._annotationKey]); + const added = docs.filter(d => !docList.includes(d)); + if (added.length) { + added.map(doc => doc.context = this.props.Document); + targetDataDoc[this.props.fieldKey + "-" + this._annotationKey] = new List([...docList, ...added]); + targetDataDoc[this.props.fieldKey + "-" + this._annotationKey + "-lastModified"] = new DateField(new Date(Date.now())); + } + return true; } whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e67f2f3a2..5b838446d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -520,9 +520,9 @@ export class MainView extends React.Component { return !this._flyoutTranslate ? (

) : (null); } - addButtonDoc = (doc: Doc) => Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc); - remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc); - moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); + addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); + remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); + moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 665ab4e41..5f300372f 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -158,9 +158,9 @@ export class TemplateMenu extends React.Component { annotationsKey={""} dontRegisterView={true} fieldKey={"data"} - moveDocument={(doc: Doc) => false} - removeDocument={(doc: Doc) => false} - addDocument={(doc: Doc) => false} /> + moveDocument={returnFalse} + removeDocument={returnFalse} + addDocument={returnFalse} /> ; } } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 250c9b3b0..ae84b1923 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -27,9 +27,9 @@ import { CollectionView } from "./CollectionView"; import React = require("react"); export interface CollectionViewProps extends FieldViewProps { - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; + addDocument: (document: Doc | Doc[]) => boolean; + removeDocument: (document: Doc | Doc[]) => boolean; + moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; PanelWidth: () => number; PanelHeight: () => number; VisibleHeight?: () => number; @@ -212,14 +212,14 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: if (docDragData) { let added = false; if (docDragData.dropAction || docDragData.userDropAction) { - added = this.props.addDocument(docDragData.droppedDocuments as any as Doc); + added = this.props.addDocument(docDragData.droppedDocuments); } else if (docDragData.moveDocument) { const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); - const res = addedDocs.length ? this.props.addDocument(addedDocs as any as Doc) : true; - added = movedDocs.length ? docDragData.moveDocument(movedDocs as any as Doc, this.props.Document, this.props.addDocument) : res; + const res = addedDocs.length ? this.props.addDocument(addedDocs) : true; + added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, this.props.addDocument) : res; } else { - added = this.props.addDocument(docDragData.droppedDocuments as any as Doc); + added = this.props.addDocument(docDragData.droppedDocuments); } e.stopPropagation(); return added; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4fb54cc07..815202cfe 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -44,7 +44,7 @@ export interface TreeViewProps { containingCollection: Doc; prevSibling?: Doc; renderDepth: number; - deleteDoc: (doc: Doc) => boolean; + deleteDoc: (doc: Doc | Doc[]) => boolean; moveDocument: DragManager.MoveFunction; dropAction: dropActionType; addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; @@ -52,7 +52,7 @@ export interface TreeViewProps { panelWidth: () => number; panelHeight: () => number; ChromeHeight: undefined | (() => number); - addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean; + addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; indentDocument?: () => void; outdentDocument?: () => void; ScreenToLocalTransform: () => Transform; @@ -133,14 +133,16 @@ class TreeView extends React.Component { @undoBatch delete = () => this.props.deleteDoc(this.props.document); @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath); @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete(); - @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => { + @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); } - @undoBatch @action remove = (document: Document, key: string) => { - return Doc.RemoveDocFromList(this.dataDoc, key, document); + @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => + flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); } - @undoBatch @action removeDoc = (document: Document) => { - return Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), document); + @undoBatch @action removeDoc = (doc: Doc | Doc[]) => { + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => + flg && Doc.RemoveDocFromList(this.props.containingCollection, Doc.LayoutFieldKey(this.props.containingCollection), doc), true); } protected createTreeDropTarget = (ele: HTMLDivElement) => { @@ -224,9 +226,10 @@ class TreeView extends React.Component { if (de.complete.docDragData) { e.stopPropagation(); if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true; - let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before); + let addDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); if (inside) { - addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc); + addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce( + ((flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc)), true) || addDoc(doc); } const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments); const move = de.complete.docDragData.dropAction === "move" || de.complete.docDragData.dropAction; @@ -286,8 +289,9 @@ class TreeView extends React.Component { let contentElement: (JSX.Element | null)[] | JSX.Element = []; if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { - const remDoc = (doc: Doc) => this.remove(doc, key); - const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); + const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); + const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce( + (flg, doc) => flg && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true), true); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, @@ -330,8 +334,9 @@ class TreeView extends React.Component { TraceMobx(); const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; if (expandKey !== undefined) { - const remDoc = (doc: Doc) => this.remove(doc, expandKey); - const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true); + 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 sortKey = `${this.fieldKey}-sortAscending`; return
    { @@ -464,6 +469,7 @@ class TreeView extends React.Component { ref={this._docRef} Document={this.props.document} DataDoc={undefined} + treeViewId={this.props.treeViewId[Id]} LibraryPath={this.props.libraryPath || emptyPath} addDocument={undefined} addDocTab={this.props.addDocTab} @@ -529,8 +535,8 @@ class TreeView extends React.Component { key: string, parentCollectionDoc: Doc | undefined, parentPrevSibling: Doc | undefined, - add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean, - remove: ((doc: Doc) => boolean), + add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, + remove: ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => boolean, @@ -619,7 +625,7 @@ class TreeView extends React.Component { remove(child); } }; - const addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => { + const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => { return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); }; const childLayout = Doc.Layout(pair.layout); @@ -689,22 +695,26 @@ export class CollectionTreeView extends CollectionSubView { - const children = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []); - if (children.indexOf(document) !== -1) { - children.splice(children.indexOf(document), 1); + remove = (doc: Doc | Doc[]): boolean => { + const docs = doc instanceof Doc ? [doc] : doc as Doc[]; + const targetDataDoc = this.props.Document[DataSym]; + const value = DocListCast(targetDataDoc[this.props.fieldKey]); + const result = value.filter(v => !docs.includes(v)); + if (result.length !== value.length) { + targetDataDoc[this.props.fieldKey] = new List(result); return true; } return false; } @action - addDoc = (doc: Document, relativeTo: Opt, before?: boolean): boolean => { - const doAddDoc = () => - Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false); + addDoc = (doc: Doc | Doc[], relativeTo: Opt, before?: boolean): boolean => { + const doAddDoc = (doc: Doc | Doc[]) => + (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => + flg && Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true); if (this.props.Document.resolvedDataDoc instanceof Promise) { - this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc()); + this.props.Document.resolvedDataDoc.then((resolved: any) => doAddDoc(doc)); } else { - doAddDoc(); + doAddDoc(doc); } return true; } @@ -788,8 +798,8 @@ export class CollectionTreeView extends CollectionSubView this.addDoc(doc, relativeTo, before); - const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); + const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); + const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(d, target, addDoc); const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs; return !childDocs ? (null) : (
    this.props.whenActiveChanged(this._isChildActive = isActive); @action.bound - addDocument = (doc: Doc): boolean => { + addDocument = (doc: Doc | Doc[]): boolean => { if (doc instanceof Doc) { if (this.props.filterAddDocument?.(doc) === false) { return false; } } - const docs = doc instanceof Doc ? [doc] : doc as any as Doc[]; + const docs = doc instanceof Doc ? [doc] : doc; const targetDataDoc = this.props.Document[DataSym]; const docList = DocListCast(targetDataDoc[this.props.fieldKey]); const added = docs.filter(d => !docList.includes(d)); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6011d5f9c..b32318d66 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -123,7 +123,7 @@ export class CollectionFreeFormView extends CollectionSubView { + private addDocument = (newBox: Doc | Doc[]) => { if (newBox instanceof Doc) { const added = this.props.addDocument(newBox); added && this.bringToFront(newBox); @@ -184,7 +184,7 @@ export class CollectionFreeFormView extends CollectionSubView { - this.props.removeDocument(this.marqueeSelect(false) as any as Doc); + this.props.removeDocument(this.marqueeSelect(false)); SelectionManager.DeselectAll(); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -352,7 +352,7 @@ export class MarqueeView extends React.Component void; - addDocument?: (doc: Doc) => boolean; - removeDocument?: (doc: Doc) => boolean; - moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; + addDocument?: (doc: Doc | Doc[]) => boolean; + removeDocument?: (doc: Doc | Doc[]) => boolean; + moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; ScreenToLocalTransform: () => Transform; setupDragLines?: () => void; renderDepth: number; @@ -243,6 +244,7 @@ export class DocumentView extends DocComponent(Docu dragData.removeDocument = this.props.removeDocument; dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument; dragData.dragDivName = this.props.dragDivName; + dragData.treeViewId = this.props.treeViewId; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart }); } } @@ -1043,7 +1045,7 @@ export class DocumentView extends DocComponent(Docu makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined. @undoBatch - hideLinkAnchor = (doc: Doc) => doc.hidden = true + hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true) anchorPanelWidth = () => this.props.PanelWidth() || 1; anchorPanelHeight = () => this.props.PanelHeight() || 1; @computed get anchors() { @@ -1143,7 +1145,7 @@ export class DocumentView extends DocComponent(Docu const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way - return
    void; rootSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; - addDocument?: (document: Doc) => boolean; + addDocument?: (document: Doc | Doc[]) => boolean; addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; - removeDocument?: (document: Doc) => boolean; - moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; + removeDocument?: (document: Doc | Doc[]) => boolean; + moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; backgroundColor?: (document: Doc) => string | undefined; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 343e74c87..49862d39a 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -271,9 +271,12 @@ export class PresBox extends ViewBoxBaseComponent }); whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); - addDocumentFilter = (doc: Doc) => { - doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); - !this.childDocs.includes(doc) && (doc.presZoomButton = true); + addDocumentFilter = (doc: Doc|Doc[]) => { + const docs = doc instanceof Doc ? [doc]: doc; + docs.forEach(doc => { + doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); + !this.childDocs.includes(doc) && (doc.presZoomButton = true); + }); return true; } childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 266b7f43f..208b93ba6 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -337,9 +337,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + const curTime = (this.layoutDoc.currentTimecode || -1); + curTime !== -1 && (doc.displayTimecode = curTime); + }) return this.addDocument(doc); } -- cgit v1.2.3-70-g09d2 From 78e0b57cab3a5fea7421ca4661d890b635bb4790 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 7 May 2020 20:36:31 -0400 Subject: fixed gear in tab panes showing up. fixed tree view titling. fixed some current_user_util templates to drag as render templates, not prototypes. --- src/client/views/MainView.tsx | 21 ++++++++- .../views/collections/CollectionDockingView.tsx | 12 ++--- .../views/collections/CollectionTreeView.tsx | 52 +++++++++------------- .../authentication/models/current_user_utils.ts | 14 ++++-- 4 files changed, 57 insertions(+), 42 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5b838446d..fad6bf295 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,12 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; +import { + faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, + faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, + faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, + faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, + faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, + faThumbtack, faTree, faTv, faUndoAlt, faVideo +} from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -105,6 +112,18 @@ export class MainView extends React.Component { } } + library.add(faTrashAlt); + library.add(faAngleRight); + library.add(faBell); + library.add(faTrash); + library.add(faCamera); + library.add(faExpand); + library.add(faCaretDown); + library.add(faCaretRight); + library.add(faCaretSquareDown); + library.add(faCaretSquareRight); + library.add(faArrowsAltH); + library.add(faPlus, faMinus); library.add(faTerminal); library.add(faToggleOn); library.add(faLocationArrow); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6f5989052..3c33d4481 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -520,11 +520,13 @@ export class CollectionDockingView extends React.Component ((view: Opt) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)), (views) => { - ReactDOM.render( - views} Stack={stack} /> - , - gearSpan); - tab.buttonDisposer?.(); + if (views.length) { + ReactDOM.render( + views} Stack={stack} /> + , + gearSpan); + tab.buttonDisposer?.(); + } }); tab.reactComponents = [gearSpan]; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 815202cfe..92018a5b8 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,7 +1,5 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, untracked } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; @@ -36,7 +34,6 @@ import React = require("react"); import { makeTemplate } from '../../util/DropConverter'; import { TraceMobx } from '../../../new_fields/util'; - export interface TreeViewProps { document: Doc; dataDoc?: Doc; @@ -63,24 +60,12 @@ export interface TreeViewProps { active: (outsideReaction?: boolean) => boolean; treeViewHideHeaderFields: () => boolean; treeViewPreventOpen: boolean; - renderedIds: string[]; + renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle onCheckedClick?: ScriptField; onChildClick?: ScriptField; ignoreFields?: string[]; } -library.add(faTrashAlt); -library.add(faAngleRight); -library.add(faBell); -library.add(faTrash); -library.add(faCamera); -library.add(faExpand); -library.add(faCaretDown); -library.add(faCaretRight); -library.add(faCaretSquareDown); -library.add(faCaretSquareRight); -library.add(faArrowsAltH); -library.add(faPlus, faMinus); @observer /** * Renders a treeView of a collection of documents @@ -91,13 +76,14 @@ library.add(faPlus, faMinus); * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ class TreeView extends React.Component { + static _editTitleScript: ScriptField | undefined; private _header?: React.RefObject = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); private _tref = React.createRef(); + private _docRef = React.createRef(); get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive - get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "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; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; } @@ -111,7 +97,7 @@ class TreeView extends React.Component { } childDocList(field: string) { const layout = Doc.LayoutField(this.props.document) instanceof Doc ? Doc.LayoutField(this.props.document) as Doc : undefined; - return ((this.props.dataDoc ? Cast(this.props.dataDoc[field], listSpec(Doc)) : undefined) || + return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) || (layout ? Cast(layout[field], listSpec(Doc)) : undefined) || Cast(this.props.document[field], listSpec(Doc))) as Doc[]; } @@ -411,7 +397,10 @@ class TreeView extends React.Component { @computed get renderBullet() { const checked = this.props.document.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined; - return
    + return
    {}
    ; } @@ -425,8 +414,6 @@ class TreeView extends React.Component { const focusScript = ScriptField.MakeFunction(`DocFocus(self)`); return [{ script: focusScript!, label: "Focus" }]; } - _docRef = React.createRef(); - _editTitleScript: ScriptField | undefined; /** * Renders the EditableView title element for placement into the tree. */ @@ -434,8 +421,7 @@ class TreeView extends React.Component { get renderTitle() { TraceMobx(); const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true); - //!this._editTitleScript && (this._editTitleScript = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)")); - + (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(this, 'editTitle', true)")); const headerElements = ( <> this.showContextMenu(e)}> @@ -475,7 +461,7 @@ class TreeView extends React.Component { addDocTab={this.props.addDocTab} rootSelected={returnTrue} pinToPres={emptyFunction} - onClick={this.props.onChildClick || this._editTitleScript} + onClick={this.props.onChildClick || TreeView._editTitleScript} dropAction={this.props.dropAction} moveDocument={this.move} removeDocument={this.removeDoc} @@ -512,12 +498,14 @@ class TreeView extends React.Component { e.stopPropagation(); e.preventDefault(); } - }} onPointerDown={e => { - if (this.props.active(true)) { - e.stopPropagation(); - e.preventDefault(); - } - }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + }} + onPointerDown={e => { + if (this.props.active(true)) { + e.stopPropagation(); + e.preventDefault(); + } + }} + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> {this.renderBullet} {this.renderTitle}
    @@ -790,7 +778,7 @@ export class CollectionTreeView extends CollectionSubView
    ; } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 6e06fe931..8a0fbeabe 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -50,7 +50,7 @@ export class CurrentUserUtils { ); queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); doc["template-button-query"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, removeDropProperties: new List(["dropAction"]), title: "query view", icon: "question-circle" }); @@ -66,7 +66,7 @@ export class CurrentUserUtils { ); slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); doc["template-button-slides"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, removeDropProperties: new List(["dropAction"]), title: "presentation slide", icon: "address-card" }); @@ -162,7 +162,7 @@ export class CurrentUserUtils { long.title = "Long Description"; doc["template-button-detail"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: new PrefetchProxy(detailView) as any as Doc, removeDropProperties: new List(["dropAction"]), title: "detail view", icon: "window-maximize" }); @@ -176,7 +176,13 @@ export class CurrentUserUtils { dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), })); } else { - DocListCast(Cast(doc["template-buttons"], Doc, null)?.data); // prefetch templates + const curButnTypes = Cast(doc["template-buttons"], Doc, null); + const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, + doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc]; + DocListCastAsync(curButnTypes.data).then(async curBtns => { + await Promise.all(curBtns!); + requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype)); + }); } return doc["template-buttons"] as Doc; } -- cgit v1.2.3-70-g09d2 From 4914965affb984e04e847460ce1b5750807b316f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 9 May 2020 15:56:11 -0400 Subject: fixed missing gear in golden layout. fixed double dragging events. changed dragging to give prefernce to modifier keys over dropactions. changed names of Documents to Catalog. added document copy past with ctrl c/ctrl v --- src/client/util/DragManager.ts | 7 ++----- src/client/views/GlobalKeyHandler.ts | 10 ++++++++++ src/client/views/MainView.tsx | 3 +-- src/client/views/PreviewCursor.tsx | 13 +++++++++++++ src/client/views/collections/CollectionDockingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 10 ++++++---- src/client/views/collections/CollectionTreeView.tsx | 3 +-- src/client/views/collections/CollectionView.tsx | 10 +++++----- src/client/views/nodes/DocumentView.tsx | 15 ++++++++++----- src/server/authentication/models/current_user_utils.ts | 14 +++++++------- 10 files changed, 56 insertions(+), 31 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index cc8cc38dd..4f547e2f7 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -176,7 +176,7 @@ export namespace DragManager { element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, doc?: Doc, - preDropFunc?: (e: Event, de: DropEvent) => void, + preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void, ): DragDropDisposer { if ("canDrop" in element.dataset) { throw new Error( @@ -187,10 +187,7 @@ export namespace DragManager { const handler = (e: Event) => dropFunc(e, (e as CustomEvent).detail); const preDropHandler = (e: Event) => { const de = (e as CustomEvent).detail; - if (de.complete.docDragData && doc?.targetDropAction) { - de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType; - } - preDropFunc?.(e, de); + preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType); }; element.addEventListener("dashOnDrop", handler); doc && element.addEventListener("dashPreDrop", preDropHandler); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index c73e1a66a..d9281ac24 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -15,6 +15,7 @@ import { DocumentView } from "./nodes/DocumentView"; import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView"; import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView"; +import { Id } from "../../new_fields/FieldSymbols"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -240,8 +241,17 @@ export default class KeyManager { break; case "a": case "v": + stopPropagation = false; + preventDefault = false; + break; case "x": case "c": + SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText("__DashDocId:" + SelectionManager.SelectedDocuments()[0].Document[Id]); + // window.getSelection()?.removeAllRanges(); + // let range = document.createRange(); + // range.selectNode(SelectionManager.SelectedDocuments()[0].ContentDiv!); + // window.getSelection()?.addRange(range); + // document.execCommand('copy'); stopPropagation = false; preventDefault = false; break; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index fad6bf295..b6dfe60e5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -249,8 +249,7 @@ export class MainView extends React.Component { title: "Collection " + workspaceCount, }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myDocuments as Doc), "data", freeformDoc); - const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myDocuments as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row"); + const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row"); const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`); mainDoc.contextMenuScripts = new List([toggleTheme!]); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 80c7f3d25..b9036bf1e 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -6,6 +6,7 @@ import "./PreviewCursor.scss"; import { Docs } from '../documents/Documents'; import { Doc } from '../../new_fields/Doc'; import { Transform } from "../util/Transform"; +import { DocServer } from '../DocServer'; @observer export class PreviewCursor extends React.Component<{}> { @@ -49,6 +50,18 @@ export class PreviewCursor extends React.Component<{}> { })); } + if (e.clipboardData.getData("text/plain").includes("__DashDocId:")) { + const docid = e.clipboardData.getData("text/plain").split("__DashDocId:")[1]; + return DocServer.GetRefField(docid).then(doc => { + if (doc instanceof Doc) { + const alias = Doc.MakeAlias(doc); + alias.x = newPoint[0]; + alias.y = newPoint[1]; + PreviewCursor._addDocument(alias); + } + }); + } + // creates text document return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", { _width: 500, diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index c1edc0fe7..6fdb96f0d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -527,7 +527,7 @@ export class CollectionDockingView extends React.Component(schemaCtor: (doc: Doc) => T, moreProps?: protected onGesture(e: Event, ge: GestureUtils.GestureEvent) { } - protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) { + protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) { if (de.complete.docDragData) { - if (de.complete.docDragData.draggedDocuments.some(d => this.childDocs.includes(d))) { - de.complete.docDragData.dropAction = "move"; + // if targetDropAction is, say 'alias', but we're just dragging within a collection, we want to ignore the targetAction. + // otherwise, the targetAction should become the actual action (which can still be overridden by the userDropAction -eg, shift/ctrl keys) + if (targetAction && !de.complete.docDragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) { + de.complete.docDragData.dropAction = targetAction; } e.stopPropagation(); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 58d021b5c..7086ba380 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -410,7 +410,6 @@ class TreeView extends React.Component { @computed get renderTitle() { TraceMobx(); - const onItemDown = SetupDrag(this._tref, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId[Id], true); (!TreeView._editTitleScript) && (TreeView._editTitleScript = ScriptField.MakeFunction("setInPlace(self, 'editTitle', true)")); const headerElements = ( <> @@ -432,7 +431,7 @@ class TreeView extends React.Component {
    ); return <> -
    boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + filterAddDocument: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) childLayoutTemplate?: () => Opt; // specify a layout Doc template to use for children of the collection childLayoutString?: string; // specify a layout string to use for children of the collection } export interface CollectionRenderProps { - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; + addDocument: (document: Doc | Doc[]) => boolean; + removeDocument: (document: Doc | Doc[]) => boolean; + moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; active: () => boolean; whenActiveChanged: (isActive: boolean) => void; PanelWidth: () => number; @@ -152,7 +152,7 @@ export class CollectionView extends Touchable boolean): boolean => { + moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e3b69b8f3..ebfc38a54 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../new_fields/Doc"; import { Document } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; import { InkTool } from '../../../new_fields/InkField'; @@ -15,7 +15,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { ImageField } from '../../../new_fields/URLField'; import { TraceMobx } from '../../../new_fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils"; +import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { ClientRecommender } from '../../ClientRecommender'; import { DocServer } from "../../DocServer"; @@ -549,7 +549,7 @@ export class DocumentView extends DocComponent(Docu if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.startDragging(this._downX, this._downY, this.props.dropAction ? this.props.dropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined); + this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType); } } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers @@ -721,6 +721,7 @@ export class DocumentView extends DocComponent(Docu let open = cm.findByDescription("Add a Perspective..."); const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; + openItems.push({ description: "New Echo ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "layer-group" }); openItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); templateDoc && openItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); if (!open) { @@ -864,8 +865,12 @@ export class DocumentView extends DocComponent(Docu const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), ""); cm.addItem({ description: `path: ${path}`, event: () => { - this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true); - Doc.linkFollowHighlight(this.props.Document); + if (this.props.LibraryPath !== emptyPath) { + this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true); + Doc.linkFollowHighlight(this.props.Document); + } else { + Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]); + } }, icon: "check" }); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index a776b6f10..bebf77a8c 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -507,13 +507,13 @@ export class CurrentUserUtils { return doc.myWorkspaces as Doc; } - static setupDocumentCollection(doc: Doc) { - if (doc.myDocuments === undefined) { - doc.myDocuments = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true, + static setupCatalog(doc: Doc) { + if (doc.myCatalog === undefined) { + doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true, })); } - return doc.myDocuments as Doc; + return doc.myCatalog as Doc; } static setupRecentlyClosed(doc: Doc) { // setup Recently Closed library item @@ -533,7 +533,7 @@ export class CurrentUserUtils { // setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) { const workspaces = CurrentUserUtils.setupWorkspaces(doc); - const documents = CurrentUserUtils.setupDocumentCollection(doc); + const documents = CurrentUserUtils.setupCatalog(doc); const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc); if (doc["tabs-button-library"] === undefined) { @@ -541,7 +541,7 @@ export class CurrentUserUtils { _width: 50, _height: 25, title: "Library", _fontSize: 10, letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], { - title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true + title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true })) as any as Doc, targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") -- cgit v1.2.3-70-g09d2 From 638fb6b16c4d69090e3806cca0a1a1909ec612c9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 9 May 2020 21:43:04 -0400 Subject: added document clone and paste for all collections --- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/GlobalKeyHandler.ts | 65 +++++++++++++--- src/client/views/MainView.tsx | 2 + src/client/views/PreviewCursor.tsx | 91 ++++++++++++---------- .../views/collections/CollectionTreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 12 ++- .../collections/collectionFreeForm/MarqueeView.tsx | 5 +- 7 files changed, 127 insertions(+), 58 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9b6105811..1054822c7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -174,8 +174,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } @undoBatch @action - onCloseClick = async (e: PointerEvent) => { - if (e.button === 0) { + onCloseClick = async (e: PointerEvent | undefined) => { + if (!e?.button) { const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = SelectionManager.SelectedDocuments().slice(); SelectionManager.DeselectAll(); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d9281ac24..3fd14735b 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -1,10 +1,10 @@ -import { UndoManager } from "../util/UndoManager"; +import { UndoManager, undoBatch } from "../util/UndoManager"; import { SelectionManager } from "../util/SelectionManager"; import { CollectionDockingView } from "./collections/CollectionDockingView"; import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; import { action, runInAction } from "mobx"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, DocListCast } from "../../new_fields/Doc"; import { DictationManager } from "../util/DictationManager"; import SharingManager from "../util/SharingManager"; import { Cast, PromiseValue, NumCast } from "../../new_fields/Types"; @@ -16,6 +16,11 @@ import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView"; import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView"; import { Id } from "../../new_fields/FieldSymbols"; +import { DocumentDecorations } from "./DocumentDecorations"; +import { DocumentType } from "../documents/DocumentTypes"; +import { DocServer } from "../DocServer"; +import { List } from "../../new_fields/List"; +import { DateField } from "../../new_fields/DateField"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -245,15 +250,25 @@ export default class KeyManager { preventDefault = false; break; case "x": + if (SelectionManager.SelectedDocuments().length) { + const bds = DocumentDecorations.Instance.Bounds; + const pt = [bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2]; + const text = `__DashDocId(${pt[0]},${pt[1]}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":"); + SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text); + DocumentDecorations.Instance.onCloseClick(undefined); + stopPropagation = false; + preventDefault = false; + } + break; case "c": - SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText("__DashDocId:" + SelectionManager.SelectedDocuments()[0].Document[Id]); - // window.getSelection()?.removeAllRanges(); - // let range = document.createRange(); - // range.selectNode(SelectionManager.SelectedDocuments()[0].ContentDiv!); - // window.getSelection()?.addRange(range); - // document.execCommand('copy'); - stopPropagation = false; - preventDefault = false; + if (SelectionManager.SelectedDocuments().length) { + const bds = DocumentDecorations.Instance.Bounds; + const pt = [bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2]; + const text = `__DashDocId(${pt[0]},${pt[1]}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":"); + SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);; + stopPropagation = false; + preventDefault = false; + } break; } @@ -263,6 +278,36 @@ export default class KeyManager { }; }); + public paste(e: ClipboardEvent) { + if (e.clipboardData?.getData("text/plain") !== "" && e.clipboardData?.getData("text/plain").startsWith("__DashDocId(")) { + const first = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; + if (first?.props.Document.type === DocumentType.COL) { + const docids = e.clipboardData.getData("text/plain").split(":"); + let count = 1; + const list: Doc[] = []; + const targetDataDoc = Doc.GetProto(first.props.Document); + const fieldKey = Doc.LayoutFieldKey(first.props.Document); + const docList = DocListCast(targetDataDoc[fieldKey]); + docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => { + count++; + if (doc instanceof Doc) { + list.push(doc); + } + if (count === docids.length) { + const added = list.filter(d => !docList.includes(d)); + if (added.length) { + added.map(doc => doc.context = targetDataDoc); + undoBatch(() => { + targetDataDoc[fieldKey] = new List([...docList, ...added]); + targetDataDoc[fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + })(); + } + } + })); + } + } + } + async printClipboard() { const text: string = await navigator.clipboard.readText(); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b6dfe60e5..cbaae63bc 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -83,12 +83,14 @@ export class MainView extends React.Component { firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); + window.addEventListener("paste", KeyManager.Instance.paste); } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.globalPointerDown); window.removeEventListener("pointerup", this.globalPointerUp); + window.removeEventListener("paste", KeyManager.Instance.paste); } constructor(props: Readonly<{}>) { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index b9036bf1e..14b5b3fce 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -7,13 +7,15 @@ import { Docs } from '../documents/Documents'; import { Doc } from '../../new_fields/Doc'; import { Transform } from "../util/Transform"; import { DocServer } from '../DocServer'; +import { NumCast } from '../../new_fields/Types'; +import { undoBatch } from '../util/UndoManager'; @observer export class PreviewCursor extends React.Component<{}> { static _onKeyPress?: (e: KeyboardEvent) => void; static _getTransform: () => Transform; + static _addDocument: (doc: Doc | Doc[]) => void; static _addLiveTextDoc: (doc: Doc) => void; - static _addDocument: (doc: Doc) => boolean; static _nudge: (x: number, y: number) => boolean; @observable static _clickPoint = [0, 0]; @observable public static Visible = false; @@ -28,62 +30,73 @@ export class PreviewCursor extends React.Component<{}> { const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); runInAction(() => PreviewCursor.Visible = false); + // tests for URL and makes web document + const re: any = /^https?:\/\//g; if (e.clipboardData.getData("text/plain") !== "") { // tests for youtube and makes video document if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) { const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/"); - return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { + undoBatch(() => PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] - })); + })))(); } - // tests for URL and makes web document - const re: any = /^https?:\/\//g; - if (re.test(e.clipboardData.getData("text/plain"))) { + else if (re.test(e.clipboardData.getData("text/plain"))) { const url = e.clipboardData.getData("text/plain"); - return PreviewCursor._addDocument(Docs.Create.WebDocument(url, { + undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, { title: url, _width: 500, _height: 300, // nativeWidth: 300, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] - })); + })))(); } - if (e.clipboardData.getData("text/plain").includes("__DashDocId:")) { - const docid = e.clipboardData.getData("text/plain").split("__DashDocId:")[1]; - return DocServer.GetRefField(docid).then(doc => { + else if (e.clipboardData.getData("text/plain").startsWith("__DashDocId(")) { + const docids = e.clipboardData.getData("text/plain").split(":"); + const strs = docids[0].split(","); + const ptx = Number(strs[0].substring("__DashDocId(".length)); + const pty = Number(strs[1].substring(0, strs[1].length - 1)); + const center = PreviewCursor._getTransform().transformPoint(ptx, pty); + let count = 1; + const list: Doc[] = []; + docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => { + count++; if (doc instanceof Doc) { - const alias = Doc.MakeAlias(doc); - alias.x = newPoint[0]; - alias.y = newPoint[1]; - PreviewCursor._addDocument(alias); + const alias = Doc.MakeClone(doc); + alias.x = newPoint[0] + NumCast(doc.x) - center[0]; + alias.y = newPoint[1] + NumCast(doc.y) - center[1]; + list.push(alias); } - }); - } + if (count === docids.length) { + undoBatch(() => PreviewCursor._addDocument(list))(); + } + })); - // creates text document - return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", { - _width: 500, - limitHeight: 400, - _autoHeight: true, - x: newPoint[0], - y: newPoint[1], - title: "-pasted text-" - })); - } - //pasting in images - if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes(" PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument("", { + _width: 500, + limitHeight: 400, + _autoHeight: true, + x: newPoint[0], + y: newPoint[1], + title: "-pasted text-" + })))(); + } + } else + //pasting in images + if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes(" PreviewCursor._addDocument(Docs.Create.ImageDocument( + arr[1], { + _width: 300, title: arr[1], + x: newPoint[0], + y: newPoint[1], + })))(); + } } } @@ -125,7 +138,7 @@ export class PreviewCursor extends React.Component<{}> { onKeyPress: (e: KeyboardEvent) => void, addLiveText: (doc: Doc) => void, getTransform: () => Transform, - addDocument: (doc: Doc) => boolean, + addDocument: (doc: Doc | Doc[]) => boolean, nudge: (nudgeX: number, nudgeY: number) => boolean) { this._clickPoint = [x, y]; this._onKeyPress = onKeyPress; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f6fcc1ac4..8cd34e7ed 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -396,7 +396,7 @@ class TreeView extends React.Component { } showContextMenu = (e: React.MouseEvent) => { - simulateMouseClick(this._docRef.current!.ContentDiv!, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); e.stopPropagation(); } focusOnDoc = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(doc)?.props.focus(doc, true); @@ -772,6 +772,9 @@ export class CollectionTreeView extends CollectionSubView; } + onKeyPress = (e: React.KeyboardEvent) => { + console.log(e); + } render() { if (!(this.props.Document instanceof Doc)) return (null); const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType; @@ -786,6 +789,7 @@ export class CollectionTreeView extends CollectionSubView this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6cc0cfcd2..1bc59c727 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1246,8 +1246,16 @@ export class CollectionFreeFormView extends CollectionSubView + return {this.children} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1addea6a1..35d925bca 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -24,13 +24,10 @@ import React = require("react"); interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; - addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => void; - removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; - isAnnotationOverlay?: boolean; nudge: (x: number, y: number) => boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @@ -248,7 +245,7 @@ export class MarqueeView extends React.Component Date: Mon, 11 May 2020 14:59:54 -0400 Subject: changed Cors behavior for WebBox(On for everything except GoogldDocs). Update Freeze to work for ctrl-resizing text, and showing full screen. reorged context menus. --- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/DocumentDecorations.scss | 1 + src/client/views/DocumentDecorations.tsx | 1 + src/client/views/MainView.tsx | 1 + src/client/views/PreviewCursor.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 12 ++++---- .../views/collections/CollectionStackingView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 27 +++++++++--------- src/client/views/nodes/DocumentView.tsx | 25 ++++++---------- src/client/views/nodes/ImageBox.tsx | 33 +++++++++++++--------- src/client/views/nodes/WebBox.tsx | 8 ++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +++++++++++- .../formattedText/FormattedTextBoxComment.tsx | 2 +- src/new_fields/Doc.ts | 26 +++++++++++------ .../authentication/models/current_user_utils.ts | 2 +- 15 files changed, 94 insertions(+), 67 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 48685302a..10d9ec401 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -194,7 +194,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV e.preventDefault(); let googleDoc = await Cast(dataDoc.googleDoc, Doc); if (!googleDoc) { - const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false }; + const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false }; googleDoc = Docs.Create.WebDocument(googleDocUrl, options); dataDoc.googleDoc = googleDoc; } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 4f34eb9e3..15eb537da 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -72,6 +72,7 @@ $linkGap : 3px; grid-column-start: 5; grid-column-end: 7; border-radius: 100%; + background: dimgray; .borderRadiusTooltip { width: 10px; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 1054822c7..9669c21a9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -301,6 +301,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { + if (e.ctrlKey && !element.props.Document._nativeHeight) element.toggleNativeDimensions(); if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { const doc = Document(element.rootDoc); let nwidth = doc._nativeWidth || 0; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index cbaae63bc..560dd5d11 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -249,6 +249,7 @@ export class MainView extends React.Component { _width: this._panelWidth * .7, _height: this._panelHeight, title: "Collection " + workspaceCount, + _LODdisable: true }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row"); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 14b5b3fce..2ddca369a 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -46,7 +46,7 @@ export class PreviewCursor extends React.Component<{}> { else if (re.test(e.clipboardData.getData("text/plain"))) { const url = e.clipboardData.getData("text/plain"); undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, { - title: url, _width: 500, _height: 300, + title: url, _width: 500, _height: 300, UseCors: true, // nativeWidth: 300, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] })))(); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 296b2453b..50e3b73eb 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -755,8 +755,10 @@ export class DockedFrameRenderer extends React.Component { } get layoutDoc() { return this._document && Doc.Layout(this._document); } - panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : this._panelWidth; - panelHeight = () => this._panelHeight; + nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0; + panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : + (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth); + panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight; nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0; nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0; @@ -794,7 +796,7 @@ export class DockedFrameRenderer extends React.Component { return Transform.Identity(); } get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; } - get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this.panelWidth() * 100}%` : undefined; } + get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}%` : undefined; } addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => { SelectionManager.DeselectAll(); @@ -832,8 +834,8 @@ export class DockedFrameRenderer extends React.Component { ContentScaling={this.contentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} - NativeHeight={returnZero} - NativeWidth={returnZero} + NativeHeight={this.nativeHeight} + NativeWidth={this.nativeWidth} ScreenToLocalTransform={this.ScreenToLocalTransform} renderDepth={0} parentActive={returnTrue} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index c199988c0..979e9e3f8 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -432,6 +432,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) if (!e.isPropagationStopped()) { const subItems: ContextMenuProps[] = []; subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" }); + subItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" }); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d24ba6bb0..129b245b8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -113,8 +113,8 @@ export class CollectionFreeFormView extends CollectionSubView !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections - private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections + private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections + private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1); private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); @@ -340,12 +340,12 @@ export class CollectionFreeFormView extends CollectionSubView { + Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight()); + } private thumbIdentifier?: number; @@ -1138,6 +1138,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); + optionItems.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index aca0433bb..6c9ade83f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -648,18 +648,8 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action - public static unfreezeNativeDimensions(layoutDoc: Doc) { - layoutDoc._nativeWidth = undefined; - layoutDoc._nativeHeight = undefined; - } - toggleNativeDimensions = () => { - if (this.Document._nativeWidth || this.Document._nativeHeight) { - DocumentView.unfreezeNativeDimensions(this.layoutDoc); - } - else { - Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight()); - } + Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight()); } @undoBatch @@ -735,10 +725,6 @@ export class DocumentView extends DocComponent(Docu let options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - optionItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - optionItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - optionItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); if (!options) { options = { description: "Options...", subitems: optionItems, icon: "compass" }; cm.addItem(options); @@ -757,6 +743,7 @@ export class DocumentView extends DocComponent(Docu onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" }); !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + const funcs: ContextMenuProps[] = []; if (this.Document.onDragStart) { funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); @@ -768,7 +755,9 @@ export class DocumentView extends DocComponent(Docu const more = cm.findByDescription("More..."); const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : []; moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); - moreItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" }); + moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); + moreItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); + moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); if (!ClientUtils.RELEASE) { // let copies: ContextMenuProps[] = []; @@ -794,6 +783,7 @@ export class DocumentView extends DocComponent(Docu // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); }); + const recommender_subitems: ContextMenuProps[] = []; recommender_subitems.push({ @@ -826,6 +816,9 @@ export class DocumentView extends DocComponent(Docu moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" }); moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" }); !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); + + cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!); + runInAction(() => { if (!ClientUtils.RELEASE) { const setWriteMode = (mode: DocServer.WriteMode) => { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4153c184e..d85373e98 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -157,21 +157,20 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); - funcs.push({ - description: "Reset Native Dimensions", event: action(async () => { - const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); - const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); - if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { - this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; - this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); - } else { - this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); - this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; - } - }), icon: "expand-arrows-alt" - }); + // funcs.push({ + // description: "Reset Native Dimensions", event: action(async () => { + // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); + // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); + // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { + // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; + // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); + // } else { + // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); + // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; + // } + // }), icon: "expand-arrows-alt" + // }); const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; @@ -181,6 +180,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); + !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" }); } } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 8239d6fdf..384a6e8a5 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -129,14 +129,12 @@ export class WebBox extends ViewBoxAnnotatableComponent { + toggleAnnotationMode = () => { if (!this.layoutDoc.isAnnotating) { - //DocumentView.unfreezeNativeDimensions(this.layoutDoc); this.layoutDoc.lockedTransform = false; this.layoutDoc.isAnnotating = true; } else { - //Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight()); this.layoutDoc.lockedTransform = true; this.layoutDoc.isAnnotating = false; } @@ -158,10 +156,10 @@ export class WebBox extends ViewBoxAnnotatableComponent
    -
    +
    -
    +
    { + Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight()); + } public static get DefaultLayout(): Doc | string | undefined { return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); @@ -438,6 +443,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); + //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); + funcs.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" }); funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); @@ -649,10 +656,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - this._disposers.height = reaction( + this._disposers.autoHeight = reaction( () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], () => this.tryUpdateHeight() ); + this._disposers.height = reaction( + () => this.layoutDoc[HeightSym](), + height => height <= 20 && (this.layoutDoc._autoHeight = true) + ); this.setupEditor(this.config, this.props.fieldKey); @@ -1206,6 +1217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp render() { TraceMobx(); const style: { [key: string]: any } = {}; + const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1); const divKeys = ["width", "height", "background"]; const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.rootDoc, this: this.layoutDoc }).result as string || ""; @@ -1258,6 +1270,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
    diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 9ad5aafb8..a33717855 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -91,7 +91,7 @@ export class FormattedTextBoxComment { (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); } } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight"); + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight"); } keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e0627fb37..a1e1e11b1 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -20,7 +20,6 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u import { Docs, DocumentOptions } from "../client/documents/Documents"; import { PdfField, VideoField, AudioField, ImageField } from "./URLField"; import { LinkManager } from "../client/util/LinkManager"; -import { string32 } from "../../deploy/assets/pdf.worker"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -936,19 +935,28 @@ export namespace Doc { } } } - - export function freezeNativeDimensions(layoutDoc: Doc, width: number, height: number): void { - layoutDoc._autoHeight = false; - if (!layoutDoc._nativeWidth) { - layoutDoc._nativeWidth = NumCast(layoutDoc._width, width); - layoutDoc._nativeHeight = NumCast(layoutDoc._height, height); - } - } export function assignDocToField(doc: Doc, field: string, id: string) { DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout)); return id; } + export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) { + runInAction(() => { + if (layoutDoc._nativeWidth || layoutDoc._nativeHeight) { + layoutDoc.scale = NumCast(layoutDoc.scale, 1) * contentScale; + layoutDoc._nativeWidth = undefined; + layoutDoc._nativeHeight = undefined; + } + else { + layoutDoc._autoHeight = false; + if (!layoutDoc._nativeWidth) { + layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); + layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight); + } + } + }); + } + export function isDocPinned(doc: Doc) { //add this new doc to props.Document const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index bebf77a8c..b6ff10bab 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -315,7 +315,7 @@ export class CurrentUserUtils { { _width: 250, _height: 250, title: "container" }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600 }); + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true }); } return [ { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, -- cgit v1.2.3-70-g09d2 From 7e1f89f48d1c4e49dea78dff1c1983e75a11a6a6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 May 2020 17:13:53 -0400 Subject: fixed issues with text auto height and scaled documents. allowed #tagging in text notes to support muliple values with ';'. DashFieldviews can have multiple values, too. logout is moved to settings. --- src/client/util/SettingsManager.tsx | 5 + src/client/views/DocumentDecorations.tsx | 8 +- src/client/views/MainView.scss | 4 +- src/client/views/MainView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 2 +- .../views/nodes/formattedText/DashFieldView.tsx | 7 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 176 +++++++++++---------- .../views/nodes/formattedText/RichTextRules.ts | 6 +- 8 files changed, 119 insertions(+), 94 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index ff0b22381..e20434461 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -8,6 +8,8 @@ import { SelectionManager } from "./SelectionManager"; import "./SettingsManager.scss"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Networking } from "../Network"; +import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { Utils } from "../../Utils"; library.add(fa.faWindowClose); @@ -90,6 +92,9 @@ export default class SettingsManager extends React.Component<{}> {
    +
    {this.settingsContent === "password" ?
    diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9669c21a9..a3c476125 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -237,6 +237,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return false; } + _initialAutoHeight = false; @action onPointerDown = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { }); @@ -251,6 +252,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } this._snapX = e.pageX; this._snapY = e.pageY; + this._initialAutoHeight = true; } onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { @@ -353,7 +355,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { dW && (doc._width = actualdW); dH && (doc._height = actualdH); - dH && doc._autoHeight && (doc._autoHeight = false); + dH && this._initialAutoHeight && (doc._autoHeight = this._initialAutoHeight = false); } } })); @@ -362,6 +364,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @action onPointerUp = (e: PointerEvent): void => { + SelectionManager.SelectedDocuments().map(dv => { + dv.layoutDoc._delayAutoHeight && (dv.layoutDoc._autoHeight = true); + dv.layoutDoc._delayAutoHeight = undefined; + }); this._resizeHdlId = ""; this.Interacting = false; (e.button === 0) && this._resizeUndo?.end(); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 753ba700c..dca2a1e3e 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -107,7 +107,9 @@ position: absolute; left: 0; bottom: 0; - font-size: 8px; + border-radius: 25%; + margin-left: -5px; + background: darkblue; } .mainView-settings:hover { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 560dd5d11..62496b01f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -493,10 +493,7 @@ export class MainView extends React.Component { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> -
    {this.docButtons} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6c9ade83f..5cccec776 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1084,7 +1084,7 @@ export class DocumentView extends DocComponent(Docu `} - ContentScaling={this.childScaling} + ContentScaling={returnOne} ChromeHeight={this.chromeHeight} isSelected={this.isSelected} select={this.select} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index d5a28fd14..3c6841f08 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -79,11 +79,13 @@ export class DashFieldViewInternal extends React.Component 1 ? new List(splits) : newText; } } }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2c51397eb..3a586ff66 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -658,11 +658,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ); this._disposers.autoHeight = reaction( () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], - () => this.tryUpdateHeight() + () => setTimeout(() => this.tryUpdateHeight(), 0) ); this._disposers.height = reaction( () => this.layoutDoc[HeightSym](), - height => height <= 20 && (this.layoutDoc._autoHeight = true) + action(height => { + if (height <= 20) { + if (this.layoutDoc._nativeWidth || this.layoutDoc._nativeHeight) { + Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight()); + } + this.layoutDoc._delayAutoHeight = true; + } + }) ); this.setupEditor(this.config, this.props.fieldKey); @@ -1184,8 +1191,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action tryUpdateHeight(limitHeight?: number) { let scrollHeight = this._ref.current?.scrollHeight; - if (this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight && - getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + if (this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + scrollHeight = scrollHeight * NumCast(this.layoutDoc.scale, 1); if (limitHeight && scrollHeight > limitHeight) { scrollHeight = limitHeight; this.layoutDoc.limitHeight = undefined; @@ -1234,87 +1241,90 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBoxComment.Hide(); } return ( - -
    this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)} - onBlur={this.onBlur} - onPointerUp={this.onPointerUp} - onPointerDown={this.onPointerDown} - onMouseUp={this.onMouseUp} - onWheel={this.onPointerWheel} - onPointerEnter={action(() => this._entered = true)} - onPointerLeave={action((e: React.PointerEvent) => { - this._entered = false; - const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); - for (let child: any = target; child; child = child?.parentElement) { - if (child === this._ref.current!) { - this._entered = true; +
    +
    this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)} + onBlur={this.onBlur} + onPointerUp={this.onPointerUp} + onPointerDown={this.onPointerDown} + onMouseUp={this.onMouseUp} + onWheel={this.onPointerWheel} + onPointerEnter={action(() => this._entered = true)} + onPointerLeave={action((e: React.PointerEvent) => { + this._entered = false; + const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); + for (let child: any = target; child; child = child?.parentElement) { + if (child === this._ref.current!) { + this._entered = true; + } } - } - })} - > -
    -
    + })} + > +
    +
    +
    + {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? +
    : +
    + + +
    +
    } + {!this.layoutDoc._showAudio ? (null) : +
    { + runInAction(() => this._recording = !this._recording); + setTimeout(() => this._editorView!.focus(), 500); + e.stopPropagation(); + }} > + +
    }
    - {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? -
    : -
    - - -
    -
    } - {!this.layoutDoc._showAudio ? (null) : -
    { - runInAction(() => this._recording = !this._recording); - setTimeout(() => this._editorView!.focus(), 500); - e.stopPropagation(); - }} > - -
    }
    ); } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index d619bc4a0..0ba591fec 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -11,6 +11,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import { wrappingInputRule } from "./prosemirrorPatches"; import RichTextMenu from "./RichTextMenu"; import { schema } from "./schema_rts"; +import { List } from "../../../../new_fields/List"; export class RichTextRules { public Document: Doc; @@ -64,11 +65,12 @@ export class RichTextRules { // create an inline view of a tag stored under the '#' field new InputRule( - new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), + new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/), (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - this.Document[DataSym]["#"] = tag; + const multiple = tag.split(";"); + this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag; const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); return state.tr.deleteRange(start, end).insert(start, fieldView); }), -- cgit v1.2.3-70-g09d2 From f1473fc5bf7f2b3109be358ae14d28725240284c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 May 2020 22:30:33 -0400 Subject: cleaned up menu items. improved snapping with fixed aspect items, but not perfect. --- src/client/documents/Documents.ts | 1 + src/client/util/DragManager.ts | 14 ++-- src/client/views/DocumentDecorations.tsx | 41 ++++++++++-- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionDockingView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 21 +++--- src/client/views/nodes/DocumentView.tsx | 75 ++++++++-------------- src/client/views/nodes/ImageBox.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 37 ++++------- 10 files changed, 101 insertions(+), 99 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a87a77e1d..95b8daafc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -150,6 +150,7 @@ export interface DocumentOptions { dragFactory?: Doc; // document to create when dragging with a suitable onDragStart 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; icon?: string; sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4f547e2f7..1ee4c57a2 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -256,7 +256,8 @@ export namespace DragManager { SnappingManager.setSnapLines(horizLines, vertLines); } - export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { + // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line + export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, oneAxis: boolean = false) { const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); const snapVal = (pts: number[], drag: number, snapLines: number[]) => { if (snapLines.length) { @@ -265,14 +266,15 @@ export namespace DragManager { const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest)); const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i])); const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2; - return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag; + return closestDists[minIndex] < snapThreshold ? [closestDists[minIndex], closestPts[minIndex] + offs[minIndex]] : [Number.MAX_VALUE, drag]; } - return drag; + return [Number.MAX_VALUE, drag]; }; - + const xsnap = snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()); + const ysnap = snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()); return { - thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()), - thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()) + thisX: !oneAxis || xsnap[0] < ysnap[0] ? xsnap[1] : e.pageX, + thisY: !oneAxis || xsnap[0] > ysnap[0] ? ysnap[1] : e.pageY }; } export let docsBeingDragged: Doc[] = []; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a3c476125..b939d96b6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -20,7 +20,6 @@ import React = require("react"); import { Id } from '../../new_fields/FieldSymbols'; import e = require('express'); import { CollectionDockingView } from './collections/CollectionDockingView'; -import { MainView } from './MainView'; import { SnappingManager } from '../util/SnappingManager'; library.add(faCaretUp); @@ -238,6 +237,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } _initialAutoHeight = false; + _dragHeights = new Map(); @action onPointerDown = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { }); @@ -253,17 +253,38 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> this._snapX = e.pageX; this._snapY = e.pageY; this._initialAutoHeight = true; + DragManager.docsBeingDragged = SelectionManager.SelectedDocuments().map(dv => dv.rootDoc); + SelectionManager.SelectedDocuments().map(dv => { + this._dragHeights.set(dv.layoutDoc, NumCast(dv.layoutDoc._height)); + dv.layoutDoc._delayAutoHeight = dv.layoutDoc._height; + }); } onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { - const { thisX, thisY } = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY); + const first = SelectionManager.SelectedDocuments()[0]; + const fixedAspect = NumCast(first.layoutDoc._nativeWidth) !== 0; + let { thisX, thisY } = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY, fixedAspect); + if (fixedAspect) { // if aspect is set, then any snapped movement must be coerced to match the aspect ratio + const aspect = NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight); + const deltaX = thisX - this._snapX; + const deltaY = thisY - this._snapY; + if (thisX !== e.pageX) { + const snapY = deltaX / aspect + this._snapY; + thisY = Math.abs(deltaX / aspect) < 10 ? snapY : thisY; + } else { + const snapX = deltaY * aspect + this._snapX; + thisX = Math.abs(deltaY * aspect) < 10 ? snapX : thisX; + } + } move[0] = thisX - this._snapX; move[1] = thisY - this._snapY; this._snapX = thisX; this._snapY = thisY; let dX = 0, dY = 0, dW = 0, dH = 0; - + const unfreeze = () => + SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => + (element.rootDoc.type === DocumentType.RTF && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions())); switch (this._resizeHdlId) { case "": break; case "documentDecorations-topLeftResizer": @@ -278,6 +299,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> dH = -move[1]; break; case "documentDecorations-topResizer": + unfreeze(); dY = -1; dH = -move[1]; break; @@ -291,13 +313,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> dH = move[1]; break; case "documentDecorations-bottomResizer": + unfreeze(); dH = move[1]; break; case "documentDecorations-leftResizer": + unfreeze(); dX = -1; dW = -move[0]; break; case "documentDecorations-rightResizer": + unfreeze(); dW = move[0]; break; } @@ -309,9 +334,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let nwidth = doc._nativeWidth || 0; let nheight = doc._nativeHeight || 0; const width = (doc._width || 0); - const height = (doc._height || (nheight / nwidth * width)); + let height = (doc._height || (nheight / nwidth * width)); const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); if (nwidth && nheight) { + if (nwidth / nheight !== width / height) { + height = nheight / nwidth * width; + } if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } @@ -365,7 +393,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @action onPointerUp = (e: PointerEvent): void => { SelectionManager.SelectedDocuments().map(dv => { - dv.layoutDoc._delayAutoHeight && (dv.layoutDoc._autoHeight = true); + if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) { + dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.props.ContentScaling(), dv.panelWidth(), dv.panelHeight()); + dv.layoutDoc._autoHeight = true; + } dv.layoutDoc._delayAutoHeight = undefined; }); this._resizeHdlId = ""; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 62496b01f..9bfef06b4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -83,14 +83,14 @@ export class MainView extends React.Component { firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); - window.addEventListener("paste", KeyManager.Instance.paste); + window.addEventListener("paste", KeyManager.Instance.paste as any); } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.globalPointerDown); window.removeEventListener("pointerup", this.globalPointerUp); - window.removeEventListener("paste", KeyManager.Instance.paste); + window.removeEventListener("paste", KeyManager.Instance.paste as any); } constructor(props: Readonly<{}>) { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 50e3b73eb..2e24cbb12 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -757,7 +757,7 @@ export class DockedFrameRenderer extends React.Component { get layoutDoc() { return this._document && Doc.Layout(this._document); } nativeAspect = () => this.nativeWidth() ? this.nativeWidth() / this.nativeHeight() : 0; panelWidth = () => this.layoutDoc?.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._nativeWidth)), this._panelWidth) : - (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth); + (this.nativeAspect() && this.nativeAspect() < this._panelWidth / this._panelHeight ? this._panelHeight * this.nativeAspect() : this._panelWidth) panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight; nativeWidth = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeWidth) || this._panelWidth : 0; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index ac9f64d94..0f239d385 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -234,7 +234,6 @@ export class CollectionView extends Touchable { if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true); this.setupViewTypes("Add a Perspective...", vtype => { const newRendition = Doc.MakeAlias(this.props.Document); newRendition._viewType = vtype; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 129b245b8..6caee960d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1133,19 +1133,25 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.props.children && this.props.annotationsKey) return; + if (this.props.annotationsKey) return; + + ContextMenu.Instance.addItem({ + description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => { + this._timelineVisible = !this._timelineVisible; + }), icon: this._timelineVisible ? faEyeSlash : faEye + }); + const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - optionItems.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" }); - optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); + optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" }); optionItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); @@ -1173,14 +1179,9 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); - - ContextMenu.Instance.addItem({ - description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => { - this._timelineVisible = !this._timelineVisible; - }), icon: this._timelineVisible ? faEyeSlash : faEye - }); } @observable _timelineVisible = false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5cccec776..1bf297796 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -704,7 +704,6 @@ export class DocumentView extends DocComponent(Docu } const cm = ContextMenu.Instance; - const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []); Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) => @@ -713,25 +712,16 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" })); - let open = cm.findByDescription("Add a Perspective..."); - const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; - openItems.push({ description: "New Echo ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "layer-group" }); - openItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); - templateDoc && openItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); - if (!open) { - open = { description: "Add a Perspective....", subitems: openItems, icon: "external-link-alt" }; - cm.addItem(open); - } - let options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; + const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); + optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); + templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); if (!options) { options = { description: "Options...", subitems: optionItems, icon: "compass" }; cm.addItem(options); } - cm.moveAfter(options, open); - const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); @@ -764,9 +754,6 @@ export class DocumentView extends DocComponent(Docu moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); // cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" }); } - if (Cast(this.props.Document.data, ImageField)) { - moreItems.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); - } if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) { moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" }); moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" }); @@ -820,47 +807,38 @@ export class DocumentView extends DocComponent(Docu cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!); runInAction(() => { - if (!ClientUtils.RELEASE) { - const setWriteMode = (mode: DocServer.WriteMode) => { - DocServer.AclsMode = mode; - const mode1 = mode; - const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; - DocServer.setFieldWriteMode("x", mode1); - DocServer.setFieldWriteMode("y", mode1); - DocServer.setFieldWriteMode("_width", mode1); - DocServer.setFieldWriteMode("_height", mode1); - - DocServer.setFieldWriteMode("_panX", mode2); - DocServer.setFieldWriteMode("_panY", mode2); - DocServer.setFieldWriteMode("scale", mode2); - DocServer.setFieldWriteMode("_viewType", mode2); - }; - const aclsMenu: ContextMenuProps[] = []; - aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" }); - aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" }); - aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" }); - aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" }); - cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" }); - } + const setWriteMode = (mode: DocServer.WriteMode) => { + DocServer.AclsMode = mode; + const mode1 = mode; + const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; + DocServer.setFieldWriteMode("x", mode1); + DocServer.setFieldWriteMode("y", mode1); + DocServer.setFieldWriteMode("_width", mode1); + DocServer.setFieldWriteMode("_height", mode1); + + DocServer.setFieldWriteMode("_panX", mode2); + DocServer.setFieldWriteMode("_panY", mode2); + DocServer.setFieldWriteMode("scale", mode2); + DocServer.setFieldWriteMode("_viewType", mode2); + }; + const aclsMenu: ContextMenuProps[] = []; + aclsMenu.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" }); + aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" }); + aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" }); + aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" }); + aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" }); + cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" }); }); runInAction(() => { - cm.addItem({ - description: "Share", - event: () => SharingManager.Instance.open(this), - icon: "external-link-alt" - }); - if (!this.topMost && !(e instanceof Touch)) { // DocumentViews should stop propagation of this event e.stopPropagation(); } ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - if (!SelectionManager.IsSelected(this, true)) { - SelectionManager.SelectDoc(this, false); - } + !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false); }); const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), ""); - cm.addItem({ + const item = ({ description: `path: ${path}`, event: () => { if (this.props.LibraryPath !== emptyPath) { this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true); @@ -870,6 +848,7 @@ export class DocumentView extends DocComponent(Docu } }, icon: "check" }); + //cm.addItem(item); } recommender = async () => { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d85373e98..aaebceaa2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -29,6 +29,7 @@ import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; const requestImageSize = require('../../util/request-image-size'); const path = require('path'); const { Howl } = require('howler'); @@ -158,6 +159,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent 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" }); // funcs.push({ // description: "Reset Native Dimensions", event: action(async () => { // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); @@ -184,7 +187,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" }); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3a586ff66..23bf86a32 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -419,9 +419,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const funcs: ContextMenuProps[] = []; this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); !this.layoutDoc.isTemplateDoc && funcs.push({ - description: "Make Template", event: () => { + description: "Convert to use as a style", event: () => { this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc); Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" @@ -444,11 +443,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, icon: "eye" }); //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - funcs.push({ description: !this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" }); + funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); + + const uicontrols: ContextMenuProps[] = []; + uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); + uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); + uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); + + funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); const highlighting: ContextMenuProps[] = []; ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => @@ -481,19 +484,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" }); !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); - - const open = cm.findByDescription("Add a Perspective..."); - const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; - - openItems.push({ - description: "FreeForm", event: undoBatch(() => { - const alias = Doc.MakeAlias(this.rootDoc); - Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform"); - this.props.addDocTab(alias, "onRight"); - }), icon: "eye" - }); - !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" }); - } recordDictation = () => { @@ -663,11 +653,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._disposers.height = reaction( () => this.layoutDoc[HeightSym](), action(height => { - if (height <= 20) { - if (this.layoutDoc._nativeWidth || this.layoutDoc._nativeHeight) { - Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight()); - } - this.layoutDoc._delayAutoHeight = true; + if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { + this.layoutDoc._delayAutoHeight = height; } }) ); @@ -1202,7 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const dh = NumCast(this.rootDoc._height, 0); const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle - if (this.rootDoc !== this.layoutDoc && !this.layoutDoc.resolvedDataDoc) { + if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) { // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... console.log("Delayed height adjustment..."); setTimeout(() => { -- cgit v1.2.3-70-g09d2 From f9385130fe297088754d4ce46d6c318c8be2121d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 14 May 2020 01:29:19 -0700 Subject: server side restructure, some documentation to accompany new wiki overview entry --- src/client/util/CurrentUserUtils.ts | 738 +++++++++++++++++++++ src/client/util/SettingsManager.tsx | 2 +- src/client/views/GestureOverlay.tsx | 2 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/Main.tsx | 2 +- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionMapView.tsx | 13 +- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- .../CollectionFreeFormRemoteCursors.tsx | 2 +- src/client/views/nodes/ColorBox.tsx | 2 +- src/mobile/ImageUpload.tsx | 3 +- src/mobile/MobileInterface.tsx | 4 +- src/server/ApiManagers/ApiManager.ts | 2 +- src/server/ApiManagers/DeleteManager.ts | 2 +- src/server/ApiManagers/GeneralGoogleManager.ts | 2 +- src/server/DashSession/DashSessionAgent.ts | 2 +- src/server/RouteManager.ts | 2 +- src/server/Websocket/Websocket.ts | 305 --------- src/server/apis/google/CredentialsLoader.ts | 29 + src/server/apis/google/GoogleApiServerUtils.ts | 10 +- .../apis/google/google_project_credentials.json | 11 + src/server/apis/youtube/youtubeApiSample.js | 22 +- src/server/authentication/AuthenticationManager.ts | 268 ++++++++ src/server/authentication/DashUserModel.ts | 86 +++ src/server/authentication/Passport.ts | 29 + src/server/authentication/config/passport.ts | 29 - .../authentication/controllers/user_controller.ts | 268 -------- .../authentication/models/current_user_utils.ts | 738 --------------------- src/server/authentication/models/user_model.ts | 86 --- src/server/credentials/CredentialsLoader.ts | 29 - .../credentials/google_project_credentials.json | 11 - src/server/database.ts | 78 ++- src/server/index.ts | 7 +- src/server/server_Initialization.ts | 4 +- src/server/websocket.ts | 297 +++++++++ webpack.config.js | 27 +- 37 files changed, 1586 insertions(+), 1534 deletions(-) create mode 100644 src/client/util/CurrentUserUtils.ts delete mode 100644 src/server/Websocket/Websocket.ts create mode 100644 src/server/apis/google/CredentialsLoader.ts create mode 100644 src/server/apis/google/google_project_credentials.json create mode 100644 src/server/authentication/AuthenticationManager.ts create mode 100644 src/server/authentication/DashUserModel.ts create mode 100644 src/server/authentication/Passport.ts delete mode 100644 src/server/authentication/config/passport.ts delete mode 100644 src/server/authentication/controllers/user_controller.ts delete mode 100644 src/server/authentication/models/current_user_utils.ts delete mode 100644 src/server/authentication/models/user_model.ts delete mode 100644 src/server/credentials/CredentialsLoader.ts delete mode 100644 src/server/credentials/google_project_credentials.json create mode 100644 src/server/websocket.ts (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts new file mode 100644 index 000000000..46d356263 --- /dev/null +++ b/src/client/util/CurrentUserUtils.ts @@ -0,0 +1,738 @@ +import { computed, observable, reaction } from "mobx"; +import * as rp from 'request-promise'; +import { Utils } from "../../Utils"; +import { DocServer } from "../DocServer"; +import { Docs, DocumentOptions } from "../documents/Documents"; +import { UndoManager } from "./UndoManager"; +import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc"; +import { List } from "../../new_fields/List"; +import { listSpec } from "../../new_fields/Schema"; +import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; +import { Cast, PromiseValue, StrCast, NumCast } from "../../new_fields/Types"; +import { nullAudio } from "../../new_fields/URLField"; +import { DragManager } from "./DragManager"; +import { InkingControl } from "../views/InkingControl"; +import { Scripting } from "./Scripting"; +import { CollectionViewType } from "../views/collections/CollectionView"; +import { makeTemplate } from "./DropConverter"; +import { RichTextField } from "../../new_fields/RichTextField"; +import { PrefetchProxy } from "../../new_fields/Proxy"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; +import { MainView } from "../views/MainView"; +import { DocumentType } from "../documents/DocumentTypes"; +import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; +import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; + +export class CurrentUserUtils { + private static curr_id: string; + //TODO tfs: these should be temporary... + private static mainDocId: string | undefined; + + 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(); } + @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).inkPen as Doc; } + + @observable public static GuestTarget: Doc | undefined; + @observable public static GuestWorkspace: Doc | undefined; + @observable public static GuestMobile: Doc | undefined; + + // sets up the default User Templates - slideView, queryView, descriptionView + static setupUserTemplateButtons(doc: Doc) { + if (doc["template-button-query"] === undefined) { + const queryTemplate = Docs.Create.MulticolumnDocument( + [ + Docs.Create.QueryDocument({ title: "query", _height: 200 }), + Docs.Create.FreeformDocument([], { title: "data", _height: 100, _LODdisable: true }) + ], + { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } + ); + queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); + doc["template-button-query"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, + removeDropProperties: new List(["dropAction"]), title: "query view", icon: "question-circle" + }); + } + + 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 }) + ], + { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } + ); + slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); + doc["template-button-slides"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, + removeDropProperties: new List(["dropAction"]), title: "presentation slide", icon: "address-card" + }); + } + + if (doc["template-button-description"] === undefined) { + const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created + Doc.GetProto(descriptionTemplate).layout = + "
    " + + "
    "; + descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView"); + + doc["template-button-description"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'), + dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc, + removeDropProperties: new List(["dropAction"]), title: "description view", icon: "window-maximize" + }); + } + + if (doc["template-button-switch"] === undefined) { + const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; + + const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _LODdisable: true, _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, _LODdisable: 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, }); + 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(["dropAction"]), title: "data switch", icon: "toggle-on" + }); + } + + 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" + }); + + 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 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 = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 }; + const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 }; + const descriptionWrapperOpts = { title: "descriptions", _height: 300, columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" }; + + const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); + descriptionWrapper.sectionHeaders = 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 }); + detailView.isTemplateDoc = makeTemplate(detailView); + + details.title = "Details"; + short.title = "A Short Description"; + long.title = "Long Description"; + + doc["template-button-detail"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + dragFactory: new PrefetchProxy(detailView) as any as Doc, + removeDropProperties: new List(["dropAction"]), title: "detail view", icon: "window-maximize" + }); + } + + if (doc["template-buttons"] === undefined) { + doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, + doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc], { + title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", + _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + })); + } else { + const curButnTypes = Cast(doc["template-buttons"], Doc, null); + const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, + doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc]; + DocListCastAsync(curButnTypes.data).then(async 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", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }); + 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" }); + 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" }); + 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") + }); + 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" } + ]; + if (doc.fieldTypes === undefined) { + doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" }); + Doc.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 })); + } 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 => { + 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, + 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.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", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + }); + iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); + doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); + } + 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)") + }); + 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)") }); + 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-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 })); + } 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-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; + DocListCastAsync(templateIconsDoc.data).then(async 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, label: string, icon: string, drag?: string, ignoreClick?: boolean, + click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc + }[] { + if (doc.emptyPresentation === undefined) { + doc.emptyPresentation = Docs.Create.PresDocument(new List(), + { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); + } + if (doc.emptyCollection === undefined) { + doc.emptyCollection = Docs.Create.FreeformDocument([], + { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" }); + } + 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" }); + } + if (doc.emptyWebpage === undefined) { + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true }); + } + return [ + { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, + { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc }, + { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: '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" })' }, + { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' }, + { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, + { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` }, + { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' }, + { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc }, + { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' }, + { title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' }, + { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, + { title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' }, + { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, + // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, + // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, + { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, + { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, + { title: "Connect a Google Account", label: "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[] = []; + 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, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({ + _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, + icon, + title, + label, + ignoreClick, + dropAction: "copy", + onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, + onClick: click ? ScriptField.MakeScript(click) : undefined, + ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, + activePen, + backgroundColor, + removeDropProperties: new List(["dropAction"]), + dragFactory, + })); + + 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 }), + })); + } else { + creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); + } + return doc.myItemCreators as Doc; + } + + static setupMobileButtons(doc: Doc, buttons?: string[]) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" }, + { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, + { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, + // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "red", activePen: doc }, + { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" }, + // { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" }, + ]; + return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ + _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, + ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, + backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, + })); + } + + static setupThumbButtons(doc: Doc) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: 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.activePen.inkPen, this)`, activePen: doc }, + { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, + ]; + 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, + ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true, + backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, + })); + } + + static setupThumbDoc(userDoc: Doc) { + 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" + }); + thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { + _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", linearViewIsExpanded: true, flexDirection: "column" + }); + userDoc.thumbDoc = thumbDoc; + } + return Cast(userDoc.thumbDoc, Doc); + } + + static setupMobileDoc(userDoc: Doc) { + return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { + columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5 + }); + } + + static setupMobileInkingDoc(userDoc: Doc) { + return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" }); + } + + 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", _chromeStatus: "enabled", lockedPosition: true + }); + const uploadDoc = Docs.Create.StackingDocument([], { + title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true + }); + return Docs.Create.StackingDocument([webDoc, uploadDoc], { + _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray" + }); + } + + // 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, sidebarContainer: Doc) { + // setup a masonry view of all he creators + const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); + const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); + + 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", + })); + } + // 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(["dropAction", "forceActive"]) + }); + doc.myColorPicker = new PrefetchProxy(color); + } + + if (doc["tabs-button-tools"] === undefined) { + doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({ + _width: 35, _height: 25, title: "Tools", _fontSize: 10, + letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", + sourcePanel: new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { + _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true + })) as any as Doc, + targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"), + })); + } + (doc["tabs-button-tools"] as Doc).sourcePanel; // prefetch sourcePanel + return doc["tabs-button-tools"] as Doc; + } + + static setupWorkspaces(doc: Doc) { + // setup workspaces library item + if (doc.myWorkspaces === undefined) { + doc.myWorkspaces = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, + })); + } + const newWorkspace = ScriptField.MakeScript(`createNewWorkspace()`); + (doc.myWorkspaces as Doc).contextMenuScripts = new List([newWorkspace!]); + (doc.myWorkspaces as Doc).contextMenuLabels = new List(["Create New Workspace"]); + + return doc.myWorkspaces as Doc; + } + static setupCatalog(doc: Doc) { + if (doc.myCatalog === undefined) { + doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true, + })); + } + return doc.myCatalog as Doc; + } + static setupRecentlyClosed(doc: Doc) { + // setup Recently Closed library item + if (doc.myRecentlyClosed === undefined) { + doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, + })); + } + // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready + PromiseValue(Cast(doc.myRecentlyClosed, Doc)).then(recent => recent && PromiseValue(recent.data).then(DocListCast)); + const clearAll = ScriptField.MakeScript(`self.data = new List([])`); + (doc.myRecentlyClosed as Doc).contextMenuScripts = new List([clearAll!]); + (doc.myRecentlyClosed as Doc).contextMenuLabels = new List(["Clear All"]); + + return doc.myRecentlyClosed as Doc; + } + // setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views + static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) { + const workspaces = CurrentUserUtils.setupWorkspaces(doc); + const documents = CurrentUserUtils.setupCatalog(doc); + const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc); + + if (doc["tabs-button-library"] === undefined) { + doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({ + _width: 50, _height: 25, title: "Library", _fontSize: 10, + letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", + sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], { + title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true + })) as any as Doc, + targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") + })); + } + return doc["tabs-button-library"] as Doc; + } + + // setup the Search button which will display the search panel. + static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) { + if (doc["tabs-button-search"] === undefined) { + doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({ + _width: 50, _height: 25, title: "Search", _fontSize: 10, + letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", + sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc, + searchFileTypes: new List([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]), + targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, + lockedPosition: true, + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel") + })); + } + return doc["tabs-button-search"] as Doc; + } + + static setupSidebarContainer(doc: Doc) { + if (doc["tabs-panelContainer"] === undefined) { + const sidebarContainer = new Doc(); + sidebarContainer._chromeStatus = "disabled"; + sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()"); + doc["tabs-panelContainer"] = new PrefetchProxy(sidebarContainer); + } + return doc["tabs-panelContainer"] as Doc; + } + + // setup the list of sidebar mode buttons which determine what is displayed in the sidebar + static async setupSidebarButtons(doc: Doc) { + const sidebarContainer = CurrentUserUtils.setupSidebarContainer(doc); + const toolsBtn = await CurrentUserUtils.setupToolsBtnPanel(doc, sidebarContainer); + const libraryBtn = CurrentUserUtils.setupLibraryPanel(doc, sidebarContainer); + const searchBtn = CurrentUserUtils.setupSearchBtnPanel(doc, sidebarContainer); + + // Finally, setup the list of buttons to display in the sidebar + if (doc["tabs-buttons"] === undefined) { + doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([searchBtn, libraryBtn, toolsBtn], { + _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", hideHeadings: true, ignoreClick: true, _chromeStatus: "view-mode", + title: "sidebar btn row stack", backgroundColor: "dimGray", + })); + (toolsBtn.onClick as ScriptField).script.run({ this: toolsBtn }); + } + } + + 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 + })) as any as Doc + + static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ + ...opts, + dropAction: "alias", removeDropProperties: new List(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100 + })) 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-pen"] === undefined) { + doc["dockedBtn-pen"] = CurrentUserUtils.ficon({ + onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)"), + author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activePen.inkPen, this)`), activePen: doc + }); + } + if (doc["dockedBtn-undo"] === undefined) { + doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); + } + if (doc["dockedBtn-redo"] === undefined) { + doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); + } + if (doc.dockedBtns === undefined) { + doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc, doc["dockedBtn-pen"] as Doc]); + } + } + // 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" })); + } + } + + // the initial presentation Doc to use + static setupDefaultPresentation(doc: Doc) { + if (doc.activePresentation === undefined) { + doc.activePresentation = Docs.Create.PresDocument(new List(), { + title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", + _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" + }); + } + } + + static setupRightSidebar(doc: Doc) { + if (doc.rightSidebarCollection === undefined) { + doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Right Sidebar" })); + } + } + + static setupClickEditorTemplates(doc: Doc) { + if (doc["clickFuncs-child"] === undefined) { + const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( + "docCast(thisContainer.target).then((target) => {" + + " target && docCast(this.source).then((source) => { " + + " target.proto.data = new List([source || this]); " + + " }); " + + "})", + { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" }); + + const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( + "openOnRight(self.doubleClickView)", + { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" }); + + doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); + } + // 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 + }, "onClick"); + const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { + title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", + isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200 + }, "onDoubleClick"); + 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 + }, "onCheckedClick"); + doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" }); + } + PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); + + return doc.clickFuncs as Doc; + } + + static async updateUserDocument(doc: Doc) { + new InkingControl(); + doc.title = Doc.CurrentUserEmail; + doc.activePen = doc; + doc.inkColor = StrCast(doc.backgroundColor, ""); + doc.fontSize = NumCast(doc.fontSize, 12); + 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.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing + 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(); + + // 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 }); + + return doc; + } + public static async loadCurrentUser() { + return rp.get(Utils.prepend("/getCurrentUser")).then(response => { + if (response) { + const result: { id: string, email: string } = JSON.parse(response); + return result; + } else { + throw new Error("There should be a user! Why does Dash think there isn't one?"); + } + }); + } + + public static async loadUserDocument({ id, email }: { id: string, email: string }) { + this.curr_id = id; + Doc.CurrentUserEmail = email; + await rp.get(Utils.prepend("/getUserDocumentId")).then(id => { + if (id && id !== "guest") { + return DocServer.GetRefField(id).then(async field => + Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true)))); + } else { + throw new Error("There should be a user id! Why does Dash think there isn't one?"); + } + }); + } +} + +Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); }); +Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); }); +Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); }); \ No newline at end of file diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index e20434461..0e15197c4 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -8,7 +8,7 @@ import { SelectionManager } from "./SelectionManager"; import "./SettingsManager.scss"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Networking } from "../Network"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "./CurrentUserUtils"; import { Utils } from "../../Utils"; library.add(fa.faWindowClose); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 4f8f9ed69..812ff3b93 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -13,7 +13,7 @@ import { DocUtils, Docs } from "../documents/Documents"; import { undoBatch } from "../util/UndoManager"; import { Scripting } from "../util/Scripting"; import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import HorizontalPalette from "./Palette"; import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils"; import { DocumentView } from "./nodes/DocumentView"; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 81d99e009..af9c4a562 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -3,7 +3,7 @@ import { ColorState } from 'react-color'; import { Doc } from "../../new_fields/Doc"; import { InkTool } from "../../new_fields/InkField"; import { FieldValue, NumCast, StrCast } from "../../new_fields/Types"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { Scripting } from "../util/Scripting"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index b21eb9c8f..17c001971 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,6 +1,6 @@ import { MainView } from "./MainView"; import { Docs } from "../documents/Documents"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import * as ReactDOM from 'react-dom'; import * as React from 'react'; import { DocServer } from "../DocServer"; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9bfef06b4..9bc08de3e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -19,7 +19,7 @@ import { List } from '../../new_fields/List'; import { listSpec } from '../../new_fields/Schema'; import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { TraceMobx } from '../../new_fields/util'; -import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; +import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx index 13c06b0a3..618285293 100644 --- a/src/client/views/collections/CollectionMapView.tsx +++ b/src/client/views/collections/CollectionMapView.tsx @@ -1,4 +1,4 @@ -import { GoogleApiWrapper, Map as GeoMap, IMapProps, Marker } from "google-maps-react"; +import { GoogleApiWrapper, Map as GeoMap, MapProps as IMapProps, Marker } from "google-maps-react"; import { observer } from "mobx-react"; import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; @@ -47,7 +47,7 @@ class CollectionMapView extends CollectionSubView private _cancelAddrReq = new Map(); private _cancelLocReq = new Map(); private _initialLookupPending = new Map(); - private responders: { location: Lambda, address: Lambda }[] = []; + private responders: { locationDisposer: Lambda, addressDisposer: Lambda }[] = []; /** * Note that all the uses of runInAction below are not included @@ -176,13 +176,16 @@ class CollectionMapView extends CollectionSubView } @computed get reactiveContents() { - this.responders.forEach(({ location, address }) => { location(); address(); }); + this.responders.forEach(({ locationDisposer, addressDisposer }) => { + locationDisposer(); + addressDisposer(); + }); this.responders = []; return this.childLayoutPairs.map(({ layout }) => { const fieldKey = Doc.LayoutFieldKey(layout); const id = layout[Id]; this.responders.push({ - location: computed(() => ({ lat: layout[`${fieldKey}-lat`], lng: layout[`${fieldKey}-lng`] })) + locationDisposer: computed(() => ({ lat: layout[`${fieldKey}-lat`], lng: layout[`${fieldKey}-lng`] })) .observe(({ oldValue, newValue }) => { if (this._cancelLocReq.get(id)) { this._cancelLocReq.set(id, false); @@ -190,7 +193,7 @@ class CollectionMapView extends CollectionSubView this.respondToLocationChange(layout, fieldKey, newValue, oldValue); } }), - address: computed(() => Cast(layout[`${fieldKey}-address`], "string", null)) + addressDisposer: computed(() => Cast(layout[`${fieldKey}-address`], "string", null)) .observe(({ oldValue, newValue }) => { if (this._cancelAddrReq.get(id)) { this._cancelAddrReq.set(id, false); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 8b50bd8fc..bff6d121b 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -8,7 +8,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, ScriptCast } from "../../../new_fields/Types"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { Upload } from "../../../server/SharedMediaTypes"; import { Utils } from "../../../Utils"; import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 0f239d385..4b3b2e234 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -35,7 +35,7 @@ import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; import { CollectionViewBaseChrome } from './CollectionViewChromes'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { Id } from '../../../new_fields/FieldSymbols'; import { listSpec } from '../../../new_fields/Schema'; import { Docs } from '../../documents/Documents'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 92fa2781c..9a5b2c27c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -3,7 +3,7 @@ import * as mobxUtils from 'mobx-utils'; import CursorField from "../../../../new_fields/CursorField"; import { listSpec } from "../../../../new_fields/Schema"; import { Cast } from "../../../../new_fields/Types"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormView.scss"; import React = require("react"); diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 7ab6d99c2..bc84204b9 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -4,7 +4,7 @@ import { SketchPicker } from 'react-color'; import { documentSchema } from "../../../new_fields/documentSchemas"; import { makeInterface } from "../../../new_fields/Schema"; import { StrCast } from "../../../new_fields/Types"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { SelectionManager } from "../../util/SelectionManager"; import { ViewBoxBaseComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index 295e82142..f30e9869a 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -12,8 +12,7 @@ import { observer } from 'mobx-react'; import { observable } from 'mobx'; import { Utils } from '../Utils'; import MobileInterface from './MobileInterface'; -import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; -import { Scripting } from '../client/util/Scripting'; +import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 69a80e1b4..9d4d58ad1 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -10,7 +10,6 @@ import { DocumentManager } from '../client/util/DocumentManager'; import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu'; import { Scripting } from '../client/util/Scripting'; import { Transform } from '../client/util/Transform'; -import { CollectionView } from '../client/views/collections/CollectionView'; import { DocumentDecorations } from '../client/views/DocumentDecorations'; import GestureOverlay from '../client/views/GestureOverlay'; import { InkingControl } from '../client/views/InkingControl'; @@ -23,9 +22,10 @@ import { InkTool } from '../new_fields/InkField'; import { listSpec } from '../new_fields/Schema'; import { Cast, FieldValue } from '../new_fields/Types'; import { WebField } from "../new_fields/URLField"; -import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; +import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from '../Utils'; import "./MobileInterface.scss"; +import { CollectionView } from '../client/views/collections/CollectionView'; library.add(faLongArrowAltLeft); diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts index e2b01d585..27e9de065 100644 --- a/src/server/ApiManagers/ApiManager.ts +++ b/src/server/ApiManagers/ApiManager.ts @@ -1,4 +1,4 @@ -import RouteManager, { RouteInitializer } from "../RouteManager"; +import { RouteInitializer } from "../RouteManager"; export type Registration = (initializer: RouteInitializer) => void; diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index bd80d6500..7fbb37658 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -1,6 +1,6 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied } from "../RouteManager"; -import { WebSocket } from "../Websocket/Websocket"; +import { WebSocket } from "../websocket"; import { Database } from "../database"; import rimraf = require("rimraf"); import { filesDirectory } from ".."; diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index 17968cc7d..f94b77cac 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -38,7 +38,7 @@ export default class GeneralGoogleManager extends ApiManager { method: Method.GET, subscription: "/revokeGoogleAccessToken", secureHandler: async ({ user, res }) => { - await Database.Auxiliary.GoogleAuthenticationToken.Revoke(user.id); + await Database.Auxiliary.GoogleAccessToken.Revoke(user.id); res.send(); } }); diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts index ef9b88541..ab3dfffcc 100644 --- a/src/server/DashSession/DashSessionAgent.ts +++ b/src/server/DashSession/DashSessionAgent.ts @@ -2,7 +2,7 @@ import { Email, pathFromRoot } from "../ActionUtilities"; import { red, yellow, green, cyan } from "colors"; import { get } from "request-promise"; import { Utils } from "../../Utils"; -import { WebSocket } from "../Websocket/Websocket"; +import { WebSocket } from "../websocket"; import { MessageStore } from "../Message"; import { launchServer, onWindows } from ".."; import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from "fs"; diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 80e4a6741..b23215996 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -1,5 +1,5 @@ import RouteSubscriber from "./RouteSubscriber"; -import { DashUserModel } from "./authentication/models/user_model"; +import { DashUserModel } from "./authentication/DashUserModel"; import { Request, Response, Express } from 'express'; import { cyan, red, green } from 'colors'; diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts deleted file mode 100644 index 844535056..000000000 --- a/src/server/Websocket/Websocket.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { Utils } from "../../Utils"; -import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "../Message"; -import { Client } from "../Client"; -import { Socket } from "socket.io"; -import { Database } from "../database"; -import { Search } from "../Search"; -import * as io from 'socket.io'; -import YoutubeApi from "../apis/youtube/youtubeApiSample"; -import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader"; -import { logPort } from "../ActionUtilities"; -import { timeMap } from "../ApiManagers/UserManager"; -import { green } from "colors"; -import { networkInterfaces } from "os"; -import executeImport from "../../scraping/buxton/final/BuxtonImporter"; -import { DocumentsCollection } from "../IDatabase"; - -export namespace WebSocket { - - export let _socket: Socket; - const clients: { [key: string]: Client } = {}; - export const socketMap = new Map(); - export let disconnect: Function; - - - export async function start(isRelease: boolean) { - await preliminaryFunctions(); - initialize(isRelease); - } - - async function preliminaryFunctions() { - } - function initialize(isRelease: boolean) { - const endpoint = io(); - endpoint.on("connection", function (socket: Socket) { - _socket = socket; - - socket.use((_packet, next) => { - const userEmail = socketMap.get(socket); - if (userEmail) { - timeMap[userEmail] = Date.now(); - } - next(); - }); - - // convenience function to log server messages on the client - function log(message?: any, ...optionalParams: any[]) { - socket.emit('log', ['Message from server:', message, ...optionalParams]); - } - - socket.on('message', function (message, room) { - console.log('Client said: ', message); - socket.in(room).emit('message', message); - }); - - socket.on('create or join', function (room) { - console.log('Received request to create or join room ' + room); - - const clientsInRoom = socket.adapter.rooms[room]; - const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; - console.log('Room ' + room + ' now has ' + numClients + ' client(s)'); - - if (numClients === 0) { - socket.join(room); - console.log('Client ID ' + socket.id + ' created room ' + room); - socket.emit('created', room, socket.id); - - } else if (numClients === 1) { - console.log('Client ID ' + socket.id + ' joined room ' + room); - socket.in(room).emit('join', room); - socket.join(room); - socket.emit('joined', room, socket.id); - socket.in(room).emit('ready'); - } else { // max two clients - socket.emit('full', room); - } - }); - - socket.on('ipaddr', function () { - const ifaces = networkInterfaces(); - for (const dev in ifaces) { - ifaces[dev].forEach(function (details) { - if (details.family === 'IPv4' && details.address !== '127.0.0.1') { - socket.emit('ipaddr', details.address); - } - }); - } - }); - - socket.on('bye', function () { - console.log('received bye'); - }); - - Utils.Emit(socket, MessageStore.Foo, "handshooken"); - - Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid)); - Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); - Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); - Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); - if (isRelease) { - Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false)); - } - - Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField); - Utils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiQuery, HandleYoutubeQuery); - Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); - Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id)); - Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); - Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); - Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content)); - Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content)); - Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content)); - Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); - Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); - - /** - * Whenever we receive the go-ahead, invoke the import script and pass in - * as an emitter and a terminator the functions that simply broadcast a result - * or indicate termination to the client via the web socket - */ - Utils.AddServerHandler(socket, MessageStore.BeginBuxtonImport, () => { - executeImport( - deviceOrError => Utils.Emit(socket, MessageStore.BuxtonDocumentResult, deviceOrError), - results => Utils.Emit(socket, MessageStore.BuxtonImportComplete, results) - ); - }); - - disconnect = () => { - socket.broadcast.emit("connection_terminated", Date.now()); - socket.disconnect(true); - }; - }); - - const socketPort = isRelease ? Number(process.env.socketPort) : 4321; - endpoint.listen(socketPort); - logPort("websocket", socketPort); - } - - function processGesturePoints(socket: Socket, content: GestureContent) { - socket.broadcast.emit("receiveGesturePoints", content); - } - - function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) { - socket.broadcast.emit("receiveOverlayTrigger", content); - } - - function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) { - socket.broadcast.emit("receiveUpdateOverlayPosition", content); - } - - function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) { - socket.broadcast.emit("receiveMobileDocumentUpload", content); - } - - function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { - const { ProjectCredentials } = GoogleCredentialsLoader; - switch (query.type) { - case YoutubeQueryTypes.Channels: - YoutubeApi.authorizedGetChannel(ProjectCredentials); - break; - case YoutubeQueryTypes.SearchVideo: - YoutubeApi.authorizedGetVideos(ProjectCredentials, query.userInput, callback); - case YoutubeQueryTypes.VideoDetails: - YoutubeApi.authorizedGetVideoDetails(ProjectCredentials, query.videoIds, callback); - } - } - - export async function doDelete(onlyFields = true) { - const target: string[] = []; - onlyFields && target.push(DocumentsCollection); - await Database.Instance.dropSchema(...target); - if (process.env.DISABLE_SEARCH !== "true") { - await Search.clear(); - } - } - - function barReceived(socket: SocketIO.Socket, userEmail: string) { - clients[userEmail] = new Client(userEmail.toString()); - console.log(green(`user ${userEmail} has connected to the web socket`)); - socketMap.set(socket, userEmail); - } - - function getField([id, callback]: [string, (result?: Transferable) => void]) { - Database.Instance.getDocument(id, (result?: Transferable) => - callback(result ? result : undefined)); - } - - function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) { - Database.Instance.getDocuments(ids, callback); - } - - function setField(socket: Socket, newValue: Transferable) { - 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 }); - } - } - - function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { - Database.Instance.getDocument(id, callback); - } - - function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { - Database.Instance.getDocuments(ids, callback); - } - - const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { - "number": "_n", - "string": "_t", - "boolean": "_b", - "image": ["_t", "url"], - "video": ["_t", "url"], - "pdf": ["_t", "url"], - "audio": ["_t", "url"], - "web": ["_t", "url"], - "script": ["_t", value => value.script.originalScript], - "RichTextField": ["_t", value => value.Text], - "date": ["_d", value => new Date(value.date).toISOString()], - "proxy": ["_i", "fieldId"], - "list": ["_l", list => { - const results = []; - for (const value of list.fields) { - const term = ToSearchTerm(value); - if (term) { - results.push(term.value); - } - } - return results.length ? results : null; - }] - }; - - function ToSearchTerm(val: any): { suffix: string, value: any } | undefined { - if (val === null || val === undefined) { - return; - } - const type = val.__type || typeof val; - let suffix = suffixMap[type]; - if (!suffix) { - return; - } - - if (Array.isArray(suffix)) { - const accessor = suffix[1]; - if (typeof accessor === "function") { - val = accessor(val); - } else { - val = val[accessor]; - } - suffix = suffix[0]; - } - - return { suffix, value: val }; - } - - function getSuffix(value: string | [string, any]): string { - return typeof value === "string" ? value : value[0]; - } - - function UpdateField(socket: Socket, diff: Diff) { - Database.Instance.update(diff.id, diff.diff, - () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); - const docfield = diff.diff.$set || diff.diff.$unset; - if (!docfield) { - return; - } - const update: any = { id: diff.id }; - let dynfield = false; - for (let key in docfield) { - if (!key.startsWith("fields.")) continue; - dynfield = true; - const val = docfield[key]; - key = key.substring(7); - Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null }); - const term = ToSearchTerm(val); - if (term !== undefined) { - const { suffix, value } = term; - update[key + suffix] = { set: value }; - } - } - if (dynfield) { - Search.updateDocument(update); - } - } - - function DeleteField(socket: Socket, id: string) { - Database.Instance.delete({ _id: id }).then(() => { - socket.broadcast.emit(MessageStore.DeleteField.Message, id); - }); - - Search.deleteDocuments([id]); - } - - function DeleteFields(socket: Socket, ids: string[]) { - Database.Instance.delete({ _id: { $in: ids } }).then(() => { - socket.broadcast.emit(MessageStore.DeleteFields.Message, ids); - }); - Search.deleteDocuments(ids); - } - - function CreateField(newValue: any) { - Database.Instance.insert(newValue); - } - -} - diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts new file mode 100644 index 000000000..e3f4d167b --- /dev/null +++ b/src/server/apis/google/CredentialsLoader.ts @@ -0,0 +1,29 @@ +import { readFile } from "fs"; + +export namespace GoogleCredentialsLoader { + + export interface InstalledCredentials { + client_id: string; + project_id: string; + auth_uri: string; + token_uri: string; + auth_provider_x509_cert_url: string; + client_secret: string; + redirect_uris: string[]; + } + + export let ProjectCredentials: InstalledCredentials; + + export async function loadCredentials() { + ProjectCredentials = await new Promise(resolve => { + readFile(__dirname + '/google_project_credentials.json', function processClientSecrets(err, content) { + if (err) { + console.log('Error loading client secret file: ' + err); + return; + } + resolve(JSON.parse(content.toString()).installed); + }); + }); + } + +} diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 48a8da89f..2e4811c86 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -3,9 +3,9 @@ import { OAuth2Client, Credentials, OAuth2ClientOptions } from "google-auth-libr import { Opt } from "../../../new_fields/Doc"; import { GaxiosResponse } from "gaxios"; import request = require('request-promise'); -import * as qs from 'query-string'; +import * as qs from "query-string"; import { Database } from "../../database"; -import { GoogleCredentialsLoader } from "../../credentials/CredentialsLoader"; +import { GoogleCredentialsLoader } from "./CredentialsLoader"; /** * Scopes give Google users fine granularity of control @@ -224,7 +224,7 @@ export namespace GoogleApiServerUtils { }); }); const enriched = injectUserInfo(credentials); - await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, enriched); + await Database.Auxiliary.GoogleAccessToken.Write(userId, enriched); return enriched; } @@ -280,7 +280,7 @@ export namespace GoogleApiServerUtils { * and a flag indicating whether or not they were refreshed during retrieval */ export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt, refreshed: boolean }> { - let credentials = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId); + let credentials = await Database.Auxiliary.GoogleAccessToken.Fetch(userId); let refreshed = false; if (!credentials) { return { credentials: undefined, refreshed }; @@ -318,7 +318,7 @@ export namespace GoogleApiServerUtils { }); // expires_in is in seconds, but we're building the new expiry date in milliseconds const expiry_date = new Date().getTime() + (expires_in * 1000); - await Database.Auxiliary.GoogleAuthenticationToken.Update(userId, access_token, expiry_date); + await Database.Auxiliary.GoogleAccessToken.Update(userId, access_token, expiry_date); // update the relevant properties credentials.access_token = access_token; credentials.expiry_date = expiry_date; diff --git a/src/server/apis/google/google_project_credentials.json b/src/server/apis/google/google_project_credentials.json new file mode 100644 index 000000000..955c5a3c1 --- /dev/null +++ b/src/server/apis/google/google_project_credentials.json @@ -0,0 +1,11 @@ +{ + "installed": { + "client_id": "343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com", + "project_id": "quickstart-1565056383187", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "w8KIFSc0MQpmUYHed4qEzn8b", + "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"] + } +} \ No newline at end of file diff --git a/src/server/apis/youtube/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js index 50b3c7b38..d535bd9ff 100644 --- a/src/server/apis/youtube/youtubeApiSample.js +++ b/src/server/apis/youtube/youtubeApiSample.js @@ -1,6 +1,8 @@ const fs = require('fs'); const readline = require('readline'); -const { google } = require('googleapis'); +const { + google +} = require('googleapis'); const OAuth2 = google.auth.OAuth2; @@ -19,21 +21,27 @@ module.exports.readApiKey = (callback) => { } callback(content); }); -} +}; module.exports.authorizedGetChannel = (apiKey) => { //this didnt get called // Authorize a client with the loaded credentials, then call the YouTube API. authorize(JSON.parse(apiKey), getChannel); -} +}; module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { - authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack }); -} + authorize(JSON.parse(apiKey), getVideos, { + userInput: userInput, + callBack: callBack + }); +}; module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { - authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack }); -} + authorize(JSON.parse(apiKey), getVideoDetails, { + videoIds: videoIds, + callBack: callBack + }); +}; /** diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts new file mode 100644 index 000000000..00f1fe44e --- /dev/null +++ b/src/server/authentication/AuthenticationManager.ts @@ -0,0 +1,268 @@ +import { default as User, DashUserModel } from "./DashUserModel"; +import { Request, Response, NextFunction } from "express"; +import * as passport from "passport"; +import { IVerifyOptions } from "passport-local"; +import "./Passport"; +import flash = require("express-flash"); +import * as async from 'async'; +import * as nodemailer from 'nodemailer'; +import c = require("crypto"); +import { Utils } from "../../Utils"; +import { MailOptions } from "nodemailer/lib/stream-transport"; + +/** + * GET /signup + * Directs user to the signup page + * modeled by signup.pug in views + */ +export let getSignup = (req: Request, res: Response) => { + if (req.user) { + return res.redirect("/home"); + } + res.render("signup.pug", { + title: "Sign Up", + user: req.user, + }); +}; + +/** + * POST /signup + * Create a new local account. + */ +export let postSignup = (req: Request, res: Response, next: NextFunction) => { + req.assert("email", "Email is not valid").isEmail(); + req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); + req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + + const errors = req.validationErrors(); + + if (errors) { + return res.redirect("/signup"); + } + + const email = req.body.email as String; + const password = req.body.password; + + const model = { + email, + password, + userDocumentId: Utils.GenerateGuid() + } as Partial; + + const user = new User(model); + + User.findOne({ email }, (err, existingUser) => { + if (err) { return next(err); } + if (existingUser) { + return res.redirect("/login"); + } + user.save((err: any) => { + if (err) { return next(err); } + req.logIn(user, (err) => { + if (err) { return next(err); } + tryRedirectToTarget(req, res); + }); + }); + }); + +}; + +const tryRedirectToTarget = (req: Request, res: Response) => { + if (req.session && req.session.target) { + const target = req.session.target; + req.session.target = undefined; + res.redirect(target); + } else { + res.redirect("/home"); + } +}; + + +/** + * GET /login + * Login page. + */ +export let getLogin = (req: Request, res: Response) => { + if (req.user) { + req.session!.target = undefined; + return res.redirect("/home"); + } + res.render("login.pug", { + title: "Log In", + user: req.user + }); +}; + +/** + * POST /login + * Sign in using email and password. + * On failure, redirect to signup page + */ +export let postLogin = (req: Request, res: Response, next: NextFunction) => { + req.assert("email", "Email is not valid").isEmail(); + req.assert("password", "Password cannot be blank").notEmpty(); + req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + + const errors = req.validationErrors(); + + if (errors) { + req.flash("errors", "Unable to login at this time. Please try again."); + return res.redirect("/signup"); + } + + passport.authenticate("local", (err: Error, user: DashUserModel, _info: IVerifyOptions) => { + if (err) { next(err); return; } + if (!user) { + return res.redirect("/signup"); + } + req.logIn(user, (err) => { + if (err) { next(err); return; } + tryRedirectToTarget(req, res); + }); + })(req, res, next); +}; + +/** + * GET /logout + * Invokes the logout function on the request + * and destroys the user's current session. + */ +export let getLogout = (req: Request, res: Response) => { + req.logout(); + const sess = req.session; + if (sess) { + sess.destroy((err) => { if (err) { console.log(err); } }); + } + res.redirect("/login"); +}; + +export let getForgot = function (req: Request, res: Response) { + res.render("forgot.pug", { + title: "Recover Password", + user: req.user, + }); +}; + +export let postForgot = function (req: Request, res: Response, next: NextFunction) { + const email = req.body.email; + async.waterfall([ + function (done: any) { + c.randomBytes(20, function (err: any, buffer: Buffer) { + if (err) { + done(null); + return; + } + done(null, buffer.toString('hex')); + }); + }, + function (token: string, done: any) { + User.findOne({ email }, function (err, user: DashUserModel) { + if (!user) { + // NO ACCOUNT WITH SUBMITTED EMAIL + res.redirect("/forgotPassword"); + return; + } + user.passwordResetToken = token; + user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR + user.save(function (err: any) { + done(null, token, user); + }); + }); + }, + function (token: Uint16Array, user: DashUserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: user.email, + from: 'brownptcdash@gmail.com', + subject: 'Dash Password Reset', + text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + + 'http://' + req.headers.host + '/resetPassword/' + token + '\n\n' + + 'If you did not request this, please ignore this email and your password will remain unchanged.\n' + } as MailOptions; + smtpTransport.sendMail(mailOptions, function (err: Error | null) { + // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); + done(null, err, 'done'); + }); + } + ], function (err) { + if (err) return next(err); + res.redirect("/forgotPassword"); + }); +}; + +export let getReset = function (req: Request, res: Response) { + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) { + if (!user || err) { + return res.redirect("/forgotPassword"); + } + res.render("reset.pug", { + title: "Reset Password", + user: req.user, + }); + }); +}; + +export let postReset = function (req: Request, res: Response) { + async.waterfall([ + function (done: any) { + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) { + if (!user || err) { + return res.redirect('back'); + } + + req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); + req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + + if (req.validationErrors()) { + return res.redirect('back'); + } + + user.password = req.body.password; + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; + + user.save(function (err) { + if (err) { + res.redirect("/login"); + return; + } + req.logIn(user, function (err) { + if (err) { + return; + } + }); + done(null, user); + }); + }); + }, + function (user: DashUserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: user.email, + from: 'brownptcdash@gmail.com', + subject: 'Your password has been changed', + text: 'Hello,\n\n' + + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' + } as MailOptions; + smtpTransport.sendMail(mailOptions, function (err) { + done(null, err); + }); + } + ], function (err) { + res.redirect("/login"); + }); +}; \ No newline at end of file diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts new file mode 100644 index 000000000..51d920a8f --- /dev/null +++ b/src/server/authentication/DashUserModel.ts @@ -0,0 +1,86 @@ +//@ts-ignore +import * as bcrypt from "bcrypt-nodejs"; +//@ts-ignore +import * as mongoose from 'mongoose'; + +export type DashUserModel = mongoose.Document & { + email: String, + password: string, + passwordResetToken?: string, + passwordResetExpires?: Date, + + userDocumentId: string; + + profile: { + name: string, + gender: string, + location: string, + website: string, + picture: string + }, + + comparePassword: comparePasswordFunction, +}; + +type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; + +export type AuthToken = { + accessToken: string, + kind: string +}; + +const userSchema = new mongoose.Schema({ + email: String, + password: String, + passwordResetToken: String, + passwordResetExpires: Date, + + userDocumentId: String, + + facebook: String, + twitter: String, + google: String, + + profile: { + name: String, + gender: String, + location: String, + website: String, + picture: String + } +}, { timestamps: true }); + +/** + * Password hash middleware. + */ +userSchema.pre("save", function save(next) { + const user = this as DashUserModel; + if (!user.isModified("password")) { + return next(); + } + bcrypt.genSalt(10, (err: any, salt: string) => { + if (err) { + return next(err); + } + bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => { + if (err) { + return next(err); + } + user.password = hash; + next(); + }); + }); +}); + +const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) { + // Choose one of the following bodies for authentication logic. + // secure (expected, default) + bcrypt.compare(candidatePassword, this.password, cb); + // bypass password (debugging) + // cb(undefined, true); +}; + +userSchema.methods.comparePassword = comparePassword; + +const User = mongoose.model("User", userSchema); +export default User; \ No newline at end of file diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts new file mode 100644 index 000000000..9b0069414 --- /dev/null +++ b/src/server/authentication/Passport.ts @@ -0,0 +1,29 @@ +import * as passport from 'passport'; +import * as passportLocal from 'passport-local'; +import { default as User } from './DashUserModel'; + +const LocalStrategy = passportLocal.Strategy; + +passport.serializeUser((user, done) => { + done(undefined, user.id); +}); + +passport.deserializeUser((id, done) => { + User.findById(id, (err, user) => { + done(err, user); + }); +}); + +// AUTHENTICATE JUST WITH EMAIL AND PASSWORD +passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => { + User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => { + if (error) return done(error); + if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email + user.comparePassword(password, (error: Error, isMatch: boolean) => { + if (error) return done(error); + if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password + // valid authentication HERE + return done(undefined, user); + }); + }); +})); \ No newline at end of file diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts deleted file mode 100644 index 286209b20..000000000 --- a/src/server/authentication/config/passport.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as passport from 'passport'; -import * as passportLocal from 'passport-local'; -import { default as User } from '../models/user_model'; - -const LocalStrategy = passportLocal.Strategy; - -passport.serializeUser((user, done) => { - done(undefined, user.id); -}); - -passport.deserializeUser((id, done) => { - User.findById(id, (err, user) => { - done(err, user); - }); -}); - -// AUTHENTICATE JUST WITH EMAIL AND PASSWORD -passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => { - User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => { - if (error) return done(error); - if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email - user.comparePassword(password, (error: Error, isMatch: boolean) => { - if (error) return done(error); - if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password - // valid authentication HERE - return done(undefined, user); - }); - }); -})); \ No newline at end of file diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts deleted file mode 100644 index f0086d4ea..000000000 --- a/src/server/authentication/controllers/user_controller.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { default as User, DashUserModel, AuthToken } from "../models/user_model"; -import { Request, Response, NextFunction } from "express"; -import * as passport from "passport"; -import { IVerifyOptions } from "passport-local"; -import "../config/passport"; -import flash = require("express-flash"); -import * as async from 'async'; -import * as nodemailer from 'nodemailer'; -import c = require("crypto"); -import { Utils } from "../../../Utils"; -import { MailOptions } from "nodemailer/lib/stream-transport"; - -/** - * GET /signup - * Directs user to the signup page - * modeled by signup.pug in views - */ -export let getSignup = (req: Request, res: Response) => { - if (req.user) { - return res.redirect("/home"); - } - res.render("signup.pug", { - title: "Sign Up", - user: req.user, - }); -}; - -/** - * POST /signup - * Create a new local account. - */ -export let postSignup = (req: Request, res: Response, next: NextFunction) => { - req.assert("email", "Email is not valid").isEmail(); - req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); - req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); - - const errors = req.validationErrors(); - - if (errors) { - return res.redirect("/signup"); - } - - const email = req.body.email as String; - const password = req.body.password; - - const model = { - email, - password, - userDocumentId: Utils.GenerateGuid() - } as Partial; - - const user = new User(model); - - User.findOne({ email }, (err, existingUser) => { - if (err) { return next(err); } - if (existingUser) { - return res.redirect("/login"); - } - user.save((err: any) => { - if (err) { return next(err); } - req.logIn(user, (err) => { - if (err) { return next(err); } - tryRedirectToTarget(req, res); - }); - }); - }); - -}; - -const tryRedirectToTarget = (req: Request, res: Response) => { - if (req.session && req.session.target) { - const target = req.session.target; - req.session.target = undefined; - res.redirect(target); - } else { - res.redirect("/home"); - } -}; - - -/** - * GET /login - * Login page. - */ -export let getLogin = (req: Request, res: Response) => { - if (req.user) { - req.session!.target = undefined; - return res.redirect("/home"); - } - res.render("login.pug", { - title: "Log In", - user: req.user - }); -}; - -/** - * POST /login - * Sign in using email and password. - * On failure, redirect to signup page - */ -export let postLogin = (req: Request, res: Response, next: NextFunction) => { - req.assert("email", "Email is not valid").isEmail(); - req.assert("password", "Password cannot be blank").notEmpty(); - req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); - - const errors = req.validationErrors(); - - if (errors) { - req.flash("errors", "Unable to login at this time. Please try again."); - return res.redirect("/signup"); - } - - passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => { - if (err) { next(err); return; } - if (!user) { - return res.redirect("/signup"); - } - req.logIn(user, (err) => { - if (err) { next(err); return; } - tryRedirectToTarget(req, res); - }); - })(req, res, next); -}; - -/** - * GET /logout - * Invokes the logout function on the request - * and destroys the user's current session. - */ -export let getLogout = (req: Request, res: Response) => { - req.logout(); - const sess = req.session; - if (sess) { - sess.destroy((err) => { if (err) { console.log(err); } }); - } - res.redirect("/login"); -}; - -export let getForgot = function (req: Request, res: Response) { - res.render("forgot.pug", { - title: "Recover Password", - user: req.user, - }); -}; - -export let postForgot = function (req: Request, res: Response, next: NextFunction) { - const email = req.body.email; - async.waterfall([ - function (done: any) { - c.randomBytes(20, function (err: any, buffer: Buffer) { - if (err) { - done(null); - return; - } - done(null, buffer.toString('hex')); - }); - }, - function (token: string, done: any) { - User.findOne({ email }, function (err, user: DashUserModel) { - if (!user) { - // NO ACCOUNT WITH SUBMITTED EMAIL - res.redirect("/forgotPassword"); - return; - } - user.passwordResetToken = token; - user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR - user.save(function (err: any) { - done(null, token, user); - }); - }); - }, - function (token: Uint16Array, user: DashUserModel, done: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: user.email, - from: 'brownptcdash@gmail.com', - subject: 'Dash Password Reset', - text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + - 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + - 'http://' + req.headers.host + '/resetPassword/' + token + '\n\n' + - 'If you did not request this, please ignore this email and your password will remain unchanged.\n' - } as MailOptions; - smtpTransport.sendMail(mailOptions, function (err: Error | null) { - // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); - done(null, err, 'done'); - }); - } - ], function (err) { - if (err) return next(err); - res.redirect("/forgotPassword"); - }); -}; - -export let getReset = function (req: Request, res: Response) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) { - if (!user || err) { - return res.redirect("/forgotPassword"); - } - res.render("reset.pug", { - title: "Reset Password", - user: req.user, - }); - }); -}; - -export let postReset = function (req: Request, res: Response) { - async.waterfall([ - function (done: any) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) { - if (!user || err) { - return res.redirect('back'); - } - - req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); - - if (req.validationErrors()) { - return res.redirect('back'); - } - - user.password = req.body.password; - user.passwordResetToken = undefined; - user.passwordResetExpires = undefined; - - user.save(function (err) { - if (err) { - res.redirect("/login"); - return; - } - req.logIn(user, function (err) { - if (err) { - return; - } - }); - done(null, user); - }); - }); - }, - function (user: DashUserModel, done: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: user.email, - from: 'brownptcdash@gmail.com', - subject: 'Your password has been changed', - text: 'Hello,\n\n' + - 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' - } as MailOptions; - smtpTransport.sendMail(mailOptions, function (err) { - done(null, err); - }); - } - ], function (err) { - res.redirect("/login"); - }); -}; \ No newline at end of file diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts deleted file mode 100644 index b6ff10bab..000000000 --- a/src/server/authentication/models/current_user_utils.ts +++ /dev/null @@ -1,738 +0,0 @@ -import { action, computed, observable, reaction } from "mobx"; -import * as rp from 'request-promise'; -import { Utils } from "../../../Utils"; -import { DocServer } from "../../../client/DocServer"; -import { Docs, DocumentOptions } from "../../../client/documents/Documents"; -import { UndoManager } from "../../../client/util/UndoManager"; -import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, PromiseValue, StrCast, NumCast } from "../../../new_fields/Types"; -import { nullAudio, ImageField } from "../../../new_fields/URLField"; -import { DragManager } from "../../../client/util/DragManager"; -import { InkingControl } from "../../../client/views/InkingControl"; -import { Scripting, CompileScript } from "../../../client/util/Scripting"; -import { CollectionViewType } from "../../../client/views/collections/CollectionView"; -import { makeTemplate } from "../../../client/util/DropConverter"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { PrefetchProxy } from "../../../new_fields/Proxy"; -import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox"; -import { MainView } from "../../../client/views/MainView"; -import { DocumentType } from "../../../client/documents/DocumentTypes"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { DimUnit } from "../../../client/views/collections/collectionMulticolumn/CollectionMulticolumnView"; - -export class CurrentUserUtils { - private static curr_id: string; - //TODO tfs: these should be temporary... - private static mainDocId: string | undefined; - - 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(); } - @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).inkPen as Doc; } - - @observable public static GuestTarget: Doc | undefined; - @observable public static GuestWorkspace: Doc | undefined; - @observable public static GuestMobile: Doc | undefined; - - // sets up the default User Templates - slideView, queryView, descriptionView - static setupUserTemplateButtons(doc: Doc) { - if (doc["template-button-query"] === undefined) { - const queryTemplate = Docs.Create.MulticolumnDocument( - [ - Docs.Create.QueryDocument({ title: "query", _height: 200 }), - Docs.Create.FreeformDocument([], { title: "data", _height: 100, _LODdisable: true }) - ], - { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } - ); - queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); - doc["template-button-query"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, - removeDropProperties: new List(["dropAction"]), title: "query view", icon: "question-circle" - }); - } - - 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 }) - ], - { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } - ); - slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); - doc["template-button-slides"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, - removeDropProperties: new List(["dropAction"]), title: "presentation slide", icon: "address-card" - }); - } - - if (doc["template-button-description"] === undefined) { - const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created - Doc.GetProto(descriptionTemplate).layout = - "
    " + - "
    "; - descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView"); - - doc["template-button-description"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'), - dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc, - removeDropProperties: new List(["dropAction"]), title: "description view", icon: "window-maximize" - }); - } - - if (doc["template-button-switch"] === undefined) { - const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; - - const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _LODdisable: true, _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, _LODdisable: 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, }); - 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(["dropAction"]), title: "data switch", icon: "toggle-on" - }); - } - - 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" - }); - - 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 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 = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 }; - const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 }; - const descriptionWrapperOpts = { title: "descriptions", _height: 300, columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" }; - - const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); - descriptionWrapper.sectionHeaders = 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 }); - detailView.isTemplateDoc = makeTemplate(detailView); - - details.title = "Details"; - short.title = "A Short Description"; - long.title = "Long Description"; - - doc["template-button-detail"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), - dragFactory: new PrefetchProxy(detailView) as any as Doc, - removeDropProperties: new List(["dropAction"]), title: "detail view", icon: "window-maximize" - }); - } - - if (doc["template-buttons"] === undefined) { - doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, - doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc], { - title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", - _autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - })); - } else { - const curButnTypes = Cast(doc["template-buttons"], Doc, null); - const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, - doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc]; - DocListCastAsync(curButnTypes.data).then(async 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", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }); - 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" }); - 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" }); - 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") - }); - 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" } - ]; - if (doc.fieldTypes === undefined) { - doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" }); - Doc.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 })); - } 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 => { - 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, - 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.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", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") - }); - iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); - doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); - } - 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)") - }); - 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)") }); - 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-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 })); - } 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-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; - DocListCastAsync(templateIconsDoc.data).then(async 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, label: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc - }[] { - if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument(new List(), - { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); - } - if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" }); - } - 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" }); - } - if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true }); - } - return [ - { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, - { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc }, - { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: '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" })' }, - { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' }, - { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, - { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` }, - { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' }, - { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc }, - { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' }, - { title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' }, - { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, - { title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' }, - { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, - // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, - // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, - { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, - { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { title: "Connect a Google Account", label: "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[] = []; - 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, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, - icon, - title, - label, - ignoreClick, - dropAction: "copy", - onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, - onClick: click ? ScriptField.MakeScript(click) : undefined, - ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined, - activePen, - backgroundColor, - removeDropProperties: new List(["dropAction"]), - dragFactory, - })); - - 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 }), - })); - } else { - creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); - } - return doc.myItemCreators as Doc; - } - - static setupMobileButtons(doc: Doc, buttons?: string[]) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" }, - { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc }, - { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc }, - // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "red", activePen: doc }, - { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" }, - // { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" }, - ]; - return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, - backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, - })); - } - - static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: 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.activePen.inkPen, this)`, activePen: doc }, - { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc }, - ]; - 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, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true, - backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, - })); - } - - static setupThumbDoc(userDoc: Doc) { - 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" - }); - thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { - _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", linearViewIsExpanded: true, flexDirection: "column" - }); - userDoc.thumbDoc = thumbDoc; - } - return Cast(userDoc.thumbDoc, Doc); - } - - static setupMobileDoc(userDoc: Doc) { - return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { - columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5 - }); - } - - static setupMobileInkingDoc(userDoc: Doc) { - return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" }); - } - - 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", _chromeStatus: "enabled", lockedPosition: true - }); - const uploadDoc = Docs.Create.StackingDocument([], { - title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true - }); - return Docs.Create.StackingDocument([webDoc, uploadDoc], { - _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray" - }); - } - - // 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, sidebarContainer: Doc) { - // setup a masonry view of all he creators - const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - - 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", - })); - } - // 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(["dropAction", "forceActive"]) - }); - doc.myColorPicker = new PrefetchProxy(color); - } - - if (doc["tabs-button-tools"] === undefined) { - doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 35, _height: 25, title: "Tools", _fontSize: 10, - letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true - })) as any as Doc, - targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"), - })); - } - (doc["tabs-button-tools"] as Doc).sourcePanel; // prefetch sourcePanel - return doc["tabs-button-tools"] as Doc; - } - - static setupWorkspaces(doc: Doc) { - // setup workspaces library item - if (doc.myWorkspaces === undefined) { - doc.myWorkspaces = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, - })); - } - const newWorkspace = ScriptField.MakeScript(`createNewWorkspace()`); - (doc.myWorkspaces as Doc).contextMenuScripts = new List([newWorkspace!]); - (doc.myWorkspaces as Doc).contextMenuLabels = new List(["Create New Workspace"]); - - return doc.myWorkspaces as Doc; - } - static setupCatalog(doc: Doc) { - if (doc.myCatalog === undefined) { - doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true, - })); - } - return doc.myCatalog as Doc; - } - static setupRecentlyClosed(doc: Doc) { - // setup Recently Closed library item - if (doc.myRecentlyClosed === undefined) { - doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, - })); - } - // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready - PromiseValue(Cast(doc.myRecentlyClosed, Doc)).then(recent => recent && PromiseValue(recent.data).then(DocListCast)); - const clearAll = ScriptField.MakeScript(`self.data = new List([])`); - (doc.myRecentlyClosed as Doc).contextMenuScripts = new List([clearAll!]); - (doc.myRecentlyClosed as Doc).contextMenuLabels = new List(["Clear All"]); - - return doc.myRecentlyClosed as Doc; - } - // setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views - static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) { - const workspaces = CurrentUserUtils.setupWorkspaces(doc); - const documents = CurrentUserUtils.setupCatalog(doc); - const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc); - - if (doc["tabs-button-library"] === undefined) { - doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 50, _height: 25, title: "Library", _fontSize: 10, - letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], { - title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true - })) as any as Doc, - targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") - })); - } - return doc["tabs-button-library"] as Doc; - } - - // setup the Search button which will display the search panel. - static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) { - if (doc["tabs-button-search"] === undefined) { - doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({ - _width: 50, _height: 25, title: "Search", _fontSize: 10, - letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", - sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc, - searchFileTypes: new List([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]), - targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, - lockedPosition: true, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel") - })); - } - return doc["tabs-button-search"] as Doc; - } - - static setupSidebarContainer(doc: Doc) { - if (doc["tabs-panelContainer"] === undefined) { - const sidebarContainer = new Doc(); - sidebarContainer._chromeStatus = "disabled"; - sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()"); - doc["tabs-panelContainer"] = new PrefetchProxy(sidebarContainer); - } - return doc["tabs-panelContainer"] as Doc; - } - - // setup the list of sidebar mode buttons which determine what is displayed in the sidebar - static async setupSidebarButtons(doc: Doc) { - const sidebarContainer = CurrentUserUtils.setupSidebarContainer(doc); - const toolsBtn = await CurrentUserUtils.setupToolsBtnPanel(doc, sidebarContainer); - const libraryBtn = CurrentUserUtils.setupLibraryPanel(doc, sidebarContainer); - const searchBtn = CurrentUserUtils.setupSearchBtnPanel(doc, sidebarContainer); - - // Finally, setup the list of buttons to display in the sidebar - if (doc["tabs-buttons"] === undefined) { - doc["tabs-buttons"] = new PrefetchProxy(Docs.Create.StackingDocument([searchBtn, libraryBtn, toolsBtn], { - _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", hideHeadings: true, ignoreClick: true, _chromeStatus: "view-mode", - title: "sidebar btn row stack", backgroundColor: "dimGray", - })); - (toolsBtn.onClick as ScriptField).script.run({ this: toolsBtn }); - } - } - - 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 - })) as any as Doc - - static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, - dropAction: "alias", removeDropProperties: new List(["dropAction"]), _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100 - })) 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-pen"] === undefined) { - doc["dockedBtn-pen"] = CurrentUserUtils.ficon({ - onClick: ScriptField.MakeScript("activatePen(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this,2, this.backgroundColor)"), - author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activePen.inkPen, this)`), activePen: doc - }); - } - if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); - } - if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); - } - if (doc.dockedBtns === undefined) { - doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc, doc["dockedBtn-pen"] as Doc]); - } - } - // 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" })); - } - } - - // the initial presentation Doc to use - static setupDefaultPresentation(doc: Doc) { - if (doc.activePresentation === undefined) { - doc.activePresentation = Docs.Create.PresDocument(new List(), { - title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", - _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" - }); - } - } - - static setupRightSidebar(doc: Doc) { - if (doc.rightSidebarCollection === undefined) { - doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Right Sidebar" })); - } - } - - static setupClickEditorTemplates(doc: Doc) { - if (doc["clickFuncs-child"] === undefined) { - const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "docCast(thisContainer.target).then((target) => {" + - " target && docCast(this.source).then((source) => { " + - " target.proto.data = new List([source || this]); " + - " }); " + - "})", - { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" }); - - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "openOnRight(self.doubleClickView)", - { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" }); - - doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); - } - // 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 - }, "onClick"); - const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", - isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200 - }, "onDoubleClick"); - 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 - }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" }); - } - PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); - - return doc.clickFuncs as Doc; - } - - static async updateUserDocument(doc: Doc) { - new InkingControl(); - doc.title = Doc.CurrentUserEmail; - doc.activePen = doc; - doc.inkColor = StrCast(doc.backgroundColor, ""); - doc.fontSize = NumCast(doc.fontSize, 12); - 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.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing - 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(); - - // 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 }); - - return doc; - } - public static async loadCurrentUser() { - return rp.get(Utils.prepend("/getCurrentUser")).then(response => { - if (response) { - const result: { id: string, email: string } = JSON.parse(response); - return result; - } else { - throw new Error("There should be a user! Why does Dash think there isn't one?"); - } - }); - } - - public static async loadUserDocument({ id, email }: { id: string, email: string }) { - this.curr_id = id; - Doc.CurrentUserEmail = email; - await rp.get(Utils.prepend("/getUserDocumentId")).then(id => { - if (id && id !== "guest") { - return DocServer.GetRefField(id).then(async field => - Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true)))); - } else { - throw new Error("There should be a user id! Why does Dash think there isn't one?"); - } - }); - } -} - -Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); }); -Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); }); -Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); }); \ No newline at end of file diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts deleted file mode 100644 index a0b688328..000000000 --- a/src/server/authentication/models/user_model.ts +++ /dev/null @@ -1,86 +0,0 @@ -//@ts-ignore -import * as bcrypt from "bcrypt-nodejs"; -//@ts-ignore -import * as mongoose from 'mongoose'; - -export type DashUserModel = mongoose.Document & { - email: String, - password: string, - passwordResetToken?: string, - passwordResetExpires?: Date, - - userDocumentId: string; - - profile: { - name: string, - gender: string, - location: string, - website: string, - picture: string - }, - - comparePassword: comparePasswordFunction, -}; - -type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; - -export type AuthToken = { - accessToken: string, - kind: string -}; - -const userSchema = new mongoose.Schema({ - email: String, - password: String, - passwordResetToken: String, - passwordResetExpires: Date, - - userDocumentId: String, - - facebook: String, - twitter: String, - google: String, - - profile: { - name: String, - gender: String, - location: String, - website: String, - picture: String - } -}, { timestamps: true }); - -/** - * Password hash middleware. - */ -userSchema.pre("save", function save(next) { - const user = this as DashUserModel; - if (!user.isModified("password")) { - return next(); - } - bcrypt.genSalt(10, (err, salt) => { - if (err) { - return next(err); - } - bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => { - if (err) { - return next(err); - } - user.password = hash; - next(); - }); - }); -}); - -const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) { - // Choose one of the following bodies for authentication logic. - // secure (expected, default) - bcrypt.compare(candidatePassword, this.password, cb); - // bypass password (debugging) - // cb(undefined, true); -}; - -userSchema.methods.comparePassword = comparePassword; - -const User = mongoose.model("User", userSchema); -export default User; \ No newline at end of file diff --git a/src/server/credentials/CredentialsLoader.ts b/src/server/credentials/CredentialsLoader.ts deleted file mode 100644 index e3f4d167b..000000000 --- a/src/server/credentials/CredentialsLoader.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { readFile } from "fs"; - -export namespace GoogleCredentialsLoader { - - export interface InstalledCredentials { - client_id: string; - project_id: string; - auth_uri: string; - token_uri: string; - auth_provider_x509_cert_url: string; - client_secret: string; - redirect_uris: string[]; - } - - export let ProjectCredentials: InstalledCredentials; - - export async function loadCredentials() { - ProjectCredentials = await new Promise(resolve => { - readFile(__dirname + '/google_project_credentials.json', function processClientSecrets(err, content) { - if (err) { - console.log('Error loading client secret file: ' + err); - return; - } - resolve(JSON.parse(content.toString()).installed); - }); - }); - } - -} diff --git a/src/server/credentials/google_project_credentials.json b/src/server/credentials/google_project_credentials.json deleted file mode 100644 index 955c5a3c1..000000000 --- a/src/server/credentials/google_project_credentials.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "installed": { - "client_id": "343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com", - "project_id": "quickstart-1565056383187", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_secret": "w8KIFSc0MQpmUYHed4qEzn8b", - "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"] - } -} \ No newline at end of file diff --git a/src/server/database.ts b/src/server/database.ts index 580f7f919..ed9a246e3 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -293,13 +293,26 @@ export namespace Database { export const Instance = getDatabase(); + /** + * Provides definitions and apis for working with + * portions of the database not dedicated to storing documents + * or Dash-internal user data. + */ export namespace Auxiliary { + /** + * All the auxiliary MongoDB collections (schemas) + */ export enum AuxiliaryCollections { GooglePhotosUploadHistory = "uploadedFromGooglePhotos", - GoogleAuthentication = "googleAuthentication" + GoogleAccess = "googleAuthentication" } + /** + * Searches for the @param query in the specified @param collection, + * and returns at most the first @param cap results. If @param removeId is true, + * as it is by default, each object will be stripped of its database id. + */ const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => { const cursor = await Instance.query(query, undefined, collection); const results = await cursor.toArray(); @@ -310,52 +323,89 @@ export namespace Database { }) : slice; }; + /** + * Searches for the @param query in the specified @param collection, + * and returns at most the first result. If @param removeId is true, + * as it is by default, each object will be stripped of its database id. + * Worth the special case since it converts the Array return type to a single + * object of the specified type. + */ const SanitizedSingletonQuery = async (query: { [key: string]: any }, collection: string, removeId = true): Promise> => { const results = await SanitizedCappedQuery(query, collection, 1, removeId); return results.length ? results[0] : undefined; }; + /** + * Checks to see if an image with the given @param contentSize + * already exists in the aux database, i.e. has already been downloaded from Google Photos. + */ export const QueryUploadHistory = async (contentSize: number) => { return SanitizedSingletonQuery({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory); }; - export namespace GoogleAuthenticationToken { + /** + * Records the uploading of the image with the given @param information, + * using the given content size as a seed for the database id. + */ + export const LogUpload = async (information: Upload.ImageInformation) => { + const bundle = { + _id: Utils.GenerateDeterministicGuid(String(information.contentSize)), + ...information + }; + return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); + }; + + /** + * Manages the storage, retrieval and updating of the access token that + * facilitates interactions with all their APIs for a given account. + */ + export namespace GoogleAccessToken { + /** + * Format stored in database. + */ type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string }; + /** + * Retrieves the credentials associaed with @param userId + * and optionally removes their database id according to @param removeId. + */ export const Fetch = async (userId: string, removeId = true): Promise> => { - return SanitizedSingletonQuery({ userId }, AuxiliaryCollections.GoogleAuthentication, removeId); + return SanitizedSingletonQuery({ userId }, AuxiliaryCollections.GoogleAccess, removeId); }; + /** + * Writes the @param enrichedCredentials to the database, associated + * with @param userId for later retrieval and updating. + */ export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => { - return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAuthentication); + return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess); }; + /** + * Updates the @param access_token and @param expiry_date fields + * in the stored credentials associated with @param userId. + */ export const Update = async (userId: string, access_token: string, expiry_date: number) => { const entry = await Fetch(userId, false); if (entry) { const parameters = { $set: { access_token, expiry_date } }; - return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAuthentication); + return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAccess); } }; + /** + * Revokes the credentials associated with @param userId. + */ export const Revoke = async (userId: string) => { const entry = await Fetch(userId, false); if (entry) { - Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAuthentication); + Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess); } }; } - export const LogUpload = async (information: Upload.ImageInformation) => { - const bundle = { - _id: Utils.GenerateDeterministicGuid(String(information.contentSize)), - ...information - }; - return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory); - }; - } } diff --git a/src/server/index.ts b/src/server/index.ts index f26c8a6ab..af8f95a5e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -11,9 +11,9 @@ import * as qs from 'query-string'; import UtilManager from './ApiManagers/UtilManager'; import { SearchManager } from './ApiManagers/SearchManager'; import UserManager from './ApiManagers/UserManager'; -import { WebSocket } from './Websocket/Websocket'; +import { WebSocket } from './websocket'; import DownloadManager from './ApiManagers/DownloadManager'; -import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; +import { GoogleCredentialsLoader } from './apis/google/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; @@ -25,7 +25,6 @@ import { yellow } from "colors"; import { DashSessionAgent } from "./DashSession/DashSessionAgent"; import SessionManager from "./ApiManagers/SessionManager"; import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent"; -import { Utils } from "../Utils"; export const onWindows = process.platform === "win32"; export let sessionAgent: AppliedSessionAgent; @@ -125,7 +124,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: // initialize the web socket (bidirectional communication: if a user changes // a field on one client, that change must be broadcast to all other clients) - WebSocket.start(isRelease); + WebSocket.initialize(isRelease); } diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index add607761..4b3094616 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -7,7 +7,7 @@ import * as cookieParser from 'cookie-parser'; import expressFlash = require('express-flash'); import flash = require('connect-flash'); import { Database } from './database'; -import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller'; +import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager'; const MongoStore = require('connect-mongo')(session); import RouteManager from './RouteManager'; import * as webpack from 'webpack'; @@ -94,6 +94,8 @@ function determineEnvironment() { const label = isRelease ? "release" : "development"; console.log(`\nrunning server in ${color(label)} mode`); + // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE + // on the client side, thanks to dotenv in webpack.config.js let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8"); clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8"); diff --git a/src/server/websocket.ts b/src/server/websocket.ts new file mode 100644 index 000000000..7278bdc32 --- /dev/null +++ b/src/server/websocket.ts @@ -0,0 +1,297 @@ +import { Utils } from "../Utils"; +import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent, RoomMessage } from "./Message"; +import { Client } from "./Client"; +import { Socket } from "socket.io"; +import { Database } from "./database"; +import { Search } from "./Search"; +import * as io from 'socket.io'; +import YoutubeApi from "./apis/youtube/youtubeApiSample"; +import { GoogleCredentialsLoader } from "./apis/google/CredentialsLoader"; +import { logPort } from "./ActionUtilities"; +import { timeMap } from "./ApiManagers/UserManager"; +import { green } from "colors"; +import { networkInterfaces } from "os"; +import executeImport from "../scraping/buxton/final/BuxtonImporter"; +import { DocumentsCollection } from "./IDatabase"; + +export namespace WebSocket { + + export let _socket: Socket; + const clients: { [key: string]: Client } = {}; + export const socketMap = new Map(); + export let disconnect: Function; + + export function initialize(isRelease: boolean) { + const endpoint = io(); + endpoint.on("connection", function (socket: Socket) { + _socket = socket; + + socket.use((_packet, next) => { + const userEmail = socketMap.get(socket); + if (userEmail) { + timeMap[userEmail] = Date.now(); + } + next(); + }); + + // convenience function to log server messages on the client + function log(message?: any, ...optionalParams: any[]) { + socket.emit('log', ['Message from server:', message, ...optionalParams]); + } + + socket.on('message', function (message, room) { + console.log('Client said: ', message); + socket.in(room).emit('message', message); + }); + + socket.on('create or join', function (room) { + console.log('Received request to create or join room ' + room); + + const clientsInRoom = socket.adapter.rooms[room]; + const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; + console.log('Room ' + room + ' now has ' + numClients + ' client(s)'); + + if (numClients === 0) { + socket.join(room); + console.log('Client ID ' + socket.id + ' created room ' + room); + socket.emit('created', room, socket.id); + + } else if (numClients === 1) { + console.log('Client ID ' + socket.id + ' joined room ' + room); + socket.in(room).emit('join', room); + socket.join(room); + socket.emit('joined', room, socket.id); + socket.in(room).emit('ready'); + } else { // max two clients + socket.emit('full', room); + } + }); + + socket.on('ipaddr', function () { + const ifaces = networkInterfaces(); + for (const dev in ifaces) { + ifaces[dev].forEach(function (details) { + if (details.family === 'IPv4' && details.address !== '127.0.0.1') { + socket.emit('ipaddr', details.address); + } + }); + } + }); + + socket.on('bye', function () { + console.log('received bye'); + }); + + Utils.Emit(socket, MessageStore.Foo, "handshooken"); + + Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid)); + Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); + Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); + Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); + if (isRelease) { + Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false)); + } + + Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField); + Utils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiQuery, HandleYoutubeQuery); + Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); + Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id)); + Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); + Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); + Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content)); + Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content)); + Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content)); + Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); + Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); + + /** + * Whenever we receive the go-ahead, invoke the import script and pass in + * as an emitter and a terminator the functions that simply broadcast a result + * or indicate termination to the client via the web socket + */ + Utils.AddServerHandler(socket, MessageStore.BeginBuxtonImport, () => { + executeImport( + deviceOrError => Utils.Emit(socket, MessageStore.BuxtonDocumentResult, deviceOrError), + results => Utils.Emit(socket, MessageStore.BuxtonImportComplete, results) + ); + }); + + disconnect = () => { + socket.broadcast.emit("connection_terminated", Date.now()); + socket.disconnect(true); + }; + }); + + const socketPort = isRelease ? Number(process.env.socketPort) : 4321; + endpoint.listen(socketPort); + logPort("websocket", socketPort); + } + + function processGesturePoints(socket: Socket, content: GestureContent) { + socket.broadcast.emit("receiveGesturePoints", content); + } + + function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) { + socket.broadcast.emit("receiveOverlayTrigger", content); + } + + function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) { + socket.broadcast.emit("receiveUpdateOverlayPosition", content); + } + + function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) { + socket.broadcast.emit("receiveMobileDocumentUpload", content); + } + + function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { + const { ProjectCredentials } = GoogleCredentialsLoader; + switch (query.type) { + case YoutubeQueryTypes.Channels: + YoutubeApi.authorizedGetChannel(ProjectCredentials); + break; + case YoutubeQueryTypes.SearchVideo: + YoutubeApi.authorizedGetVideos(ProjectCredentials, query.userInput, callback); + case YoutubeQueryTypes.VideoDetails: + YoutubeApi.authorizedGetVideoDetails(ProjectCredentials, query.videoIds, callback); + } + } + + export async function doDelete(onlyFields = true) { + const target: string[] = []; + onlyFields && target.push(DocumentsCollection); + await Database.Instance.dropSchema(...target); + if (process.env.DISABLE_SEARCH !== "true") { + await Search.clear(); + } + } + + function barReceived(socket: SocketIO.Socket, userEmail: string) { + clients[userEmail] = new Client(userEmail.toString()); + console.log(green(`user ${userEmail} has connected to the web socket`)); + socketMap.set(socket, userEmail); + } + + function getField([id, callback]: [string, (result?: Transferable) => void]) { + Database.Instance.getDocument(id, (result?: Transferable) => + callback(result ? result : undefined)); + } + + function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) { + Database.Instance.getDocuments(ids, callback); + } + + function setField(socket: Socket, newValue: Transferable) { + 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 }); + } + } + + function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { + Database.Instance.getDocument(id, callback); + } + + function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { + Database.Instance.getDocuments(ids, callback); + } + + const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { + "number": "_n", + "string": "_t", + "boolean": "_b", + "image": ["_t", "url"], + "video": ["_t", "url"], + "pdf": ["_t", "url"], + "audio": ["_t", "url"], + "web": ["_t", "url"], + "script": ["_t", value => value.script.originalScript], + "RichTextField": ["_t", value => value.Text], + "date": ["_d", value => new Date(value.date).toISOString()], + "proxy": ["_i", "fieldId"], + "list": ["_l", list => { + const results = []; + for (const value of list.fields) { + const term = ToSearchTerm(value); + if (term) { + results.push(term.value); + } + } + return results.length ? results : null; + }] + }; + + function ToSearchTerm(val: any): { suffix: string, value: any } | undefined { + if (val === null || val === undefined) { + return; + } + const type = val.__type || typeof val; + let suffix = suffixMap[type]; + if (!suffix) { + return; + } + + if (Array.isArray(suffix)) { + const accessor = suffix[1]; + if (typeof accessor === "function") { + val = accessor(val); + } else { + val = val[accessor]; + } + suffix = suffix[0]; + } + + return { suffix, value: val }; + } + + function getSuffix(value: string | [string, any]): string { + return typeof value === "string" ? value : value[0]; + } + + function UpdateField(socket: Socket, diff: Diff) { + Database.Instance.update(diff.id, diff.diff, + () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); + const docfield = diff.diff.$set || diff.diff.$unset; + if (!docfield) { + return; + } + const update: any = { id: diff.id }; + let dynfield = false; + for (let key in docfield) { + if (!key.startsWith("fields.")) continue; + dynfield = true; + const val = docfield[key]; + key = key.substring(7); + Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null }); + const term = ToSearchTerm(val); + if (term !== undefined) { + const { suffix, value } = term; + update[key + suffix] = { set: value }; + } + } + if (dynfield) { + Search.updateDocument(update); + } + } + + function DeleteField(socket: Socket, id: string) { + Database.Instance.delete({ _id: id }).then(() => { + socket.broadcast.emit(MessageStore.DeleteField.Message, id); + }); + + Search.deleteDocuments([id]); + } + + function DeleteFields(socket: Socket, ids: string[]) { + Database.Instance.delete({ _id: { $in: ids } }).then(() => { + socket.broadcast.emit(MessageStore.DeleteFields.Message, ids); + }); + Search.deleteDocuments(ids); + } + + function CreateField(newValue: any) { + Database.Instance.insert(newValue); + } + +} + diff --git a/webpack.config.js b/webpack.config.js index 6265883fd..67d492e1f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,22 +16,21 @@ const plugins = [ new webpack.HotModuleReplacementPlugin(), ]; -const dotenv = require('dotenv'); - -function transferEnvironmentVariables() { +(function transferEnvironmentVariables() { const prefix = "_CLIENT_"; - const env = dotenv.config().parsed; - if (env) { - plugins.push(new webpack.DefinePlugin(Object.keys(env).reduce((mapping, envKey) => { - if (envKey.startsWith(prefix)) { - mapping[`process.env.${envKey.replace(prefix, "")}`] = JSON.stringify(env[envKey]); - } - return mapping; - }, {}))); + const { + parsed + } = require('dotenv').config(); + if (!parsed) { + return; } -} - -transferEnvironmentVariables(); + plugins.push(new webpack.DefinePlugin(Object.keys(parsed).reduce((mapping, envKey) => { + if (envKey.startsWith(prefix)) { + mapping[`process.env.${envKey.replace(prefix, "")}`] = JSON.stringify(parsed[envKey]); + } + return mapping; + }, {}))); +})(); module.exports = { mode: 'development', -- cgit v1.2.3-70-g09d2 From 2b79008596351f6948d8de80c7887446d97b068c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 15 May 2020 00:03:41 -0700 Subject: renamed new_fields to fields --- src/client/ClientRecommender.tsx | 16 +- src/client/DocServer.ts | 6 +- src/client/apis/GoogleAuthenticationManager.tsx | 2 +- src/client/apis/IBM_Recommender.ts | 2 +- .../apis/google_docs/GoogleApiClientUtils.ts | 2 +- .../apis/google_docs/GooglePhotosClientUtils.ts | 12 +- src/client/apis/youtube/YoutubeBox.tsx | 4 +- src/client/cognitive_services/CognitiveServices.ts | 8 +- src/client/documents/Documents.ts | 22 +- src/client/util/CurrentUserUtils.ts | 18 +- src/client/util/DictationManager.ts | 12 +- src/client/util/DocumentManager.ts | 6 +- src/client/util/DragManager.ts | 16 +- src/client/util/DropConverter.ts | 12 +- src/client/util/History.ts | 2 +- .../util/Import & Export/DirectoryImportBox.tsx | 12 +- src/client/util/Import & Export/ImageUtils.ts | 8 +- .../util/Import & Export/ImportMetadataEntry.tsx | 4 +- src/client/util/LinkManager.ts | 8 +- src/client/util/Scripting.ts | 2 +- src/client/util/SearchUtil.ts | 4 +- src/client/util/SelectionManager.ts | 4 +- src/client/util/SerializationHelper.ts | 2 +- src/client/util/SharingManager.tsx | 6 +- src/client/views/DocComponent.tsx | 14 +- src/client/views/DocumentButtonBar.tsx | 6 +- src/client/views/DocumentDecorations.tsx | 24 +- src/client/views/EditableView.tsx | 4 +- src/client/views/GestureOverlay.tsx | 12 +- src/client/views/GlobalKeyHandler.ts | 14 +- src/client/views/InkingControl.tsx | 6 +- src/client/views/InkingStroke.tsx | 10 +- src/client/views/MainView.tsx | 14 +- src/client/views/MainViewNotifs.tsx | 2 +- src/client/views/MetadataEntryMenu.tsx | 2 +- src/client/views/OverlayView.tsx | 6 +- src/client/views/Palette.tsx | 4 +- src/client/views/PreviewCursor.tsx | 4 +- src/client/views/RecommendationsBox.tsx | 8 +- src/client/views/ScriptBox.tsx | 6 +- src/client/views/SearchDocBox.tsx | 6 +- src/client/views/TemplateMenu.tsx | 8 +- src/client/views/animationtimeline/Keyframe.tsx | 8 +- src/client/views/animationtimeline/Timeline.tsx | 8 +- src/client/views/animationtimeline/Track.tsx | 12 +- .../views/collections/CollectionCarouselView.tsx | 10 +- .../views/collections/CollectionDockingView.tsx | 29 +- .../views/collections/CollectionLinearView.tsx | 10 +- src/client/views/collections/CollectionMapView.tsx | 10 +- .../collections/CollectionMasonryViewFieldRow.tsx | 8 +- .../views/collections/CollectionPileView.tsx | 6 +- .../views/collections/CollectionSchemaCells.tsx | 8 +- .../views/collections/CollectionSchemaHeaders.tsx | 2 +- .../CollectionSchemaMovableTableHOC.tsx | 6 +- .../views/collections/CollectionSchemaView.tsx | 14 +- .../views/collections/CollectionStackingView.tsx | 16 +- .../CollectionStackingViewFieldColumn.tsx | 16 +- .../views/collections/CollectionStaffView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 14 +- .../views/collections/CollectionTimeView.tsx | 12 +- .../views/collections/CollectionTreeView.tsx | 16 +- src/client/views/collections/CollectionView.tsx | 20 +- .../views/collections/CollectionViewChromes.tsx | 10 +- .../views/collections/ParentDocumentSelector.tsx | 6 +- .../CollectionFreeFormLayoutEngines.tsx | 14 +- .../CollectionFreeFormLinkView.tsx | 6 +- .../CollectionFreeFormLinksView.tsx | 4 +- .../CollectionFreeFormRemoteCursors.tsx | 10 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 23 +- .../collections/collectionFreeForm/MarqueeView.tsx | 12 +- .../CollectionMulticolumnView.tsx | 10 +- .../CollectionMultirowView.tsx | 10 +- .../collectionMulticolumn/MulticolumnResizer.tsx | 4 +- .../MulticolumnWidthLabel.tsx | 4 +- .../collectionMulticolumn/MultirowHeightLabel.tsx | 4 +- .../collectionMulticolumn/MultirowResizer.tsx | 4 +- src/client/views/linking/LinkEditor.tsx | 4 +- src/client/views/linking/LinkMenu.tsx | 2 +- src/client/views/linking/LinkMenuGroup.tsx | 6 +- src/client/views/linking/LinkMenuItem.tsx | 4 +- src/client/views/nodes/AudioBox.tsx | 16 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 8 +- src/client/views/nodes/ColorBox.tsx | 6 +- .../views/nodes/ContentFittingDocumentView.tsx | 6 +- src/client/views/nodes/DocHolderBox.tsx | 12 +- src/client/views/nodes/DocumentContentsView.tsx | 8 +- src/client/views/nodes/DocumentIcon.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 20 +- src/client/views/nodes/FaceRectangles.tsx | 6 +- src/client/views/nodes/FieldView.tsx | 10 +- src/client/views/nodes/FontIconBox.tsx | 8 +- src/client/views/nodes/ImageBox.tsx | 20 +- src/client/views/nodes/KeyValueBox.tsx | 14 +- src/client/views/nodes/KeyValuePair.tsx | 2 +- src/client/views/nodes/LabelBox.tsx | 10 +- src/client/views/nodes/LinkAnchorBox.tsx | 10 +- src/client/views/nodes/LinkBox.tsx | 6 +- src/client/views/nodes/PDFBox.tsx | 12 +- src/client/views/nodes/PresBox.tsx | 16 +- src/client/views/nodes/QueryBox.tsx | 10 +- src/client/views/nodes/ScreenshotBox.tsx | 8 +- src/client/views/nodes/ScriptingBox.tsx | 10 +- src/client/views/nodes/SliderBox.tsx | 8 +- src/client/views/nodes/VideoBox.tsx | 16 +- src/client/views/nodes/WebBox.tsx | 14 +- .../nodes/formattedText/DashDocCommentView.tsx | 16 +- .../views/nodes/formattedText/DashDocView.tsx | 10 +- .../views/nodes/formattedText/DashFieldView.tsx | 12 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 24 +- .../formattedText/FormattedTextBoxComment.tsx | 4 +- .../views/nodes/formattedText/ImageResizeView.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 6 +- .../views/nodes/formattedText/RichTextMenu.tsx | 6 +- .../views/nodes/formattedText/RichTextRules.ts | 10 +- .../views/nodes/formattedText/RichTextSchema.tsx | 16 +- src/client/views/nodes/formattedText/marks_rts.ts | 2 +- src/client/views/pdf/Annotation.tsx | 8 +- src/client/views/pdf/PDFMenu.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 20 +- .../views/presentationview/PresElementBox.tsx | 10 +- src/client/views/search/FilterBox.tsx | 6 +- src/client/views/search/SearchBox.tsx | 8 +- src/client/views/search/SearchItem.tsx | 6 +- src/debug/Repl.tsx | 6 +- src/debug/Test.tsx | 2 +- src/debug/Viewer.tsx | 16 +- src/fields/CursorField.ts | 66 ++ src/fields/DateField.ts | 36 + src/fields/Doc.ts | 1109 ++++++++++++++++++++ src/fields/FieldSymbols.ts | 12 + src/fields/HtmlField.ts | 26 + src/fields/IconField.ts | 26 + src/fields/InkField.ts | 50 + src/fields/List.ts | 330 ++++++ src/fields/ListSpec.ts | 0 src/fields/ObjectField.ts | 20 + src/fields/PresField.ts | 6 + src/fields/Proxy.ts | 126 +++ src/fields/RefField.ts | 21 + src/fields/RichTextField.ts | 41 + src/fields/RichTextUtils.ts | 519 +++++++++ src/fields/Schema.ts | 120 +++ src/fields/SchemaHeaderField.ts | 122 +++ src/fields/ScriptField.ts | 193 ++++ src/fields/Types.ts | 108 ++ src/fields/URLField.ts | 53 + src/fields/documentSchemas.ts | 100 ++ src/fields/util.ts | 199 ++++ src/mobile/ImageUpload.tsx | 8 +- src/mobile/MobileInkOverlay.tsx | 6 +- src/mobile/MobileInterface.tsx | 12 +- src/new_fields/CursorField.ts | 66 -- src/new_fields/DateField.ts | 36 - src/new_fields/Doc.ts | 1109 -------------------- src/new_fields/FieldSymbols.ts | 12 - src/new_fields/HtmlField.ts | 26 - src/new_fields/IconField.ts | 26 - src/new_fields/InkField.ts | 50 - src/new_fields/List.ts | 330 ------ src/new_fields/ListSpec.ts | 0 src/new_fields/ObjectField.ts | 20 - src/new_fields/PresField.ts | 6 - src/new_fields/Proxy.ts | 126 --- src/new_fields/RefField.ts | 21 - src/new_fields/RichTextField.ts | 41 - src/new_fields/RichTextUtils.ts | 519 --------- src/new_fields/Schema.ts | 120 --- src/new_fields/SchemaHeaderField.ts | 122 --- src/new_fields/ScriptField.ts | 193 ---- src/new_fields/Types.ts | 108 -- src/new_fields/URLField.ts | 53 - src/new_fields/documentSchemas.ts | 100 -- src/new_fields/util.ts | 199 ---- src/pen-gestures/GestureUtils.ts | 6 +- src/server/ApiManagers/GooglePhotosManager.ts | 2 +- src/server/ApiManagers/UserManager.ts | 2 +- src/server/DashUploadUtils.ts | 2 +- src/server/Message.ts | 2 +- src/server/Recommender.ts | 6 +- src/server/apis/google/GoogleApiServerUtils.ts | 2 +- src/server/database.ts | 2 +- test/test.ts | 8 +- 182 files changed, 3889 insertions(+), 3889 deletions(-) create mode 100644 src/fields/CursorField.ts create mode 100644 src/fields/DateField.ts create mode 100644 src/fields/Doc.ts create mode 100644 src/fields/FieldSymbols.ts create mode 100644 src/fields/HtmlField.ts create mode 100644 src/fields/IconField.ts create mode 100644 src/fields/InkField.ts create mode 100644 src/fields/List.ts create mode 100644 src/fields/ListSpec.ts create mode 100644 src/fields/ObjectField.ts create mode 100644 src/fields/PresField.ts create mode 100644 src/fields/Proxy.ts create mode 100644 src/fields/RefField.ts create mode 100644 src/fields/RichTextField.ts create mode 100644 src/fields/RichTextUtils.ts create mode 100644 src/fields/Schema.ts create mode 100644 src/fields/SchemaHeaderField.ts create mode 100644 src/fields/ScriptField.ts create mode 100644 src/fields/Types.ts create mode 100644 src/fields/URLField.ts create mode 100644 src/fields/documentSchemas.ts create mode 100644 src/fields/util.ts delete mode 100644 src/new_fields/CursorField.ts delete mode 100644 src/new_fields/DateField.ts delete mode 100644 src/new_fields/Doc.ts delete mode 100644 src/new_fields/FieldSymbols.ts delete mode 100644 src/new_fields/HtmlField.ts delete mode 100644 src/new_fields/IconField.ts delete mode 100644 src/new_fields/InkField.ts delete mode 100644 src/new_fields/List.ts delete mode 100644 src/new_fields/ListSpec.ts delete mode 100644 src/new_fields/ObjectField.ts delete mode 100644 src/new_fields/PresField.ts delete mode 100644 src/new_fields/Proxy.ts delete mode 100644 src/new_fields/RefField.ts delete mode 100644 src/new_fields/RichTextField.ts delete mode 100644 src/new_fields/RichTextUtils.ts delete mode 100644 src/new_fields/Schema.ts delete mode 100644 src/new_fields/SchemaHeaderField.ts delete mode 100644 src/new_fields/ScriptField.ts delete mode 100644 src/new_fields/Types.ts delete mode 100644 src/new_fields/URLField.ts delete mode 100644 src/new_fields/documentSchemas.ts delete mode 100644 src/new_fields/util.ts (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 537e331ab..d18669b02 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -1,6 +1,6 @@ -import { Doc, FieldResult } from "../new_fields/Doc"; -import { StrCast, Cast } from "../new_fields/Types"; -import { List } from "../new_fields/List"; +import { Doc, FieldResult } from "../fields/Doc"; +import { StrCast, Cast } from "../fields/Types"; +import { List } from "../fields/List"; import { CognitiveServices, Confidence, Tag, Service } from "./cognitive_services/CognitiveServices"; import React = require("react"); import { observer } from "mobx-react"; @@ -11,11 +11,11 @@ import { observable, action, computed, reaction } from "mobx"; // var https = require('https'); import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; -import { RichTextField } from "../new_fields/RichTextField"; -import { ToPlainText } from "../new_fields/FieldSymbols"; -import { listSpec } from "../new_fields/Schema"; -import { ComputedField } from "../new_fields/ScriptField"; -import { ImageField } from "../new_fields/URLField"; +import { RichTextField } from "../fields/RichTextField"; +import { ToPlainText } from "../fields/FieldSymbols"; +import { listSpec } from "../fields/Schema"; +import { ComputedField } from "../fields/ScriptField"; +import { ImageField } from "../fields/URLField"; import { KeyphraseQueryView } from "./views/KeyphraseQueryView"; import { Networking } from "./Network"; diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index ae125694b..ac5b7a218 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,10 +1,10 @@ import * as OpenSocket from 'socket.io-client'; import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; -import { Opt, Doc } from '../new_fields/Doc'; +import { Opt, Doc } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; -import { RefField } from '../new_fields/RefField'; -import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; +import { RefField } from '../fields/RefField'; +import { Id, HandleUpdate } from '../fields/FieldSymbols'; import GestureOverlay from './views/GestureOverlay'; import MobileInkOverlay from '../mobile/MobileInkOverlay'; import { runInAction } from 'mobx'; diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx index 22d6fb582..bf4469aeb 100644 --- a/src/client/apis/GoogleAuthenticationManager.tsx +++ b/src/client/apis/GoogleAuthenticationManager.tsx @@ -2,7 +2,7 @@ import { observable, action, reaction, runInAction, IReactionDisposer } from "mo import { observer } from "mobx-react"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Opt } from "../../new_fields/Doc"; +import { Opt } from "../../fields/Doc"; import { Networking } from "../Network"; import "./GoogleAuthenticationManager.scss"; import { Scripting } from "../util/Scripting"; diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts index 4e1c541c8..480b9cb1c 100644 --- a/src/client/apis/IBM_Recommender.ts +++ b/src/client/apis/IBM_Recommender.ts @@ -1,4 +1,4 @@ -// import { Opt } from "../../new_fields/Doc"; +// import { Opt } from "../../fields/Doc"; // const NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1'); // const { IamAuthenticator } = require('ibm-watson/auth'); diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 2f3cac8d3..551dca073 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,5 +1,5 @@ import { docs_v1 } from "googleapis"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt } from "../../../fields/Doc"; import { isArray } from "util"; import { EditorState } from "prosemirror-state"; import { Networking } from "../../Network"; diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 1e4c120bc..fef71ffeb 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -1,11 +1,11 @@ import { AssertionError } from "assert"; import { EditorState } from "prosemirror-state"; -import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { RichTextUtils } from "../../../new_fields/RichTextUtils"; -import { Cast, StrCast } from "../../../new_fields/Types"; -import { ImageField } from "../../../new_fields/URLField"; +import { Doc, DocListCastAsync, Opt } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { RichTextField } from "../../../fields/RichTextField"; +import { RichTextUtils } from "../../../fields/RichTextUtils"; +import { Cast, StrCast } from "../../../fields/Types"; +import { ImageField } from "../../../fields/URLField"; import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/SharedTypes"; import { Utils } from "../../../Utils"; import { Docs, DocumentOptions } from "../../documents/Documents"; diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 1575e53fc..ce7f49e64 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -1,7 +1,7 @@ import { action, observable, runInAction } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync } from "../../../new_fields/Doc"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc, DocListCastAsync } from "../../../fields/Doc"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs } from "../../documents/Documents"; diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 8c63ae906..d4df7ce57 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -1,12 +1,12 @@ import * as request from "request-promise"; -import { Doc, Field } from "../../new_fields/Doc"; -import { Cast } from "../../new_fields/Types"; +import { Doc, Field } from "../../fields/Doc"; +import { Cast } from "../../fields/Types"; import { Docs } from "../documents/Documents"; import { Utils } from "../../Utils"; -import { InkData } from "../../new_fields/InkField"; +import { InkData } from "../../fields/InkField"; import { UndoManager } from "../util/UndoManager"; import requestPromise = require("request-promise"); -import { List } from "../../new_fields/List"; +import { List } from "../../fields/List"; import { ClientRecommender } from "../ClientRecommender"; type APIManager = { converter: BodyConverter, requester: RequestExecutor }; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 125a531f4..13687e4e8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -9,14 +9,14 @@ import { ScriptingBox } from "../views/nodes/ScriptingBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; import { OmitKeys, JSONUtils, Utils } from "../../Utils"; -import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../new_fields/Doc"; -import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; -import { HtmlField } from "../../new_fields/HtmlField"; -import { List } from "../../new_fields/List"; -import { Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../fields/Doc"; +import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../fields/URLField"; +import { HtmlField } from "../../fields/HtmlField"; +import { List } from "../../fields/List"; +import { Cast, NumCast, StrCast } from "../../fields/Types"; import { DocServer } from "../DocServer"; import { dropActionType } from "../util/DragManager"; -import { DateField } from "../../new_fields/DateField"; +import { DateField } from "../../fields/DateField"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { LinkManager } from "../util/LinkManager"; @@ -26,10 +26,10 @@ import { Scripting } from "../util/Scripting"; import { LabelBox } from "../views/nodes/LabelBox"; import { SliderBox } from "../views/nodes/SliderBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { PresBox } from "../views/nodes/PresBox"; -import { ComputedField, ScriptField } from "../../new_fields/ScriptField"; -import { ProxyField } from "../../new_fields/Proxy"; +import { ComputedField, ScriptField } from "../../fields/ScriptField"; +import { ProxyField } from "../../fields/Proxy"; import { DocumentType } from "./DocumentTypes"; import { RecommendationsBox } from "../views/RecommendationsBox"; import { PresElementBox } from "../views/presentationview/PresElementBox"; @@ -39,9 +39,9 @@ import { ColorBox } from "../views/nodes/ColorBox"; import { LinkAnchorBox } from "../views/nodes/LinkAnchorBox"; import { DocHolderBox } from "../views/nodes/DocHolderBox"; import { InkingStroke } from "../views/InkingStroke"; -import { InkField } from "../../new_fields/InkField"; +import { InkField } from "../../fields/InkField"; import { InkingControl } from "../views/InkingControl"; -import { RichTextField } from "../../new_fields/RichTextField"; +import { RichTextField } from "../../fields/RichTextField"; import { extname } from "path"; import { MessageStore } from "../../server/Message"; import { ContextMenuProps } from "../views/ContextMenuItem"; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b67c3ea60..d9faed36d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -4,23 +4,23 @@ import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions } from "../documents/Documents"; import { UndoManager } from "./UndoManager"; -import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import { listSpec } from "../../new_fields/Schema"; -import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; -import { Cast, PromiseValue, StrCast, NumCast } from "../../new_fields/Types"; -import { nullAudio } from "../../new_fields/URLField"; +import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc"; +import { List } from "../../fields/List"; +import { listSpec } from "../../fields/Schema"; +import { ScriptField, ComputedField } from "../../fields/ScriptField"; +import { Cast, PromiseValue, StrCast, NumCast } from "../../fields/Types"; +import { nullAudio } from "../../fields/URLField"; import { DragManager } from "./DragManager"; import { InkingControl } from "../views/InkingControl"; import { Scripting } from "./Scripting"; import { CollectionViewType } from "../views/collections/CollectionView"; import { makeTemplate } from "./DropConverter"; -import { RichTextField } from "../../new_fields/RichTextField"; -import { PrefetchProxy } from "../../new_fields/Proxy"; +import { RichTextField } from "../../fields/RichTextField"; +import { PrefetchProxy } from "../../fields/Proxy"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { MainView } from "../views/MainView"; import { DocumentType } from "../documents/DocumentTypes"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; export class CurrentUserUtils { diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b3295ece0..e46225b4a 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -3,15 +3,15 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { UndoManager } from "./UndoManager"; import * as interpreter from "words-to-numbers"; import { DocumentType } from "../documents/DocumentTypes"; -import { Doc, Opt } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; +import { Doc, Opt } from "../../fields/Doc"; +import { List } from "../../fields/List"; import { Docs } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionView"; -import { Cast, CastCtor } from "../../new_fields/Types"; -import { listSpec } from "../../new_fields/Schema"; -import { AudioField, ImageField } from "../../new_fields/URLField"; +import { Cast, CastCtor } from "../../fields/Types"; +import { listSpec } from "../../fields/Schema"; +import { AudioField, ImageField } from "../../fields/URLField"; import { Utils } from "../../Utils"; -import { RichTextField } from "../../new_fields/RichTextField"; +import { RichTextField } from "../../fields/RichTextField"; import { DictationOverlay } from "../views/DictationOverlay"; /** diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 99d85125a..ab087335e 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,7 +1,7 @@ import { action, computed, observable } from 'mobx'; -import { Doc, DocListCastAsync, DocListCast, Opt } from '../../new_fields/Doc'; -import { Id } from '../../new_fields/FieldSymbols'; -import { Cast, NumCast, StrCast } from '../../new_fields/Types'; +import { Doc, DocListCastAsync, DocListCast, Opt } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; import { DocumentView, DocFocusFunc } from '../views/nodes/DocumentView'; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d1d7f2a8a..66c7ecf7d 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,12 +1,12 @@ import { action, observable, runInAction } from "mobx"; -import { DateField } from "../../new_fields/DateField"; -import { Doc, Field, Opt } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import { PrefetchProxy } from "../../new_fields/Proxy"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ScriptField } from "../../new_fields/ScriptField"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../new_fields/Types"; +import { DateField } from "../../fields/DateField"; +import { Doc, Field, Opt } from "../../fields/Doc"; +import { List } from "../../fields/List"; +import { PrefetchProxy } from "../../fields/Proxy"; +import { listSpec } from "../../fields/Schema"; +import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; +import { ScriptField } from "../../fields/ScriptField"; +import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; import { emptyFunction } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import * as globalCssVariables from "../views/globalCssVariables.scss"; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index d6db882b8..752c1cfc5 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,12 +1,12 @@ import { DragManager } from "./DragManager"; -import { Doc, DocListCast, Opt } from "../../new_fields/Doc"; +import { Doc, DocListCast, Opt } from "../../fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { StrCast } from "../../new_fields/Types"; +import { ObjectField } from "../../fields/ObjectField"; +import { StrCast } from "../../fields/Types"; import { Docs } from "../documents/Documents"; -import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; -import { RichTextField } from "../../new_fields/RichTextField"; -import { ImageField } from "../../new_fields/URLField"; +import { ScriptField, ComputedField } from "../../fields/ScriptField"; +import { RichTextField } from "../../fields/RichTextField"; +import { ImageField } from "../../fields/URLField"; import { Scripting } from "./Scripting"; // diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 2c53d7e52..7b7d4b835 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -1,4 +1,4 @@ -import { Doc } from "../../new_fields/Doc"; +import { Doc } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { MainView } from "../views/MainView"; import * as qs from 'query-string'; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 438904688..1e8f07049 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,6 +1,6 @@ import "fs"; import React = require("react"); -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; import { action, observable, runInAction, computed, reaction, IReactionDisposer } from "mobx"; import { FieldViewProps, FieldView } from "../../views/nodes/FieldView"; import Measure, { ContentRect } from "react-measure"; @@ -12,12 +12,12 @@ import { observer } from "mobx-react"; import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; import { Utils } from "../../../Utils"; import { DocumentManager } from "../DocumentManager"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { Cast, BoolCast, NumCast } from "../../../new_fields/Types"; -import { listSpec } from "../../../new_fields/Schema"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { Cast, BoolCast, NumCast } from "../../../fields/Types"; +import { listSpec } from "../../../fields/Schema"; import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import "./DirectoryImportBox.scss"; import { Networking } from "../../Network"; import { BatchedArray } from "array-batcher"; diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts index c8d1530b3..072e5f58a 100644 --- a/src/client/util/Import & Export/ImageUtils.ts +++ b/src/client/util/Import & Export/ImageUtils.ts @@ -1,9 +1,9 @@ -import { Doc } from "../../../new_fields/Doc"; -import { ImageField } from "../../../new_fields/URLField"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Doc } from "../../../fields/Doc"; +import { ImageField } from "../../../fields/URLField"; +import { Cast, StrCast } from "../../../fields/Types"; import { Docs } from "../../documents/Documents"; import { Networking } from "../../Network"; -import { Id } from "../../../new_fields/FieldSymbols"; +import { Id } from "../../../fields/FieldSymbols"; import { Utils } from "../../../Utils"; export namespace ImageUtils { diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx index 8e1c50bea..dcb94e2e0 100644 --- a/src/client/util/Import & Export/ImportMetadataEntry.tsx +++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx @@ -5,8 +5,8 @@ import { action, computed } from "mobx"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { library } from '@fortawesome/fontawesome-svg-core'; -import { Doc } from "../../../new_fields/Doc"; -import { StrCast, BoolCast } from "../../../new_fields/Types"; +import { Doc } from "../../../fields/Doc"; +import { StrCast, BoolCast } from "../../../fields/Types"; interface KeyValueProps { Document: Doc; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index e236c7f47..8e6ccf098 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,7 +1,7 @@ -import { Doc, DocListCast } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import { listSpec } from "../../new_fields/Schema"; -import { Cast, StrCast } from "../../new_fields/Types"; +import { Doc, DocListCast } from "../../fields/Doc"; +import { List } from "../../fields/List"; +import { listSpec } from "../../fields/Schema"; +import { Cast, StrCast } from "../../fields/Types"; import { Docs } from "../documents/Documents"; import { Scripting } from "./Scripting"; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 8b7b9c9c7..ab577315c 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -9,7 +9,7 @@ export { ts }; // @ts-ignore import * as typescriptlib from '!!raw-loader!./type_decls.d'; -import { Doc, Field } from '../../new_fields/Doc'; +import { Doc, Field } from '../../fields/Doc'; export interface ScriptSucccess { success: true; diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 6501da34a..5679c0a14 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -1,7 +1,7 @@ import * as rp from 'request-promise'; import { DocServer } from '../DocServer'; -import { Doc } from '../../new_fields/Doc'; -import { Id } from '../../new_fields/FieldSymbols'; +import { Doc } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; import { Utils } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 11d2cafb2..bd743c28e 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,8 +1,8 @@ import { observable, action, runInAction, ObservableMap } from "mobx"; -import { Doc } from "../../new_fields/Doc"; +import { Doc } from "../../fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { computedFn } from "mobx-utils"; -import { List } from "../../new_fields/List"; +import { List } from "../../fields/List"; export namespace SelectionManager { diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 1f6b939d3..ad0309fa7 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -1,5 +1,5 @@ import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from "serializr"; -import { Field } from "../../new_fields/Doc"; +import { Field } from "../../fields/Doc"; import { ClientUtils } from "./ClientUtils"; let serializing = 0; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 3ce6de80d..dc67145fc 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,13 +1,13 @@ import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, DocCastAsync } from "../../new_fields/Doc"; +import { Doc, Opt, DocCastAsync } from "../../fields/Doc"; import { DocServer } from "../DocServer"; -import { Cast, StrCast } from "../../new_fields/Types"; +import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; import { Utils } from "../../Utils"; import "./SharingManager.scss"; -import { Id } from "../../new_fields/FieldSymbols"; +import { Id } from "../../fields/FieldSymbols"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { library } from '@fortawesome/fontawesome-svg-core'; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 6517f2fb9..1ba9fcc32 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,14 +1,14 @@ -import { Doc, Opt, DataSym, DocListCast } from '../../new_fields/Doc'; +import { Doc, Opt, DataSym, DocListCast } from '../../fields/Doc'; import { Touchable } from './Touchable'; import { computed, action, observable } from 'mobx'; -import { Cast, BoolCast, ScriptCast } from '../../new_fields/Types'; -import { listSpec } from '../../new_fields/Schema'; +import { Cast, BoolCast, ScriptCast } from '../../fields/Types'; +import { listSpec } from '../../fields/Schema'; import { InkingControl } from './InkingControl'; -import { InkTool } from '../../new_fields/InkField'; +import { InkTool } from '../../fields/InkField'; import { InteractionUtils } from '../util/InteractionUtils'; -import { List } from '../../new_fields/List'; -import { DateField } from '../../new_fields/DateField'; -import { ScriptField } from '../../new_fields/ScriptField'; +import { List } from '../../fields/List'; +import { DateField } from '../../fields/DateField'; +import { ScriptField } from '../../fields/ScriptField'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 10d9ec401..2db5cd3ba 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -3,9 +3,9 @@ import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faArrowAltCircl import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../new_fields/Doc"; -import { RichTextField } from '../../new_fields/RichTextField'; -import { NumCast, StrCast, Cast } from "../../new_fields/Types"; +import { Doc, DocListCast } from "../../fields/Doc"; +import { RichTextField } from '../../fields/RichTextField'; +import { NumCast, StrCast, Cast } from "../../fields/Types"; import { emptyFunction, setupMoveUpEvents } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { UndoManager } from "../util/UndoManager"; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 313d8be23..1f4f45f0e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -3,10 +3,10 @@ import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faT import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DataSym, Field, WidthSym, HeightSym } from "../../new_fields/Doc"; -import { Document } from '../../new_fields/documentSchemas'; -import { ScriptField } from '../../new_fields/ScriptField'; -import { Cast, StrCast, NumCast } from "../../new_fields/Types"; +import { Doc, DataSym, Field, WidthSym, HeightSym } from "../../fields/Doc"; +import { Document } from '../../fields/documentSchemas'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, StrCast, NumCast } from "../../fields/Types"; import { Utils, setupMoveUpEvents, emptyFunction, returnFalse, simulateMouseClick } from "../../Utils"; import { DocUtils } from "../documents/Documents"; import { DocumentType } from '../documents/DocumentTypes'; @@ -17,7 +17,7 @@ import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; import { DocumentView } from "./nodes/DocumentView"; import React = require("react"); -import { Id } from '../../new_fields/FieldSymbols'; +import { Id } from '../../fields/FieldSymbols'; import e = require('express'); import { CollectionDockingView } from './collections/CollectionDockingView'; import { SnappingManager } from '../util/SnappingManager'; @@ -266,16 +266,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const fixedAspect = first.layoutDoc._nativeWidth ? NumCast(first.layoutDoc._nativeWidth) / NumCast(first.layoutDoc._nativeHeight) : 0; if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles const project = (p: number[], a: number[], b: number[]) => { - var atob = [b[0] - a[0], b[1] - a[1]]; - var atop = [p[0] - a[0], p[1] - a[1]]; - var len = atob[0] * atob[0] + atob[1] * atob[1]; - var dot = atop[0] * atob[0] + atop[1] * atob[1]; - var t = dot / len; + const atob = [b[0] - a[0], b[1] - a[1]]; + const atop = [p[0] - a[0], p[1] - a[1]]; + const len = atob[0] * atob[0] + atob[1] * atob[1]; + let dot = atop[0] * atob[0] + atop[1] * atob[1]; + const t = dot / len; dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]); return [a[0] + atob[0] * t, a[1] + atob[1] * t]; - } + }; const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]) + const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]); thisPt = DragManager.snapDragAspect(drag, fixedAspect); } else { thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY); diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index c51173ad3..e0e205df9 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -2,8 +2,8 @@ import React = require('react'); import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as Autosuggest from 'react-autosuggest'; -import { ObjectField } from '../../new_fields/ObjectField'; -import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField'; +import { ObjectField } from '../../fields/ObjectField'; +import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import "./EditableView.scss"; export interface EditableProps { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 812ff3b93..aadf5f535 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -6,13 +6,13 @@ import { computed, observable, action, runInAction, IReactionDisposer, reaction, import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; -import { InkTool, InkData } from "../../new_fields/InkField"; -import { Doc } from "../../new_fields/Doc"; +import { InkTool, InkData } from "../../fields/InkField"; +import { Doc } from "../../fields/Doc"; import { LinkManager } from "../util/LinkManager"; import { DocUtils, Docs } from "../documents/Documents"; import { undoBatch } from "../util/UndoManager"; import { Scripting } from "../util/Scripting"; -import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types"; +import { FieldValue, Cast, NumCast, BoolCast } from "../../fields/Types"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import HorizontalPalette from "./Palette"; import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils"; @@ -22,9 +22,9 @@ import { DocumentContentsView } from "./nodes/DocumentContentsView"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { DocServer } from "../DocServer"; import htmlToImage from "html-to-image"; -import { ScriptField } from "../../new_fields/ScriptField"; -import { listSpec } from "../../new_fields/Schema"; -import { List } from "../../new_fields/List"; +import { ScriptField } from "../../fields/ScriptField"; +import { listSpec } from "../../fields/Schema"; +import { List } from "../../fields/List"; import { CollectionViewType } from "./collections/CollectionView"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; import MobileInterface from "../../mobile/MobileInterface"; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index b52a0063b..255142771 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -4,23 +4,23 @@ import { CollectionDockingView } from "./collections/CollectionDockingView"; import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; import { action, runInAction } from "mobx"; -import { Doc, DocListCast } from "../../new_fields/Doc"; +import { Doc, DocListCast } from "../../fields/Doc"; import { DictationManager } from "../util/DictationManager"; import SharingManager from "../util/SharingManager"; -import { Cast, PromiseValue, NumCast } from "../../new_fields/Types"; -import { ScriptField } from "../../new_fields/ScriptField"; +import { Cast, PromiseValue, NumCast } from "../../fields/Types"; +import { ScriptField } from "../../fields/ScriptField"; import { InkingControl } from "./InkingControl"; -import { InkTool } from "../../new_fields/InkField"; +import { InkTool } from "../../fields/InkField"; import { DocumentView } from "./nodes/DocumentView"; import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; import { CollectionFreeFormView } from "./collections/collectionFreeForm/CollectionFreeFormView"; import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView"; -import { Id } from "../../new_fields/FieldSymbols"; +import { Id } from "../../fields/FieldSymbols"; import { DocumentDecorations } from "./DocumentDecorations"; import { DocumentType } from "../documents/DocumentTypes"; import { DocServer } from "../DocServer"; -import { List } from "../../new_fields/List"; -import { DateField } from "../../new_fields/DateField"; +import { List } from "../../fields/List"; +import { DateField } from "../../fields/DateField"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 5115995b0..156f58ec1 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -1,8 +1,8 @@ import { action, computed, observable } from "mobx"; import { ColorState } from 'react-color'; -import { Doc } from "../../new_fields/Doc"; -import { InkTool } from "../../new_fields/InkField"; -import { FieldValue, NumCast, StrCast } from "../../new_fields/Types"; +import { Doc } from "../../fields/Doc"; +import { InkTool } from "../../fields/InkField"; +import { FieldValue, NumCast, StrCast } from "../../fields/Types"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { Scripting } from "../util/Scripting"; import { SelectionManager } from "../util/SelectionManager"; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 7a318d5c2..25ea50a27 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,14 +1,14 @@ import { observer } from "mobx-react"; -import { documentSchema } from "../../new_fields/documentSchemas"; -import { InkData, InkField, InkTool } from "../../new_fields/InkField"; -import { makeInterface } from "../../new_fields/Schema"; -import { Cast, StrCast, NumCast } from "../../new_fields/Types"; +import { documentSchema } from "../../fields/documentSchemas"; +import { InkData, InkField, InkTool } from "../../fields/InkField"; +import { makeInterface } from "../../fields/Schema"; +import { Cast, StrCast, NumCast } from "../../fields/Types"; import { ViewBoxBaseComponent } from "./DocComponent"; import { InkingControl } from "./InkingControl"; import "./InkingStroke.scss"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import React = require("react"); -import { TraceMobx } from "../../new_fields/util"; +import { TraceMobx } from "../../fields/util"; import { InteractionUtils } from "../util/InteractionUtils"; import { ContextMenu } from "./ContextMenu"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9bc08de3e..978cf7868 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -13,12 +13,12 @@ import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import Measure from 'react-measure'; -import { Doc, DocListCast, Field, Opt } from '../../new_fields/Doc'; -import { Id } from '../../new_fields/FieldSymbols'; -import { List } from '../../new_fields/List'; -import { listSpec } from '../../new_fields/Schema'; -import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types'; -import { TraceMobx } from '../../new_fields/util'; +import { Doc, DocListCast, Field, Opt } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types'; +import { TraceMobx } from '../../fields/util'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; @@ -48,7 +48,7 @@ import { RadialMenu } from './nodes/RadialMenu'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; -import { ScriptField } from '../../new_fields/ScriptField'; +import { ScriptField } from '../../fields/ScriptField'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; import { DragManager } from '../util/DragManager'; import { SnappingManager } from '../util/SnappingManager'; diff --git a/src/client/views/MainViewNotifs.tsx b/src/client/views/MainViewNotifs.tsx index 82e07c449..05f890485 100644 --- a/src/client/views/MainViewNotifs.tsx +++ b/src/client/views/MainViewNotifs.tsx @@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; -import { Doc, DocListCast, Opt } from '../../new_fields/Doc'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { emptyFunction } from '../../Utils'; import { SetupDrag } from '../util/DragManager'; import "./MainViewNotifs.scss"; diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 8bc80ed06..e100d3f52 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -3,7 +3,7 @@ import "./MetadataEntryMenu.scss"; import { observer } from 'mobx-react'; import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx'; import { KeyValueBox } from './nodes/KeyValueBox'; -import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc'; +import { Doc, Field, DocListCastAsync } from '../../fields/Doc'; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; import { emptyFunction, emptyPath } from '../../Utils'; diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index afb6bfb7d..bfa44fe47 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,9 +1,9 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc, DocListCast, Opt } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { NumCast } from "../../new_fields/Types"; +import { Doc, DocListCast, Opt } from "../../fields/Doc"; +import { Id } from "../../fields/FieldSymbols"; +import { NumCast } from "../../fields/Types"; import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils"; import { Transform } from "../util/Transform"; import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView"; diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 63744cb50..108eb83d6 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -1,8 +1,8 @@ import { IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc } from "../../new_fields/Doc"; -import { NumCast } from "../../new_fields/Types"; +import { Doc } from "../../fields/Doc"; +import { NumCast } from "../../fields/Types"; import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils"; import { Transform } from "../util/Transform"; import { DocumentView } from "./nodes/DocumentView"; diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index f50ac34c8..dd65681d4 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -4,11 +4,11 @@ import "normalize.css"; import * as React from 'react'; import "./PreviewCursor.scss"; import { Docs } from '../documents/Documents'; -import { Doc } from '../../new_fields/Doc'; +import { Doc } from '../../fields/Doc'; import { Transform } from "../util/Transform"; import { DocServer } from '../DocServer'; import { undoBatch } from '../util/UndoManager'; -import { NumCast } from '../../new_fields/Types'; +import { NumCast } from '../../fields/Types'; @observer export class PreviewCursor extends React.Component<{}> { diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx index e66fd3eb4..8ca81c070 100644 --- a/src/client/views/RecommendationsBox.tsx +++ b/src/client/views/RecommendationsBox.tsx @@ -3,17 +3,17 @@ import React = require("react"); import { observable, action, computed, runInAction } from "mobx"; import Measure from "react-measure"; import "./RecommendationsBox.scss"; -import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { Doc, DocListCast, WidthSym, HeightSym } from "../../fields/Doc"; import { DocumentIcon } from "./nodes/DocumentIcon"; -import { StrCast, NumCast } from "../../new_fields/Types"; +import { StrCast, NumCast } from "../../fields/Types"; import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils"; import { Transform } from "../util/Transform"; -import { ObjectField } from "../../new_fields/ObjectField"; +import { ObjectField } from "../../fields/ObjectField"; import { DocumentView } from "./nodes/DocumentView"; import { DocumentType } from '../documents/DocumentTypes'; import { ClientRecommender } from "../ClientRecommender"; import { DocServer } from "../DocServer"; -import { Id } from "../../new_fields/FieldSymbols"; +import { Id } from "../../fields/FieldSymbols"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import { DocumentManager } from "../util/DocumentManager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 66d3b937e..888f84dfa 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -5,11 +5,11 @@ import { observable, action } from "mobx"; import "./ScriptBox.scss"; import { OverlayView } from "./OverlayView"; import { DocumentIconContainer } from "./nodes/DocumentIcon"; -import { Opt, Doc } from "../../new_fields/Doc"; +import { Opt, Doc } from "../../fields/Doc"; import { emptyFunction } from "../../Utils"; -import { ScriptCast } from "../../new_fields/Types"; +import { ScriptCast } from "../../fields/Types"; import { CompileScript } from "../util/Scripting"; -import { ScriptField } from "../../new_fields/ScriptField"; +import { ScriptField } from "../../fields/ScriptField"; import { DragManager } from "../util/DragManager"; import { EditableView } from "./EditableView"; import { getEffectiveTypeRoots } from "typescript"; diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx index 7bd689b19..e038d8213 100644 --- a/src/client/views/SearchDocBox.tsx +++ b/src/client/views/SearchDocBox.tsx @@ -3,9 +3,9 @@ import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; //import "./SearchBoxDoc.scss"; -import { Doc, DocListCast } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { Doc, DocListCast } from "../../fields/Doc"; +import { Id } from "../../fields/FieldSymbols"; +import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; import { returnFalse, returnZero } from "../../Utils"; import { Docs } from "../documents/Documents"; import { SearchUtil } from "../util/SearchUtil"; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 43debfe99..f5e95e4fd 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -6,15 +6,15 @@ import './TemplateMenu.scss'; import { DocumentView } from "./nodes/DocumentView"; import { Template } from "./Templates"; import React = require("react"); -import { Doc, DocListCast } from "../../new_fields/Doc"; +import { Doc, DocListCast } from "../../fields/Doc"; import { Docs, } from "../documents/Documents"; -import { StrCast, Cast } from "../../new_fields/Types"; +import { StrCast, Cast } from "../../fields/Types"; import { CollectionTreeView } from "./collections/CollectionTreeView"; import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils"; import { Transform } from "../util/Transform"; -import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; +import { ScriptField, ComputedField } from "../../fields/ScriptField"; import { Scripting } from "../util/Scripting"; -import { List } from "../../new_fields/List"; +import { List } from "../../fields/List"; @observer class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent, template: Template) => void }> { diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index bbd7b2676..92b0f05b3 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -4,10 +4,10 @@ import "./Timeline.scss"; import "../globalCssVariables.scss"; import { observer } from "mobx-react"; import { observable, reaction, action, IReactionDisposer, observe, computed, runInAction, trace } from "mobx"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; -import { Cast, NumCast } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; -import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../new_fields/Schema"; +import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; +import { Cast, NumCast } from "../../../fields/Types"; +import { List } from "../../../fields/List"; +import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../fields/Schema"; import { Transform } from "../../util/Transform"; import { TimelineMenu } from "./TimelineMenu"; import { Docs } from "../../documents/Documents"; diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index 466cbb867..30692944d 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -1,12 +1,12 @@ import * as React from "react"; import "./Timeline.scss"; -import { listSpec } from "../../../new_fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { observer } from "mobx-react"; import { Track } from "./Track"; import { observable, action, computed, runInAction, IReactionDisposer, reaction, trace } from "mobx"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types"; +import { List } from "../../../fields/List"; +import { Doc, DocListCast } from "../../../fields/Doc"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPlayCircle, faBackward, faForward, faGripLines, faPauseCircle, faEyeSlash, faEye, faCheckCircle, faTimesCircle } from "@fortawesome/free-solid-svg-icons"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx index 461db4858..fc96c320a 100644 --- a/src/client/views/animationtimeline/Track.tsx +++ b/src/client/views/animationtimeline/Track.tsx @@ -1,12 +1,12 @@ import { action, computed, intercept, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../new_fields/Doc"; -import { Copy } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { ObjectField } from "../../../new_fields/ObjectField"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast } from "../../../new_fields/Types"; +import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../fields/Doc"; +import { Copy } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { ObjectField } from "../../../fields/ObjectField"; +import { listSpec } from "../../../fields/Schema"; +import { Cast, NumCast } from "../../../fields/Types"; import { Transform } from "../../util/Transform"; import { Keyframe, KeyframeFunc, RegionData } from "./Keyframe"; import "./Track.scss"; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index a04136e51..39bb9bc23 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -2,18 +2,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observable, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { documentSchema, collectionSchema } from '../../../new_fields/documentSchemas'; -import { makeInterface } from '../../../new_fields/Schema'; -import { NumCast, StrCast, ScriptCast, Cast } from '../../../new_fields/Types'; +import { documentSchema, collectionSchema } from '../../../fields/documentSchemas'; +import { makeInterface } from '../../../fields/Schema'; +import { NumCast, StrCast, ScriptCast, Cast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import "./CollectionCarouselView.scss"; import { CollectionSubView } from './CollectionSubView'; import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; -import { Doc } from '../../../new_fields/Doc'; +import { Doc } from '../../../fields/Doc'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { ContextMenu } from '../ContextMenu'; -import { ObjectField } from '../../../new_fields/ObjectField'; +import { ObjectField } from '../../../fields/ObjectField'; import { returnFalse } from '../../../Utils'; type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 581625222..745476ef7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -7,13 +7,13 @@ import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; -import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, Field, Opt, DataSym } from "../../../new_fields/Doc"; -import { Id } from '../../../new_fields/FieldSymbols'; -import { List } from '../../../new_fields/List'; -import { FieldId } from "../../../new_fields/RefField"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { TraceMobx } from '../../../new_fields/util'; +import { DateField } from '../../../fields/DateField'; +import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc"; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { FieldId } from "../../../fields/RefField"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; @@ -196,15 +196,16 @@ export class CollectionDockingView extends React.Component => { - for (let i = 0; i < child.contentItems.length; i++) { - if (child.contentItems[i].isRow || child.contentItems[i].isColumn || child.contentItems[i].isStack) { - const val = replaceTab(doc, child.contentItems[i]); + 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 (child.contentItems[i].config.component === "DocumentFrameRenderer" && - child.contentItems[i].config.props.documentId === doc[Id]) { + } else if (config.component === "DocumentFrameRenderer" && + config.props.documentId === doc[Id]) { const alias = Doc.MakeAlias(doc); - child.contentItems[i].config.props.documentId = alias[Id]; - child.contentItems[i].config.title = alias.title; + config.props.documentId = alias[Id]; + config.title = alias.title; instance.stateChanged(); return alias; } diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 344dca23a..f1002044a 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -1,9 +1,9 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc'; -import { makeInterface } from '../../../new_fields/Schema'; -import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../new_fields/Types'; +import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { makeInterface } from '../../../fields/Schema'; +import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../fields/Types'; import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; @@ -11,8 +11,8 @@ import "./CollectionLinearView.scss"; import { CollectionViewType } from './CollectionView'; import { CollectionSubView } from './CollectionSubView'; import { DocumentView } from '../nodes/DocumentView'; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id } from '../../../new_fields/FieldSymbols'; +import { documentSchema } from '../../../fields/documentSchemas'; +import { Id } from '../../../fields/FieldSymbols'; type LinearDocument = makeInterface<[typeof documentSchema,]>; diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx index 618285293..da902cecd 100644 --- a/src/client/views/collections/CollectionMapView.tsx +++ b/src/client/views/collections/CollectionMapView.tsx @@ -1,10 +1,10 @@ import { GoogleApiWrapper, Map as GeoMap, MapProps as IMapProps, Marker } from "google-maps-react"; import { observer } from "mobx-react"; -import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../new_fields/Doc"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; +import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../fields/Doc"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { Id } from "../../../fields/FieldSymbols"; +import { makeInterface } from "../../../fields/Schema"; +import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import "./CollectionMapView.scss"; import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 95c7643c9..d6cb174cc 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -4,10 +4,10 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; -import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { StrCast, NumCast } from "../../../new_fields/Types"; +import { Doc } from "../../../fields/Doc"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { StrCast, NumCast } from "../../../fields/Types"; import { numberRange, setupMoveUpEvents, emptyFunction } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index d3ae21f3a..e3bcf2a21 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,8 +1,8 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { ScriptField } from "../../../fields/ScriptField"; +import { BoolCast, NumCast, StrCast } from "../../../fields/Types"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 8a5450b0c..62aed67ed 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -4,8 +4,8 @@ import { observer } from "mobx-react"; import { CellInfo } from "react-table"; import "react-table/react-table.css"; import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils"; -import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; +import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; import { KeyCodes } from "../../util/KeyCodes"; import { SetupDrag, DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; @@ -16,11 +16,11 @@ import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; -import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../new_fields/Types"; +import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../fields/Types"; import { Docs } from "../../documents/Documents"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faExpand } from '@fortawesome/free-solid-svg-icons'; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; import { SnappingManager } from "../../util/SnappingManager"; diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index 507ee89e4..dae0600b1 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -7,7 +7,7 @@ import { library, IconProp } 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 "../../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index 5aec46a83..6f1e8ac1f 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -2,16 +2,16 @@ import React = require("react"); import { ReactTableDefaults, TableCellRenderer, RowInfo } from "react-table"; import "./CollectionSchemaView.scss"; import { Transform } from "../../util/Transform"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc } from "../../../fields/Doc"; import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager"; -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, StrCast } from "../../../fields/Types"; import { ContextMenu } from "../ContextMenu"; import { action } from "mobx"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faGripVertical, faTrash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { DocumentManager } from "../../util/DocumentManager"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; import { SnappingManager } from "../../util/SnappingManager"; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 51ad6c81b..35f892d65 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -6,13 +6,13 @@ import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; import "react-table/react-table.css"; -import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; +import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { listSpec } from "../../../fields/Schema"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ComputedField } from "../../../fields/ScriptField"; +import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d97f26daf..cc6077d98 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,14 +4,14 @@ 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 } from "../../../new_fields/Doc"; -import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { listSpec, makeInterface } from "../../../new_fields/Schema"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; -import { TraceMobx } from "../../../new_fields/util"; +import { DataSym, Doc, HeightSym, WidthSym } from "../../../fields/Doc"; +import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { listSpec, makeInterface } from "../../../fields/Schema"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, smoothScroll } from "../../../Utils"; import { DragManager, dropActionType } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index dccef7983..53435ccc9 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -4,13 +4,13 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; -import { ImageField } from "../../../new_fields/URLField"; -import { TraceMobx } from "../../../new_fields/util"; +import { Doc, DocListCast } from "../../../fields/Doc"; +import { RichTextField } from "../../../fields/RichTextField"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { NumCast, StrCast, Cast } from "../../../fields/Types"; +import { ImageField } from "../../../fields/URLField"; +import { TraceMobx } from "../../../fields/util"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; @@ -21,7 +21,7 @@ import { EditableView } from "../EditableView"; import { CollectionStackingView } from "./CollectionStackingView"; import { setupMoveUpEvents, emptyFunction } from "../../../Utils"; import "./CollectionStackingView.scss"; -import { listSpec } from "../../../new_fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { SnappingManager } from "../../util/SnappingManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx index 5b9a69bf7..c5c3f96e8 100644 --- a/src/client/views/collections/CollectionStaffView.tsx +++ b/src/client/views/collections/CollectionStaffView.tsx @@ -1,7 +1,7 @@ import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx"; -import { NumCast } from "../../../new_fields/Types"; +import { NumCast } from "../../../fields/Types"; import "./CollectionStaffView.scss"; import { observer } from "mobx-react"; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index bff6d121b..a4e843c8e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,12 +1,12 @@ import { action, computed, IReactionDisposer, reaction } from "mobx"; import { basename } from 'path'; -import CursorField from "../../../new_fields/CursorField"; -import { Doc, Opt } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, ScriptCast } from "../../../new_fields/Types"; +import CursorField from "../../../fields/CursorField"; +import { Doc, Opt } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { listSpec } from "../../../fields/Schema"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, ScriptCast } from "../../../fields/Types"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { Upload } from "../../../server/SharedMediaTypes"; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index a2d4774c8..15bc0bfd5 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,11 +1,11 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocCastAsync } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { ObjectField } from "../../../new_fields/ObjectField"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; -import { NumCast, StrCast, BoolCast, Cast } from "../../../new_fields/Types"; +import { Doc, Opt, DocCastAsync } from "../../../fields/Doc"; +import { List } from "../../../fields/List"; +import { ObjectField } from "../../../fields/ObjectField"; +import { RichTextField } from "../../../fields/RichTextField"; +import { ComputedField, ScriptField } from "../../../fields/ScriptField"; +import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types"; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; import { Scripting } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2f332e77d..3e99af724 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,13 +1,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { List } from '../../../new_fields/List'; -import { PrefetchProxy } from '../../../new_fields/Proxy'; -import { Document, listSpec } from '../../../new_fields/Schema'; -import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types'; +import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { PrefetchProxy } from '../../../fields/Proxy'; +import { Document, listSpec } from '../../../fields/Schema'; +import { ComputedField, ScriptField } from '../../../fields/ScriptField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; @@ -32,7 +32,7 @@ import "./CollectionTreeView.scss"; import { CollectionViewType } from './CollectionView'; import React = require("react"); import { makeTemplate } from '../../util/DropConverter'; -import { TraceMobx } from '../../../new_fields/util'; +import { TraceMobx } from '../../../fields/util'; export interface TreeViewProps { document: Doc; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 4b3b2e234..3b2e5e4fc 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -7,12 +7,12 @@ import { observer } from "mobx-react"; import * as React from 'react'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app -import { DateField } from '../../../new_fields/DateField'; -import { DataSym, Doc, DocListCast, Field, Opt } from '../../../new_fields/Doc'; -import { List } from '../../../new_fields/List'; -import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types'; -import { ImageField } from '../../../new_fields/URLField'; -import { TraceMobx } from '../../../new_fields/util'; +import { DateField } from '../../../fields/DateField'; +import { DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; +import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../fields/Types'; +import { ImageField } from '../../../fields/URLField'; +import { TraceMobx } from '../../../fields/util'; import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; @@ -36,12 +36,12 @@ import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; import { CollectionViewBaseChrome } from './CollectionViewChromes'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { listSpec } from '../../../new_fields/Schema'; +import { Id } from '../../../fields/FieldSymbols'; +import { listSpec } from '../../../fields/Schema'; import { Docs } from '../../documents/Documents'; -import { ScriptField, ComputedField } from '../../../new_fields/ScriptField'; +import { ScriptField, ComputedField } from '../../../fields/ScriptField'; import { InteractionUtils } from '../../util/InteractionUtils'; -import { ObjectField } from '../../../new_fields/ObjectField'; +import { ObjectField } from '../../../fields/ObjectField'; import CollectionMapView from './CollectionMapView'; import { CollectionPileView } from './CollectionPileView'; const higflyout = require("@hig/flyout"); diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 62b03bbdc..5dc0b09ac 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -2,11 +2,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc, DocListCast } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { listSpec } from "../../../fields/Schema"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { undoBatch } from "../../util/UndoManager"; diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 6e4f801c0..649406e6c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -1,12 +1,12 @@ import * as React from "react"; import './ParentDocumentSelector.scss'; -import { Doc } from "../../../new_fields/Doc"; +import { Doc } from "../../../fields/Doc"; import { observer } from "mobx-react"; import { observable, action, runInAction, trace, computed, reaction, IReactionDisposer } from "mobx"; -import { Id } from "../../../new_fields/FieldSymbols"; +import { Id } from "../../../fields/FieldSymbols"; import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; -import { NumCast, StrCast } from "../../../new_fields/Types"; +import { NumCast, StrCast } from "../../../fields/Types"; import { CollectionViewType } from "./CollectionView"; import { DocumentButtonBar } from "../DocumentButtonBar"; import { DocumentManager } from "../../util/DocumentManager"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 9a864078a..3860ce2d7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,15 +1,15 @@ -import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc"; -import { NumCast, StrCast, Cast } from "../../../../new_fields/Types"; +import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../fields/Doc"; +import { NumCast, StrCast, Cast } from "../../../../fields/Types"; import { ScriptBox } from "../../ScriptBox"; import { CompileScript } from "../../../util/Scripting"; -import { ScriptField } from "../../../../new_fields/ScriptField"; +import { ScriptField } from "../../../../fields/ScriptField"; import { OverlayView, OverlayElementOptions } from "../../OverlayView"; import { emptyFunction, aggregateBounds } from "../../../../Utils"; import React = require("react"); -import { Id, ToString } from "../../../../new_fields/FieldSymbols"; -import { ObjectField } from "../../../../new_fields/ObjectField"; -import { RefField } from "../../../../new_fields/RefField"; -import { listSpec } from "../../../../new_fields/Schema"; +import { Id, ToString } from "../../../../fields/FieldSymbols"; +import { ObjectField } from "../../../../fields/ObjectField"; +import { RefField } from "../../../../fields/RefField"; +import { listSpec } from "../../../../fields/Schema"; export interface ViewDefBounds { type: string; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index ba71aff32..f3fc04752 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../fields/Doc"; import { Utils } from '../../../../Utils'; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; @@ -7,8 +7,8 @@ import React = require("react"); import v5 = require("uuid/v5"); import { DocumentType } from "../../../documents/DocumentTypes"; import { observable, action, reaction, IReactionDisposer } from "mobx"; -import { StrCast, Cast } from "../../../../new_fields/Types"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { StrCast, Cast } from "../../../../fields/Types"; +import { Id } from "../../../../fields/FieldSymbols"; import { SnappingManager } from "../../../util/SnappingManager"; export interface CollectionFreeFormLinkViewProps { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 1208fb324..ae81b4b36 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,7 +1,7 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Doc } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinksView.scss"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 9a5b2c27c..548ad78a5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,16 +1,16 @@ import { observer } from "mobx-react"; import * as mobxUtils from 'mobx-utils'; -import CursorField from "../../../../new_fields/CursorField"; -import { listSpec } from "../../../../new_fields/Schema"; -import { Cast } from "../../../../new_fields/Types"; +import CursorField from "../../../../fields/CursorField"; +import { listSpec } from "../../../../fields/Schema"; +import { Cast } from "../../../../fields/Types"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormView.scss"; import React = require("react"); import v5 = require("uuid/v5"); import { computed } from "mobx"; -import { FieldResult } from "../../../../new_fields/Doc"; -import { List } from "../../../../new_fields/List"; +import { FieldResult } from "../../../../fields/Doc"; +import { List } from "../../../../fields/List"; @observer export class CollectionFreeFormRemoteCursors extends React.Component { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6c4660c39..fb8bd25da 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -4,16 +4,16 @@ import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrows import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; -import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; -import { documentSchema, collectionSchema } from "../../../../new_fields/documentSchemas"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkData, InkField, InkTool, PointData } from "../../../../new_fields/InkField"; -import { List } from "../../../../new_fields/List"; -import { RichTextField } from "../../../../new_fields/RichTextField"; -import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema"; -import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types"; -import { TraceMobx } from "../../../../new_fields/util"; +import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc"; +import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas"; +import { Id } from "../../../../fields/FieldSymbols"; +import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField"; +import { List } from "../../../../fields/List"; +import { RichTextField } from "../../../../fields/RichTextField"; +import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema"; +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, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; @@ -1034,8 +1034,7 @@ export class CollectionFreeFormView extends CollectionSubView { - for (let i = 0; i < array.length; i++) { - const entry = array[i]; + for (const entry of array) { const lastPos = this._cachedPool.get(entry[0]); // last computed pos const newPos = entry[1]; if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 8e20b39d2..04d37934e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,11 +1,11 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt } from "../../../../new_fields/Doc"; -import { InkData, InkField } from "../../../../new_fields/InkField"; -import { List } from "../../../../new_fields/List"; -import { RichTextField } from "../../../../new_fields/RichTextField"; -import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; +import { Doc, Opt } from "../../../../fields/Doc"; +import { InkData, InkField } from "../../../../fields/InkField"; +import { List } from "../../../../fields/List"; +import { RichTextField } from "../../../../fields/RichTextField"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; import { Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index a09124304..c0e1a0232 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,10 +1,10 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; -import { Doc } from '../../../../new_fields/Doc'; -import { documentSchema } from '../../../../new_fields/documentSchemas'; -import { makeInterface } from '../../../../new_fields/Schema'; -import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../new_fields/Types'; +import { Doc } from '../../../../fields/Doc'; +import { documentSchema } from '../../../../fields/documentSchemas'; +import { makeInterface } from '../../../../fields/Schema'; +import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../fields/Types'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -13,7 +13,7 @@ import { CollectionSubView } from '../CollectionSubView'; import "./collectionMulticolumnView.scss"; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; -import { List } from '../../../../new_fields/List'; +import { List } from '../../../../fields/List'; import { returnZero, returnFalse, returnOne } from '../../../../Utils'; type MulticolumnDocument = makeInterface<[typeof documentSchema]>; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 4326723b1..602246d07 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -1,10 +1,10 @@ import { observer } from 'mobx-react'; -import { makeInterface } from '../../../../new_fields/Schema'; -import { documentSchema } from '../../../../new_fields/documentSchemas'; +import { makeInterface } from '../../../../fields/Schema'; +import { documentSchema } from '../../../../fields/documentSchemas'; import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import * as React from "react"; -import { Doc } from '../../../../new_fields/Doc'; -import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types'; +import { Doc } from '../../../../fields/Doc'; +import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../fields/Types'; import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils'; import "./collectionMultirowView.scss"; @@ -14,7 +14,7 @@ import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; import { undoBatch } from '../../../util/UndoManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { List } from '../../../../new_fields/List'; +import { List } from '../../../../fields/List'; type MultirowDocument = makeInterface<[typeof documentSchema]>; const MultirowDocument = makeInterface(documentSchema); diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index a24eb1631..734915a93 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -1,8 +1,8 @@ import * as React from "react"; import { observer } from "mobx-react"; import { observable, action } from "mobx"; -import { Doc } from "../../../../new_fields/Doc"; -import { NumCast, StrCast } from "../../../../new_fields/Types"; +import { Doc } from "../../../../fields/Doc"; +import { NumCast, StrCast } from "../../../../fields/Types"; import { DimUnit } from "./CollectionMulticolumnView"; import { UndoManager } from "../../../util/UndoManager"; diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx index 5b2054428..9985a9fba 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx @@ -1,8 +1,8 @@ import * as React from "react"; import { observer } from "mobx-react"; import { computed } from "mobx"; -import { Doc } from "../../../../new_fields/Doc"; -import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types"; +import { Doc } from "../../../../fields/Doc"; +import { NumCast, StrCast, BoolCast } from "../../../../fields/Types"; import { EditableView } from "../../EditableView"; import { DimUnit } from "./CollectionMulticolumnView"; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx index 899577fd5..aa5439fa4 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx @@ -1,8 +1,8 @@ import * as React from "react"; import { observer } from "mobx-react"; import { computed } from "mobx"; -import { Doc } from "../../../../new_fields/Doc"; -import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types"; +import { Doc } from "../../../../fields/Doc"; +import { NumCast, StrCast, BoolCast } from "../../../../fields/Types"; import { EditableView } from "../../EditableView"; import { DimUnit } from "./CollectionMultirowView"; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 5f00b18b9..d0bc4d01c 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -1,8 +1,8 @@ import * as React from "react"; import { observer } from "mobx-react"; import { observable, action } from "mobx"; -import { Doc } from "../../../../new_fields/Doc"; -import { NumCast, StrCast } from "../../../../new_fields/Types"; +import { Doc } from "../../../../fields/Doc"; +import { NumCast, StrCast } from "../../../../fields/Types"; import { DimUnit } from "./CollectionMultirowView"; import { UndoManager } from "../../../util/UndoManager"; diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index b7f3dd995..5fb4cf3c6 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -3,8 +3,8 @@ import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTime import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; -import { StrCast } from "../../../new_fields/Types"; +import { Doc } from "../../../fields/Doc"; +import { StrCast } from "../../../fields/Types"; import { Utils } from "../../../Utils"; import { LinkManager } from "../../util/LinkManager"; import './LinkEditor.scss'; diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index b768eacc3..786d6be47 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -4,7 +4,7 @@ import { DocumentView } from "../nodes/DocumentView"; import { LinkEditor } from "./LinkEditor"; import './LinkMenu.scss'; import React = require("react"); -import { Doc } from "../../../new_fields/Doc"; +import { Doc } from "../../../fields/Doc"; import { LinkManager } from "../../util/LinkManager"; import { LinkMenuGroup } from "./LinkMenuGroup"; import { faTrash } from '@fortawesome/free-solid-svg-icons'; diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index c97e1062d..89deb3a55 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,9 +1,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { Doc } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { Docs } from "../../documents/Documents"; import { DragManager, SetupDrag } from "../../util/DragManager"; import { LinkManager } from "../../util/LinkManager"; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index b0c61cb04..17cd33241 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -3,8 +3,8 @@ import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, DocListCast } from '../../../new_fields/Doc'; -import { Cast, StrCast } from '../../../new_fields/Types'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { Cast, StrCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 1c5e13620..1a935d9b0 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -2,23 +2,23 @@ import React = require("react"); import { FieldViewProps, FieldView } from './FieldView'; import { observer } from "mobx-react"; import "./AudioBox.scss"; -import { Cast, DateCast, NumCast } from "../../../new_fields/Types"; -import { AudioField, nullAudio } from "../../../new_fields/URLField"; +import { Cast, DateCast, NumCast } from "../../../fields/Types"; +import { AudioField, nullAudio } from "../../../fields/URLField"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { makeInterface, createSchema } from "../../../new_fields/Schema"; -import { documentSchema } from "../../../new_fields/documentSchemas"; +import { makeInterface, createSchema } from "../../../fields/Schema"; +import { documentSchema } from "../../../fields/documentSchemas"; import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; -import { DateField } from "../../../new_fields/DateField"; +import { DateField } from "../../../fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../fields/Doc"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; -import { Id } from "../../../new_fields/FieldSymbols"; +import { Id } from "../../../fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { DocumentView } from "./DocumentView"; import { Docs, DocUtils } from "../../documents/Documents"; -import { ComputedField } from "../../../new_fields/ScriptField"; +import { ComputedField } from "../../../fields/ScriptField"; import { Networking } from "../../Network"; import { LinkAnchorBox } from "./LinkAnchorBox"; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index cdbe506a5..5d6e587d9 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,14 +1,14 @@ import { computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import "./CollectionFreeFormDocumentView.scss"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); -import { Document } from "../../../new_fields/documentSchemas"; -import { TraceMobx } from "../../../new_fields/util"; +import { Document } from "../../../fields/documentSchemas"; +import { TraceMobx } from "../../../fields/util"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index bc84204b9..bef6609c4 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -1,9 +1,9 @@ import React = require("react"); import { observer } from "mobx-react"; import { SketchPicker } from 'react-color'; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface } from "../../../new_fields/Schema"; -import { StrCast } from "../../../new_fields/Types"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface } from "../../../fields/Schema"; +import { StrCast } from "../../../fields/Types"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { SelectionManager } from "../../util/SelectionManager"; import { ViewBoxBaseComponent } from "../DocComponent"; diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 1c6250b94..a90b4668e 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -2,9 +2,9 @@ import React = require("react"); import { computed } from "mobx"; import { observer } from "mobx-react"; import "react-table/react-table.css"; -import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; -import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; -import { TraceMobx } from "../../../new_fields/util"; +import { Doc, Opt, WidthSym, HeightSym } from "../../../fields/Doc"; +import { NumCast, StrCast, Cast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnOne } from "../../../Utils"; import '../DocumentDecorations.scss'; import { DocumentView, DocumentViewProps } from "../nodes/DocumentView"; diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index af8dc704c..0c5239d66 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Field } from "../../../new_fields/Doc"; -import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface, listSpec } from "../../../new_fields/Schema"; -import { ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { TraceMobx } from "../../../new_fields/util"; +import { Doc, Field } from "../../../fields/Doc"; +import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface, listSpec } from "../../../fields/Schema"; +import { ComputedField } from "../../../fields/ScriptField"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; import { emptyPath, returnFalse, returnOne, returnZero } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { DragManager } from "../../util/DragManager"; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 2fc82e524..f4785bb0c 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,7 +1,7 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, Field } from "../../../new_fields/Doc"; -import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; +import { Doc, Opt, Field } from "../../../fields/Doc"; +import { Cast, StrCast, NumCast } from "../../../fields/Types"; import { OmitKeys, Without, emptyPath } from "../../../Utils"; import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { CollectionDockingView } from "../collections/CollectionDockingView"; @@ -35,8 +35,8 @@ import { WebBox } from "./WebBox"; import { InkingStroke } from "../InkingStroke"; import React = require("react"); import { RecommendationsBox } from "../RecommendationsBox"; -import { TraceMobx } from "../../../new_fields/util"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { TraceMobx } from "../../../fields/util"; +import { ScriptField } from "../../../fields/ScriptField"; import XRegExp = require("xregexp"); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index f56f5e829..fb54f18e8 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { DocumentView } from "./DocumentView"; import { DocumentManager } from "../../util/DocumentManager"; import { Transformer, Scripting, ts } from "../../util/Scripting"; -import { Field } from "../../../new_fields/Doc"; +import { Field } from "../../../fields/Doc"; @observer export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d821bb8ac..340fa06a8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -4,16 +4,16 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../new_fields/Doc"; -import { Document } from '../../../new_fields/documentSchemas'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { InkTool } from '../../../new_fields/InkField'; -import { listSpec } from "../../../new_fields/Schema"; -import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField'; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { ImageField } from '../../../new_fields/URLField'; -import { TraceMobx } from '../../../new_fields/util'; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym } from "../../../fields/Doc"; +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, StrCast } from "../../../fields/Types"; +import { ImageField } from '../../../fields/URLField'; +import { TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx index 3c7f1f206..92ca276cb 100644 --- a/src/client/views/nodes/FaceRectangles.tsx +++ b/src/client/views/nodes/FaceRectangles.tsx @@ -1,8 +1,8 @@ import React = require("react"); -import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { Cast, NumCast } from "../../../new_fields/Types"; +import { Doc, DocListCast } from "../../../fields/Doc"; +import { Cast, NumCast } from "../../../fields/Types"; import { observer } from "mobx-react"; -import { Id } from "../../../new_fields/FieldSymbols"; +import { Id } from "../../../fields/FieldSymbols"; import FaceRectangle from "./FaceRectangle"; interface FaceRectanglesProps { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 2ab0a416d..e9dc43bd8 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,11 +1,11 @@ import React = require("react"); import { computed } from "mobx"; import { observer } from "mobx-react"; -import { DateField } from "../../../new_fields/DateField"; -import { Doc, FieldResult, Opt, Field } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { AudioField, VideoField } from "../../../new_fields/URLField"; +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 { Transform } from "../../util/Transform"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 0d597b3a8..cf0b16c7c 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -1,16 +1,16 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { createSchema, makeInterface } from '../../../new_fields/Schema'; +import { createSchema, makeInterface } from '../../../fields/Schema'; import { DocComponent } from '../DocComponent'; import './FontIconBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; -import { StrCast, Cast } from '../../../new_fields/Types'; +import { StrCast, Cast } from '../../../fields/Types'; import { Utils } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; -import { Doc } from '../../../new_fields/Doc'; +import { Doc } from '../../../fields/Doc'; import { ContextMenu } from '../ContextMenu'; -import { ScriptField } from '../../../new_fields/ScriptField'; +import { ScriptField } from '../../../fields/ScriptField'; const FontIconSchema = createSchema({ icon: "string" }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index aaebceaa2..47e7607d6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -4,16 +4,16 @@ import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortaw import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc'; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { List } from '../../../new_fields/List'; -import { ObjectField } from '../../../new_fields/ObjectField'; -import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; -import { ComputedField } from '../../../new_fields/ScriptField'; -import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; -import { AudioField, ImageField } from '../../../new_fields/URLField'; -import { TraceMobx } from '../../../new_fields/util'; +import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc'; +import { documentSchema } from '../../../fields/documentSchemas'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { ObjectField } from '../../../fields/ObjectField'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { ComputedField } from '../../../fields/ScriptField'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { AudioField, ImageField } from '../../../fields/URLField'; +import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 39d7109b1..e983852ea 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -1,13 +1,13 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Field, FieldResult } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { listSpec } from "../../../new_fields/Schema"; -import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { ImageField } from "../../../new_fields/URLField"; +import { Doc, Field, FieldResult } from "../../../fields/Doc"; +import { List } from "../../../fields/List"; +import { RichTextField } from "../../../fields/RichTextField"; +import { listSpec } from "../../../fields/Schema"; +import { ComputedField, ScriptField } from "../../../fields/ScriptField"; +import { Cast, FieldValue, NumCast } from "../../../fields/Types"; +import { ImageField } from "../../../fields/URLField"; import { Docs } from "../../documents/Documents"; import { SetupDrag } from "../../util/DragManager"; import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting"; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 6dc4ae578..956d6556b 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,6 +1,6 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, Field, Opt } from '../../../new_fields/Doc'; +import { Doc, Field, Opt } from '../../../fields/Doc'; import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index ac27640bd..2d27ec441 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -3,11 +3,11 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons'; import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast } from '../../../new_fields/Doc'; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { List } from '../../../new_fields/List'; -import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; -import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { documentSchema } from '../../../fields/documentSchemas'; +import { List } from '../../../fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index bc36e056e..098aa58e9 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -1,9 +1,9 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc, DocListCast } from "../../../fields/Doc"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface } from "../../../fields/Schema"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; @@ -16,7 +16,7 @@ import { ContextMenu } from "../ContextMenu"; import { LinkEditor } from "../linking/LinkEditor"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SelectionManager } from "../../util/SelectionManager"; -import { TraceMobx } from "../../../new_fields/util"; +import { TraceMobx } from "../../../fields/util"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 740f2ef04..3f942e87b 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,13 +1,13 @@ import React = require("react"); import { observer } from "mobx-react"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface, listSpec } from "../../../new_fields/Schema"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface, listSpec } from "../../../fields/Schema"; import { returnFalse, returnZero } from "../../../Utils"; import { CollectionTreeView } from "../collections/CollectionTreeView"; import { ViewBoxBaseComponent } from "../DocComponent"; import { FieldView, FieldViewProps } from './FieldView'; import "./LinkBox.scss"; -import { Cast } from "../../../new_fields/Types"; +import { Cast } from "../../../fields/Types"; type LinkDocument = makeInterface<[typeof documentSchema]>; const LinkDocument = makeInterface(documentSchema); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3712c648e..493f23dc4 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -3,11 +3,11 @@ import { action, observable, runInAction, reaction, IReactionDisposer, trace, un import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Opt, WidthSym, Doc, HeightSym } from "../../../new_fields/Doc"; -import { makeInterface } from "../../../new_fields/Schema"; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { PdfField, URLField } from "../../../new_fields/URLField"; +import { Opt, WidthSym, Doc, HeightSym } from "../../../fields/Doc"; +import { makeInterface } from "../../../fields/Schema"; +import { ScriptField } from '../../../fields/ScriptField'; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { PdfField, URLField } from "../../../fields/URLField"; import { Utils } from '../../../Utils'; import { undoBatch } from '../../util/UndoManager'; import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; @@ -20,7 +20,7 @@ import { pageSchema } from "./ImageBox"; import { KeyCodes } from '../../util/KeyCodes'; import "./PDFBox.scss"; import React = require("react"); -import { documentSchema } from '../../../new_fields/documentSchemas'; +import { documentSchema } from '../../../fields/documentSchemas'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 42552ec73..342a8a215 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -2,11 +2,11 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DocCastAsync } from "../../../new_fields/Doc"; -import { InkTool } from "../../../new_fields/InkField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc, DocListCast, DocCastAsync } from "../../../fields/Doc"; +import { InkTool } from "../../../fields/InkField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; import { returnFalse } from "../../../Utils"; -import { documentSchema } from "../../../new_fields/documentSchemas"; +import { documentSchema } from "../../../fields/documentSchemas"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; @@ -15,11 +15,11 @@ import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { makeInterface } from "../../../new_fields/Schema"; -import { List } from "../../../new_fields/List"; +import { makeInterface } from "../../../fields/Schema"; +import { List } from "../../../fields/List"; import { Docs } from "../../documents/Documents"; -import { PrefetchProxy } from "../../../new_fields/Proxy"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { PrefetchProxy } from "../../../fields/Proxy"; +import { ScriptField } from "../../../fields/ScriptField"; import { Scripting } from "../../util/Scripting"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index 2a21a380d..0fff0b57f 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -1,15 +1,15 @@ import React = require("react"); import { IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Id } from '../../../new_fields/FieldSymbols'; -import { makeInterface, listSpec } from "../../../new_fields/Schema"; -import { StrCast, Cast } from "../../../new_fields/Types"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { Id } from '../../../fields/FieldSymbols'; +import { makeInterface, listSpec } from "../../../fields/Schema"; +import { StrCast, Cast } from "../../../fields/Types"; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { SearchBox } from "../search/SearchBox"; import { FieldView, FieldViewProps } from './FieldView'; import "./QueryBox.scss"; -import { List } from "../../../new_fields/List"; +import { List } from "../../../fields/List"; import { SnappingManager } from "../../util/SnappingManager"; type QueryDocument = makeInterface<[typeof documentSchema]>; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index a0ecc9ff5..5d4af2d77 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -5,10 +5,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast } from "../../../new_fields/Types"; -import { VideoField } from "../../../new_fields/URLField"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { makeInterface } from "../../../fields/Schema"; +import { Cast, NumCast } from "../../../fields/Types"; +import { VideoField } from "../../../fields/URLField"; import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index c607d6614..0944edf60 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -1,10 +1,10 @@ import { action, observable, computed } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { StrCast, ScriptCast, Cast } from "../../../new_fields/Types"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { createSchema, makeInterface, listSpec } from "../../../fields/Schema"; +import { ScriptField } from "../../../fields/ScriptField"; +import { StrCast, ScriptCast, Cast } from "../../../fields/Types"; import { InteractionUtils } from "../../util/InteractionUtils"; import { CompileScript, isCompileError, ScriptParam } from "../../util/Scripting"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; @@ -13,7 +13,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./ScriptingBox.scss"; import { OverlayView } from "../OverlayView"; import { DocumentIconContainer } from "./DocumentIcon"; -import { List } from "../../../new_fields/List"; +import { List } from "../../../fields/List"; const ScriptingSchema = createSchema({}); type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>; diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx index cb2526769..9a1aefba9 100644 --- a/src/client/views/nodes/SliderBox.tsx +++ b/src/client/views/nodes/SliderBox.tsx @@ -4,10 +4,10 @@ import { runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider'; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { createSchema, makeInterface } from '../../../new_fields/Schema'; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; +import { documentSchema } from '../../../fields/documentSchemas'; +import { createSchema, makeInterface } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 9602eac52..ccf1f5588 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -5,12 +5,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; -import { Doc } from "../../../new_fields/Doc"; -import { InkTool } from "../../../new_fields/InkField"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../../new_fields/Types"; -import { VideoField } from "../../../new_fields/URLField"; +import { Doc } from "../../../fields/Doc"; +import { InkTool } from "../../../fields/InkField"; +import { createSchema, makeInterface } from "../../../fields/Schema"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../fields/Types"; +import { VideoField } from "../../../fields/URLField"; import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; @@ -21,7 +21,7 @@ import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import { documentSchema } from "../../../new_fields/documentSchemas"; +import { documentSchema } from "../../../fields/documentSchemas"; const path = require('path'); export const timeSchema = createSchema({ @@ -337,7 +337,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const curTime = (this.layoutDoc.currentTimecode || -1); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 6d4cc1a07..82f05012a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -2,13 +2,13 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons'; import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, FieldResult } from "../../../new_fields/Doc"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { HtmlField } from "../../../new_fields/HtmlField"; -import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; -import { WebField } from "../../../new_fields/URLField"; +import { Doc, FieldResult } from "../../../fields/Doc"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { HtmlField } from "../../../fields/HtmlField"; +import { InkTool } from "../../../fields/InkField"; +import { makeInterface } from "../../../fields/Schema"; +import { Cast, NumCast, BoolCast, StrCast } from "../../../fields/Types"; +import { WebField } from "../../../fields/URLField"; import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index d94fe7fc6..d56b87ae5 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -8,14 +8,14 @@ import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-s import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { List } from "../../../../new_fields/List"; -import { ObjectField } from "../../../../new_fields/ObjectField"; -import { listSpec } from "../../../../new_fields/Schema"; -import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; +import { List } from "../../../../fields/List"; +import { ObjectField } from "../../../../fields/ObjectField"; +import { listSpec } from "../../../../fields/Schema"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 7130fee2b..05e6a5959 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,10 +1,10 @@ import { IReactionDisposer, reaction } from "mobx"; import { NodeSelection } from "prosemirror-state"; -import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { ObjectField } from "../../../../new_fields/ObjectField"; -import { ComputedField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; +import { ObjectField } from "../../../../fields/ObjectField"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 3c6841f08..d05e8f1ea 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,10 +1,10 @@ import { IReactionDisposer, observable, runInAction, computed, action } from "mobx"; -import { Doc, DocListCast, Field } from "../../../../new_fields/Doc"; -import { List } from "../../../../new_fields/List"; -import { listSpec } from "../../../../new_fields/Schema"; -import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../../../new_fields/Types"; +import { Doc, DocListCast, Field } from "../../../../fields/Doc"; +import { List } from "../../../../fields/List"; +import { listSpec } from "../../../../fields/Schema"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../../fields/Types"; import { DocServer } from "../../../DocServer"; import { CollectionViewType } from "../../collections/CollectionView"; import { FormattedTextBox } from "./FormattedTextBox"; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 206c3db8c..5e33e7e70 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -12,17 +12,17 @@ import { Fragment, Mark, Node, Slice } from "prosemirror-model"; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; -import { DateField } from '../../../../new_fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; -import { documentSchema } from '../../../../new_fields/documentSchemas'; -import { Id } from '../../../../new_fields/FieldSymbols'; -import { InkTool } from '../../../../new_fields/InkField'; -import { PrefetchProxy } from '../../../../new_fields/Proxy'; -import { RichTextField } from "../../../../new_fields/RichTextField"; -import { RichTextUtils } from '../../../../new_fields/RichTextUtils'; -import { createSchema, makeInterface } from "../../../../new_fields/Schema"; -import { Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; -import { TraceMobx } from '../../../../new_fields/util'; +import { DateField } from '../../../../fields/DateField'; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../fields/Doc"; +import { documentSchema } from '../../../../fields/documentSchemas'; +import { Id } from '../../../../fields/FieldSymbols'; +import { InkTool } from '../../../../fields/InkField'; +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 } from "../../../../fields/Types"; +import { TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -58,7 +58,7 @@ import { FieldView, FieldViewProps } from "../FieldView"; import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); -import { ScriptField } from '../../../../new_fields/ScriptField'; +import { ScriptField } from '../../../../fields/ScriptField'; import GoogleAuthenticationManager from '../../../apis/GoogleAuthenticationManager'; library.add(faEdit); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 07aecf148..d47ae63af 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -2,8 +2,8 @@ import { Mark, ResolvedPos } from "prosemirror-model"; import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, DocCastAsync } from "../../../../new_fields/Doc"; -import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; +import { Doc, DocCastAsync } from "../../../../fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx index 8f98da0fd..401ecd7e6 100644 --- a/src/client/views/nodes/formattedText/ImageResizeView.tsx +++ b/src/client/views/nodes/formattedText/ImageResizeView.tsx @@ -1,5 +1,5 @@ import { NodeSelection } from "prosemirror-state"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../fields/Doc"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import React = require("react"); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index a0b02880e..2f7d23021 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -7,10 +7,10 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list"; import { EditorState, Transaction, TextSelection } from "prosemirror-state"; import { SelectionManager } from "../../../util/SelectionManager"; import { Docs } from "../../../documents/Documents"; -import { NumCast, BoolCast, Cast, StrCast } from "../../../../new_fields/Types"; -import { Doc } from "../../../../new_fields/Doc"; +import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types"; +import { Doc } from "../../../../fields/Doc"; import { FormattedTextBox } from "./FormattedTextBox"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Id } from "../../../../fields/FieldSymbols"; const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 170a39801..fd1b26208 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -11,14 +11,14 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons"; import { updateBullets } from "./ProsemirrorExampleTransfer"; import { FieldViewProps } from "../FieldView"; -import { Cast, StrCast } from "../../../../new_fields/Types"; +import { Cast, StrCast } from "../../../../fields/Types"; import { FormattedTextBoxProps } from "./FormattedTextBox"; import { unimplementedFunction, Utils } from "../../../../Utils"; import { wrapInList } from "prosemirror-schema-list"; -import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../new_fields/SchemaHeaderField'; +import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; import "./RichTextMenu.scss"; import { DocServer } from "../../../DocServer"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../fields/Doc"; import { SelectionManager } from "../../../util/SelectionManager"; import { LinkManager } from "../../../util/LinkManager"; const { toggleMark, setBlockType } = require("prosemirror-commands"); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 0ba591fec..fbd6c87bb 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,9 +1,9 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules"; import { NodeSelection, TextSelection } from "prosemirror-state"; -import { DataSym, Doc } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { ComputedField } from "../../../../new_fields/ScriptField"; -import { Cast, NumCast } from "../../../../new_fields/Types"; +import { DataSym, Doc } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { Cast, NumCast } from "../../../../fields/Types"; import { returnFalse, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -11,7 +11,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import { wrappingInputRule } from "./prosemirrorPatches"; import RichTextMenu from "./RichTextMenu"; import { schema } from "./schema_rts"; -import { List } from "../../../../new_fields/List"; +import { List } from "../../../../fields/List"; export class RichTextRules { public Document: Doc; diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index 7edd191cf..91280dea4 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -8,14 +8,14 @@ import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-s import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { List } from "../../../../new_fields/List"; -import { ObjectField } from "../../../../new_fields/ObjectField"; -import { listSpec } from "../../../../new_fields/Schema"; -import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types"; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; +import { List } from "../../../../fields/List"; +import { ObjectField } from "../../../../fields/ObjectField"; +import { listSpec } from "../../../../fields/Schema"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { ComputedField } from "../../../../fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 46bf481fb..ebaa23e99 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -1,6 +1,6 @@ import React = require("react"); import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../fields/Doc"; const emDOM: DOMOutputSpecArray = ["em", 0]; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 672d3adb8..cb6a15f36 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,10 +1,10 @@ import React = require("react"); import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 2a6eff7ff..ff328068b 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { unimplementedFunction, returnFalse } from "../../../Utils"; import AntimodeMenu from "../AntimodeMenu"; -import { Doc, Opt } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../fields/Doc"; @observer export default class PDFMenu extends AntimodeMenu { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index acaa4363e..c50969493 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -4,16 +4,16 @@ import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import * as rp from "request-promise"; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; -import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { InkTool } from "../../../new_fields/InkField"; -import { List } from "../../../new_fields/List"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, NumCast } from "../../../new_fields/Types"; -import { PdfField } from "../../../new_fields/URLField"; -import { TraceMobx } from "../../../new_fields/util"; +import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { Id } from "../../../fields/FieldSymbols"; +import { InkTool } from "../../../fields/InkField"; +import { List } from "../../../fields/List"; +import { createSchema, makeInterface } from "../../../fields/Schema"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, NumCast } from "../../../fields/Types"; +import { PdfField } from "../../../fields/URLField"; +import { TraceMobx } from "../../../fields/util"; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index e13a5f2f7..280ba9093 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -1,11 +1,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DataSym, DocListCast } from "../../../new_fields/Doc"; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id } from "../../../new_fields/FieldSymbols"; -import { createSchema, makeInterface } from '../../../new_fields/Schema'; -import { Cast, NumCast, BoolCast, ScriptCast } from "../../../new_fields/Types"; +import { Doc, DataSym, DocListCast } from "../../../fields/Doc"; +import { documentSchema } from '../../../fields/documentSchemas'; +import { Id } from "../../../fields/FieldSymbols"; +import { createSchema, makeInterface } from '../../../fields/Schema'; +import { Cast, NumCast, BoolCast, ScriptCast } from "../../../fields/Types"; import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionView'; diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 662b37d77..4b53963a5 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -4,10 +4,10 @@ import { observable, action } from 'mobx'; import "./SearchBox.scss"; import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { Doc } from '../../../new_fields/Doc'; -import { Id } from '../../../new_fields/FieldSymbols'; +import { Doc } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; import { DocumentType } from "../../documents/DocumentTypes"; -import { Cast, StrCast } from '../../../new_fields/Types'; +import { Cast, StrCast } from '../../../fields/Types'; import * as _ from "lodash"; import { IconBar } from './IconBar'; import { FieldFilters } from './FieldFilters'; diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index e41b725b1..eea7b528b 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -5,9 +5,9 @@ import { action, computed, observable, runInAction, IReactionDisposer, reaction import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; -import { Doc } from '../../../new_fields/Doc'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; +import { Doc } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; @@ -19,7 +19,7 @@ import { FieldView } from '../nodes/FieldView'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentView } from '../nodes/DocumentView'; import { SelectionManager } from '../../util/SelectionManager'; -import { listSpec } from '../../../new_fields/Schema'; +import { listSpec } from '../../../fields/Schema'; library.add(faTimes); diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 96f43e931..24d6e9d6f 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -4,9 +4,9 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Doc } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx index fd6b47ff0..d541c8009 100644 --- a/src/debug/Repl.tsx +++ b/src/debug/Repl.tsx @@ -3,9 +3,9 @@ import * as ReactDOM from 'react-dom'; import { observer } from 'mobx-react'; import { observable, computed } from 'mobx'; import { CompileScript } from '../client/util/Scripting'; -import { makeInterface } from '../new_fields/Schema'; -import { ObjectField } from '../new_fields/ObjectField'; -import { RefField } from '../new_fields/RefField'; +import { makeInterface } from '../fields/Schema'; +import { ObjectField } from '../fields/ObjectField'; +import { RefField } from '../fields/RefField'; import { DocServer } from '../client/DocServer'; @observer diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index 3baedce4b..17d3db8fd 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { DocServer } from '../client/DocServer'; -import { Doc } from '../new_fields/Doc'; +import { Doc } from '../fields/Doc'; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import { Utils } from '../Utils'; diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index a26d2e06a..6ce39d533 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -3,17 +3,17 @@ import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { observer } from 'mobx-react'; -import { Doc, Field, FieldResult, Opt } from '../new_fields/Doc'; +import { Doc, Field, FieldResult, Opt } from '../fields/Doc'; import { DocServer } from '../client/DocServer'; -import { Id } from '../new_fields/FieldSymbols'; -import { List } from '../new_fields/List'; -import { URLField } from '../new_fields/URLField'; +import { Id } from '../fields/FieldSymbols'; +import { List } from '../fields/List'; +import { URLField } from '../fields/URLField'; import { EditableView } from '../client/views/EditableView'; import { CompileScript } from '../client/util/Scripting'; -import { RichTextField } from '../new_fields/RichTextField'; -import { DateField } from '../new_fields/DateField'; -import { ScriptField } from '../new_fields/ScriptField'; -import CursorField from '../new_fields/CursorField'; +import { RichTextField } from '../fields/RichTextField'; +import { DateField } from '../fields/DateField'; +import { ScriptField } from '../fields/ScriptField'; +import CursorField from '../fields/CursorField'; DateField; URLField; diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts new file mode 100644 index 000000000..28467377b --- /dev/null +++ b/src/fields/CursorField.ts @@ -0,0 +1,66 @@ +import { ObjectField } from "./ObjectField"; +import { observable } from "mobx"; +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, createSimpleSchema, object, date } from "serializr"; +import { OnUpdate, ToScriptString, ToString, Copy } from "./FieldSymbols"; + +export type CursorPosition = { + x: number, + y: number +}; + +export type CursorMetadata = { + id: string, + identifier: string, + timestamp: number +}; + +export type CursorData = { + metadata: CursorMetadata, + position: CursorPosition +}; + +const PositionSchema = createSimpleSchema({ + x: true, + y: true +}); + +const MetadataSchema = createSimpleSchema({ + id: true, + identifier: true, + timestamp: true +}); + +const CursorSchema = createSimpleSchema({ + metadata: object(MetadataSchema), + position: object(PositionSchema) +}); + +@Deserializable("cursor") +export default class CursorField extends ObjectField { + + @serializable(object(CursorSchema)) + readonly data: CursorData; + + constructor(data: CursorData) { + super(); + this.data = data; + } + + setPosition(position: CursorPosition) { + this.data.position = position; + this.data.metadata.timestamp = Date.now(); + this[OnUpdate](); + } + + [Copy]() { + return new CursorField(this.data); + } + + [ToScriptString]() { + return "invalid"; + } + [ToString]() { + return "invalid"; + } +} \ No newline at end of file diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts new file mode 100644 index 000000000..a925148c2 --- /dev/null +++ b/src/fields/DateField.ts @@ -0,0 +1,36 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, date } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; +import { scriptingGlobal, Scripting } from "../client/util/Scripting"; + +@scriptingGlobal +@Deserializable("date") +export class DateField extends ObjectField { + @serializable(date()) + readonly date: Date; + + constructor(date: Date = new Date()) { + super(); + this.date = date; + } + + [Copy]() { + return new DateField(this.date); + } + + toString() { + return `${this.date.toISOString()}`; + } + + [ToScriptString]() { + return `new DateField(new Date(${this.date.toISOString()}))`; + } + [ToString]() { + return this.date.toISOString(); + } +} + +Scripting.addGlobal(function d(...dateArgs: any[]) { + return new DateField(new (Date as any)(...dateArgs)); +}); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts new file mode 100644 index 000000000..a1e1e11b1 --- /dev/null +++ b/src/fields/Doc.ts @@ -0,0 +1,1109 @@ +import { action, computed, observable, ObservableMap, runInAction } from "mobx"; +import { computedFn } from "mobx-utils"; +import { alias, map, serializable } from "serializr"; +import { DocServer } from "../client/DocServer"; +import { DocumentType } from "../client/documents/DocumentTypes"; +import { Scripting, scriptingGlobal } from "../client/util/Scripting"; +import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; +import { UndoManager } from "../client/util/UndoManager"; +import { intersectRect, Utils } from "../Utils"; +import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols"; +import { List } from "./List"; +import { ObjectField } from "./ObjectField"; +import { PrefetchProxy, ProxyField } from "./Proxy"; +import { FieldId, RefField } from "./RefField"; +import { RichTextField } from "./RichTextField"; +import { listSpec } from "./Schema"; +import { ComputedField, ScriptField } from "./ScriptField"; +import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; +import { Docs, DocumentOptions } from "../client/documents/Documents"; +import { PdfField, VideoField, AudioField, ImageField } from "./URLField"; +import { LinkManager } from "../client/util/LinkManager"; + +export namespace Field { + export function toKeyValueString(doc: Doc, key: string): string { + const onDelegate = Object.keys(doc).includes(key); + + const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + if (Field.IsField(field)) { + return (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); + } + return ""; + } + export function toScriptString(field: Field): string { + if (typeof field === "string") { + return `"${field}"`; + } else if (typeof field === "number" || typeof field === "boolean") { + return String(field); + } else { + return field[ToScriptString](); + } + } + export function toString(field: Field): string { + if (typeof field === "string") { + return field; + } else if (typeof field === "number" || typeof field === "boolean") { + return String(field); + } else if (field instanceof ObjectField) { + return field[ToString](); + } else if (field instanceof RefField) { + return field[ToString](); + } + return ""; + } + export function IsField(field: any): field is Field; + export function IsField(field: any, includeUndefined: true): field is Field | undefined; + export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined { + return (typeof field === "string") + || (typeof field === "number") + || (typeof field === "boolean") + || (field instanceof ObjectField) + || (field instanceof RefField) + || (includeUndefined && field === undefined); + } +} +export type Field = number | string | boolean | ObjectField | RefField; +export type Opt = T | undefined; +export type FieldWaiting = T extends undefined ? never : Promise; +export type FieldResult = Opt | FieldWaiting>; + +/** + * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs. + * If a default value is given, that will be returned instead of undefined. + * If a default value is given, the returned value should not be modified as it might be a temporary value. + * If no default value is given, and the returned value is not undefined, it can be safely modified. + */ +export function DocListCastAsync(field: FieldResult): Promise; +export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise; +export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { + const list = Cast(field, listSpec(Doc)); + return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); +} + +export async function DocCastAsync(field: FieldResult): Promise> { + return Cast(field, Doc); +} + +export function DocListCast(field: FieldResult): Doc[] { + return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; +} + +export const WidthSym = Symbol("Width"); +export const HeightSym = Symbol("Height"); +export const DataSym = Symbol("Data"); +export const LayoutSym = Symbol("Layout"); +export const UpdatingFromServer = Symbol("UpdatingFromServer"); +const CachedUpdates = Symbol("Cached updates"); + + +function fetchProto(doc: Doc) { + const proto = doc.proto; + if (proto instanceof Promise) { + return proto; + } +} + +@scriptingGlobal +@Deserializable("Doc", fetchProto).withFields(["id"]) +export class Doc extends RefField { + constructor(id?: FieldId, forceSave?: boolean) { + super(id); + const doc = new Proxy(this, { + set: setter, + get: getter, + // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter + has: (target, key) => key in target.__fields, + ownKeys: target => { + const obj = {} as any; + Object.assign(obj, target.___fields); + runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); + return Object.keys(obj); + }, + getOwnPropertyDescriptor: (target, prop) => { + if (prop.toString() === "__LAYOUT__") { + return Reflect.getOwnPropertyDescriptor(target, prop); + } + if (prop in target.__fields) { + return { + configurable: true,//TODO Should configurable be true? + enumerable: true, + value: target.__fields[prop] + }; + } + return Reflect.getOwnPropertyDescriptor(target, prop); + }, + deleteProperty: deleteProperty, + defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, + }); + this[SelfProxy] = doc; + if (!id || forceSave) { + DocServer.CreateField(doc); + } + return doc; + } + + proto: Opt; + [key: string]: FieldResult; + + @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize }))) + private get __fields() { return this.___fields; } + private set __fields(value) { + this.___fields = value; + for (const key in value) { + const field = value[key]; + if (!(field instanceof ObjectField)) continue; + field[Parent] = this[Self]; + field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); + } + } + + @observable + //{ [key: string]: Field | FieldWaiting | undefined } + private ___fields: any = {}; + + private [UpdatingFromServer]: boolean = false; + + private [Update] = (diff: any) => { + !this[UpdatingFromServer] && DocServer.UpdateField(this[Id], diff); + } + + private [Self] = this; + private [SelfProxy]: any; + public [WidthSym] = () => NumCast(this[SelfProxy]._width); + public [HeightSym] = () => NumCast(this[SelfProxy]._height); + public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } + public get [DataSym]() { + const self = this[SelfProxy]; + return self.resolvedDataDoc && !self.isTemplateForField ? self : + Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); + } + @computed get __LAYOUT__() { + const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); + if (templateLayoutDoc) { + let renderFieldKey: any; + const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")]; + if (typeof layoutField === "string") { + renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1]; + } else { + return Cast(layoutField, Doc, null); + } + return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc; + } + return undefined; + } + + [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } + [ToString]() { return `Doc(${this.title})`; } + + private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; + public static CurrentUserEmail: string = ""; + public async [HandleUpdate](diff: any) { + const set = diff.$set; + const sameAuthor = this.author === Doc.CurrentUserEmail; + if (set) { + for (const key in set) { + if (!key.startsWith("fields.")) { + continue; + } + const fKey = key.substring(7); + const fn = async () => { + const value = await SerializationHelper.Deserialize(set[key]); + this[UpdatingFromServer] = true; + this[fKey] = value; + this[UpdatingFromServer] = false; + }; + if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { + delete this[CachedUpdates][fKey]; + await fn(); + } else { + this[CachedUpdates][fKey] = fn; + } + } + } + const unset = diff.$unset; + if (unset) { + for (const key in unset) { + if (!key.startsWith("fields.")) { + continue; + } + const fKey = key.substring(7); + const fn = () => { + this[UpdatingFromServer] = true; + delete this[fKey]; + this[UpdatingFromServer] = false; + }; + if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { + delete this[CachedUpdates][fKey]; + await fn(); + } else { + this[CachedUpdates][fKey] = fn; + } + } + } + } +} + +export namespace Doc { + // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise { + // const self = doc[Self]; + // return new Promise(res => getField(self, key, ignoreProto, res)); + // } + // export function GetTAsync(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): Promise { + // return new Promise(async res => { + // const field = await GetAsync(doc, key, ignoreProto); + // return Cast(field, ctor); + // }); + // } + + export function RunCachedUpdate(doc: Doc, field: string) { + const update = doc[CachedUpdates][field]; + if (update) { + update(); + delete doc[CachedUpdates][field]; + } + } + export function AddCachedUpdate(doc: Doc, field: string, oldValue: any) { + const val = oldValue; + doc[CachedUpdates][field] = () => { + doc[UpdatingFromServer] = true; + doc[field] = val; + doc[UpdatingFromServer] = false; + }; + } + export function MakeReadOnly(): { end(): void } { + makeReadOnly(); + return { + end() { + makeEditable(); + } + }; + } + + export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult { + try { + return getField(doc[Self], key, ignoreProto); + } catch { + return doc; + } + } + export function GetT(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): FieldResult { + return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult; + } + export function IsPrototype(doc: Doc) { + return GetT(doc, "isPrototype", "boolean", true); + } + export function IsBaseProto(doc: Doc) { + return GetT(doc, "baseProto", "boolean", true); + } + export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { + const hasProto = doc.proto instanceof Doc; + const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; + const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; + if (onDeleg || !hasProto || (!onProto && !defaultProto)) { + doc[key] = value; + } else doc.proto![key] = value; + } + export async function SetOnPrototype(doc: Doc, key: string, value: Field) { + const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc; + + if (proto) { + proto[key] = value; + } + } + export function GetAllPrototypes(doc: Doc): Doc[] { + const protos: Doc[] = []; + let d: Opt = doc; + while (d) { + protos.push(d); + d = FieldValue(d.proto); + } + return protos; + } + + /** + * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies + * the values of the properties of a source object into the target. + * + * This is just a specific, Dash-authored version that serves the same role for our + * Doc class. + * + * @param doc the target document into which you'd like to insert the new fields + * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key + * to a potentially undefined field, where each entry in this mapping is optional. + */ + export function assign(doc: Doc, fields: Partial>>, skipUndefineds: boolean = false) { + for (const key in fields) { + if (fields.hasOwnProperty(key)) { + const value = fields[key]; + if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds? + doc[key] = value; + } + } + } + return doc; + } + + // compare whether documents or their protos match + export function AreProtosEqual(doc?: Doc, other?: Doc) { + if (!doc || !other) return false; + const r = (doc === other); + const r2 = (Doc.GetProto(doc) === other); + const r3 = (Doc.GetProto(other) === doc); + const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined); + return r || r2 || r3 || r4; + } + + // Gets the data document for the document. Note: this is mis-named -- it does not specifically + // return the doc's proto, but rather recursively searches through the proto inheritance chain + // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype'). + export function GetProto(doc: Doc): Doc { + if (doc instanceof Promise) { + console.log("GetProto: error: got Promise insead of Doc"); + } + const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); + return proto === doc ? proto : Doc.GetProto(proto); + } + export function GetDataDoc(doc: Doc): Doc { + const proto = Doc.GetProto(doc); + return proto === doc ? proto : Doc.GetDataDoc(proto); + } + + export function allKeys(doc: Doc): string[] { + const results: Set = new Set; + + let proto: Doc | undefined = doc; + while (proto) { + Object.keys(proto).forEach(key => results.add(key)); + proto = proto.proto; + } + + return Array.from(results); + } + + export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) { + let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1); + index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1); + return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); + } + export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) { + const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); + if (listDoc[key] === undefined) { + Doc.GetProto(listDoc)[key] = new List(); + } + const list = Cast(listDoc[key], listSpec(Doc)); + if (list) { + const ind = list.indexOf(doc); + if (ind !== -1) { + list.splice(ind, 1); + return true; + } + } + return false; + } + export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { + const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); + if (listDoc[key] === undefined) { + Doc.GetProto(listDoc)[key] = new List(); + } + const list = Cast(listDoc[key], listSpec(Doc)); + if (list) { + if (allowDuplicates !== true) { + const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); + if (pind !== -1) { + list.splice(pind, 1); + } + } + if (first) { + list.splice(0, 0, doc); + } + else { + const ind = relativeTo ? list.indexOf(relativeTo) : -1; + if (ind === -1) { + if (reversed) list.splice(0, 0, doc); + else list.push(doc); + } + else { + if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc); + else list.splice(before ? ind : ind + 1, 0, doc); + } + } + return true; + } + return false; + } + + // + // Computes the bounds of the contents of a set of documents. + // + export function ComputeContentBounds(docList: Doc[]) { + const bounds = docList.reduce((bounds, doc) => { + const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }); + return bounds; + } + + export function MakeAlias(doc: Doc, id?: string) { + const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); + const layout = Doc.LayoutField(alias); + if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) { + Doc.SetLayout(alias, Doc.MakeAlias(layout)); + } + alias.aliasOf = doc; + alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); + return alias; + } + + // + // Determines whether the layout needs to be expanded (as a template). + // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template + // + export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) { + return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc; + } + + const _pendingMap: Map = new Map(); + // + // Returns an expanded template layout for a target data document if there is a template relationship + // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties + // of the original layout while allowing for individual layout properties to be overridden in the expanded layout. + // templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key. + // NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field + // so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and + // the derefence will then occur on the rootDocument (the original document). + // in the future, field references could be written as @ and then arguments would be passed in the layout key as: + // layout_mytemplate(somparam=somearg). + // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument + export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) { + const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS); + if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc; + + const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders + // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc + // using the template layout doc's id as the field key. + // If it doesn't find the expanded layout, then it makes a delegate of the template layout and + // saves it on the data doc indexed by the template layout's id. + // + const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS"; + const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); + const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]"; + let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; + + if (templateLayoutDoc.resolvedDataDoc instanceof Promise) { + expandedTemplateLayout = undefined; + _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); + } + else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { + if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) { + expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params + } else { + _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true); + templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype + setTimeout(action(() => { + if (!targetDoc[expandedLayoutFieldKey]) { + const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"); + // the template's arguments are stored in params which is derefenced to find + // the actual field key where the parameterized template data is stored. + newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances + newLayoutDoc.rootDocument = targetDoc; + targetDoc[expandedLayoutFieldKey] = newLayoutDoc; + const dataDoc = Doc.GetProto(targetDoc); + newLayoutDoc.resolvedDataDoc = dataDoc; + if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List) { + dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc }); + } + _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); + } + }), 0); + } + } + return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending. + } + + // if the childDoc is a template for a field, then this will return the expanded layout with its data doc. + // otherwise, it just returns the childDoc + export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt, childDoc: Doc) { + if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) { + console.log("No, no, no!"); + return { layout: childDoc, data: childDoc }; + } + const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc); + return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc }; + } + + export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { + Object.keys(doc).forEach(key => { + const field = ProxyField.WithoutProxy(() => doc[key]); + if (key === "proto" && copyProto) { + if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) { + overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto); + } + } else { + if (field instanceof RefField) { + overwrite[key] = field; + } else if (field instanceof ObjectField) { + overwrite[key] = ObjectField.MakeCopy(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } else { + overwrite[key] = field; + } + } + }); + + return overwrite; + } + + export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { + const copy = new Doc(copyProtoId, true); + const exclude = Cast(doc.excludeFields, listSpec("string"), []); + Object.keys(doc).forEach(key => { + if (exclude.includes(key)) return; + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = ProxyField.WithoutProxy(() => doc[key]); + if (key === "proto" && copyProto) { + if (doc[key] instanceof Doc) { + copy[key] = Doc.MakeCopy(doc[key]!, false); + } + } else { + if (field instanceof RefField) { + copy[key] = field; + } else if (cfield instanceof ComputedField) { + copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); + } else if (field instanceof ObjectField) { + copy[key] = doc[key] instanceof Doc ? + key.includes("layout[") ? Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields + ObjectField.MakeCopy(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } else { + copy[key] = field; + } + } + }); + + return copy; + } + + export function MakeClone(doc: Doc): Doc { + const cloneMap = new Map(); + const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; + const copy = Doc.makeClone(doc, cloneMap, rtfMap); + rtfMap.map(({ copy, key, field }) => { + const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; + }; + const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return href + (mapped ? mapped[Id] : id); + }; + const regex = `(${Utils.prepend("/doc/")})([^"]*)`; + const re = new RegExp(regex, "g"); + copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); + }); + return copy; + } + + export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { + if (Doc.IsBaseProto(doc)) return doc; + if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; + const copy = new Doc(undefined, true); + cloneMap.set(doc[Id], copy); + if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); + const exclude = Cast(doc.excludeFields, listSpec("string"), []); + Object.keys(doc).forEach(key => { + if (exclude.includes(key)) return; + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = ProxyField.WithoutProxy(() => doc[key]); + const copyObjectField = (field: ObjectField) => { + const list = Cast(doc[key], listSpec(Doc)); + if (list !== undefined && !(list instanceof Promise)) { + copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); + } else if (doc[key] instanceof Doc) { + copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields + } else { + copy[key] = ObjectField.MakeCopy(field); + if (field instanceof RichTextField) { + if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { + rtfs.push({ copy, key, field }); + } + } + } + }; + if (key === "proto") { + if (doc[key] instanceof Doc) { + copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); + } + } else { + if (field instanceof RefField) { + copy[key] = field; + } else if (cfield instanceof ComputedField) { + copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); + (key === "links" && field instanceof ObjectField) && copyObjectField(field); + } else if (field instanceof ObjectField) { + copyObjectField(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happend... + } else { + copy[key] = field; + } + } + }); + Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); + copy.cloneOf = doc; + cloneMap.set(doc[Id], copy); + return copy; + } + + export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc; + export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt; + export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt { + if (doc) { + const delegate = new Doc(id, true); + delegate.proto = doc; + title && (delegate.title = title); + return delegate; + } + return undefined; + } + + let _applyCount: number = 0; + export function ApplyTemplate(templateDoc: Doc) { + if (templateDoc) { + const target = Doc.MakeDelegate(new Doc()); + const targetKey = StrCast(templateDoc.layoutKey, "layout"); + const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); + target.layoutKey = targetKey; + applied && (Doc.GetProto(applied).type = templateDoc.type); + return applied; + } + return undefined; + } + export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) { + if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) { + if (target.resolvedDataDoc) { + target[targetKey] = new PrefetchProxy(templateDoc); + } else { + titleTarget && (Doc.GetProto(target).title = titleTarget); + Doc.GetProto(target)[targetKey] = new PrefetchProxy(templateDoc); + } + } + return target; + } + + // + // This function converts a generic field layout display into a field layout that displays a specific + // metadata field indicated by the title of the template field (not the default field that it was rendering) + // + export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt): boolean { + + // find the metadata field key that this template field doc will display (indicated by its title) + const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ""); + + // update the original template to mark it as a template + templateField.isTemplateForField = metadataFieldKey; + templateField.title = metadataFieldKey; + + const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)]; + const templateCaptionValue = templateField.caption; + // move any data that the template field had been rendering over to the template doc so that things will still be rendered + // when the template field is adjusted to point to the new metadatafield key. + // note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well. + // note 2: this will not overwrite any field that already exists on the template doc at the field key + if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) { + Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc)); + (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue)); + } + // copy the textTemplates from 'this' (not 'self') because the layout contains the template info, not the original doc + if (templateCaptionValue instanceof RichTextField && !templateCaptionValue.Empty()) { + templateField["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`); + } + if (templateFieldValue instanceof RichTextField && !templateFieldValue.Empty()) { + templateField[metadataFieldKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${metadataFieldKey})`); + } + + // get the layout string that the template uses to specify its layout + const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField))); + + // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document. + Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`); + + // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino) + Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc); + + return true; + } + + export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { + const doc2Layout = Doc.Layout(doc2); + const doc1Layout = Doc.Layout(doc1); + const x2 = NumCast(doc2.x) - clusterDistance; + const y2 = NumCast(doc2.y) - clusterDistance; + const w2 = NumCast(doc2Layout._width) + clusterDistance; + const h2 = NumCast(doc2Layout._height) + clusterDistance; + const x = NumCast(doc1.x) - clusterDistance; + const y = NumCast(doc1.y) - clusterDistance; + const w = NumCast(doc1Layout._width) + clusterDistance; + const h = NumCast(doc1Layout._height) + clusterDistance; + return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); + } + + export function isBrushedHighlightedDegree(doc: Doc) { + if (Doc.IsHighlighted(doc)) { + return 6; + } + else { + return Doc.IsBrushedDegree(doc); + } + } + + export class DocBrush { + BrushedDoc: ObservableMap = new ObservableMap(); + } + const brushManager = new DocBrush(); + + export class DocData { + @observable _user_doc: Doc = undefined!; + @observable _searchQuery: string = ""; + } + + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field or 'layout' is given. + export function Layout(doc: Doc, layout?: Doc): Doc { + const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null); + return overrideLayout || doc[LayoutSym] || doc; + } + export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; } + export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } + export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; } + const manager = new DocData(); + export function SearchQuery(): string { return manager._searchQuery; } + export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } + export function UserDoc(): Doc { return manager._user_doc; } + export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } + export function IsBrushed(doc: Doc) { + return computedFn(function IsBrushed(doc: Doc) { + return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); + })(doc); + } + // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) + export function IsBrushedDegreeUnmemoized(doc: Doc) { + return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; + } + export function IsBrushedDegree(doc: Doc) { + return computedFn(function IsBrushDegree(doc: Doc) { + return Doc.IsBrushedDegreeUnmemoized(doc); + })(doc); + } + export function BrushDoc(doc: Doc) { + brushManager.BrushedDoc.set(doc, true); + brushManager.BrushedDoc.set(Doc.GetProto(doc), true); + return doc; + } + export function UnBrushDoc(doc: Doc) { + brushManager.BrushedDoc.delete(doc); + brushManager.BrushedDoc.delete(Doc.GetProto(doc)); + return doc; + } + + + export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; } + + export function linkFollowUnhighlight() { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", linkFollowUnhighlight); + } + + let _lastDate = 0; + export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) { + linkFollowUnhighlight(); + Doc.HighlightDoc(destDoc, dataAndDisplayDocs); + document.removeEventListener("pointerdown", linkFollowUnhighlight); + document.addEventListener("pointerdown", linkFollowUnhighlight); + const lastDate = _lastDate = Date.now(); + window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); + } + + export class HighlightBrush { + @observable HighlightedDoc: Map = new Map(); + } + const highlightManager = new HighlightBrush(); + export function IsHighlighted(doc: Doc) { + return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); + } + export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { + runInAction(() => { + highlightManager.HighlightedDoc.set(doc, true); + dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true); + }); + } + export function UnHighlightDoc(doc: Doc) { + runInAction(() => { + highlightManager.HighlightedDoc.set(doc, false); + highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false); + }); + } + export function UnhighlightAll() { + const mapEntries = highlightManager.HighlightedDoc.keys(); + let docEntry: IteratorResult; + while (!(docEntry = mapEntries.next()).done) { + const targetDoc = docEntry.value; + targetDoc && Doc.UnHighlightDoc(targetDoc); + } + + } + export function UnBrushAllDocs() { + brushManager.BrushedDoc.clear(); + } + + export function getDocTemplate(doc?: Doc) { + return doc?.isTemplateDoc ? doc : + Cast(doc?.dragFactory, Doc, null)?.isTemplateDoc ? doc?.dragFactory : + Cast(doc?.layout, Doc, null)?.isTemplateDoc ? doc?.layout : undefined; + } + + export function matchFieldValue(doc: Doc, key: string, value: any): boolean { + const fieldVal = doc[key]; + if (Cast(fieldVal, listSpec("string"), []).length) { + const vals = Cast(fieldVal, listSpec("string"), []); + return vals.some(v => v === value); + } + const fieldStr = Field.toString(fieldVal as Field); + return fieldStr === value; + } + + export function deiconifyView(doc: any) { + StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); + } + + export function setNativeView(doc: any) { + const prevLayout = StrCast(doc.layoutKey).split("_")[1]; + const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : ""; + prevLayout === "icon" && (doc.deiconifyLayout = undefined); + doc.layoutKey = deiconify || "layout"; + } + export function setDocFilterRange(target: Doc, key: string, range?: number[]) { + const docRangeFilters = Cast(target._docRangeFilters, listSpec("string"), []); + for (let i = 0; i < docRangeFilters.length; i += 3) { + if (docRangeFilters[i] === key) { + docRangeFilters.splice(i, 3); + break; + } + } + if (range !== undefined) { + docRangeFilters.push(key); + docRangeFilters.push(range[0].toString()); + docRangeFilters.push(range[1].toString()); + target._docRangeFilters = new List(docRangeFilters); + } + } + + export function aliasDocs(field: any) { + return new List(field.map((d: any) => Doc.MakeAlias(d))); + } + + // filters document in a container collection: + // all documents with the specified value for the specified key are included/excluded + // based on the modifiers :"check", "x", undefined + export function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { + const docFilters = Cast(container._docFilters, listSpec("string"), []); + for (let i = 0; i < docFilters.length; i += 3) { + if (docFilters[i] === key && docFilters[i + 1] === value) { + docFilters.splice(i, 3); + break; + } + } + if (typeof modifiers === "string") { + docFilters.push(key); + docFilters.push(value); + docFilters.push(modifiers); + container._docFilters = new List(docFilters); + } + } + export function readDocRangeFilter(doc: Doc, key: string) { + const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []); + for (let i = 0; i < docRangeFilters.length; i += 3) { + if (docRangeFilters[i] === key) { + return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])]; + } + } + } + export function assignDocToField(doc: Doc, field: string, id: string) { + DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout)); + return id; + } + + export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) { + runInAction(() => { + if (layoutDoc._nativeWidth || layoutDoc._nativeHeight) { + layoutDoc.scale = NumCast(layoutDoc.scale, 1) * contentScale; + layoutDoc._nativeWidth = undefined; + layoutDoc._nativeHeight = undefined; + } + else { + layoutDoc._autoHeight = false; + if (!layoutDoc._nativeWidth) { + layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); + layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight); + } + } + }); + } + + export function isDocPinned(doc: Doc) { + //add this new doc to props.Document + const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + if (curPres) { + return DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; + } + return false; + } + + // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) + export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) { + const batch = UndoManager.StartBatch("makeCustomViewClicked"); + runInAction(() => { + doc.layoutKey = "layout_" + templateSignature; + if (doc[doc.layoutKey] === undefined) { + createCustomView(doc, creator, templateSignature, docLayoutTemplate); + } + }); + batch.end(); + } + export function findTemplate(templateName: string, type: string, signature: string) { + let docLayoutTemplate: Opt; + const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data); + const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); + const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data); + const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data); + const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc); + // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized + // first try to find a template that matches the specific document type (_). otherwise, fallback to a general match on + !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc)); + !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc)); + return docLayoutTemplate; + } + export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) { + const templateName = templateSignature.replace(/\(.*\)/, ""); + docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature); + + const customName = "layout_" + templateSignature; + const _width = NumCast(doc._width); + const _height = NumCast(doc._height); + const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; + + let fieldTemplate: Opt; + if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { + fieldTemplate = Docs.Create.TextDocument("", options); + } else if (doc.data instanceof PdfField) { + fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); + } else if (doc.data instanceof VideoField) { + fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof AudioField) { + fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof ImageField) { + fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + } + const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); + + fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); + docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + } + export function makeCustomView(doc: Doc, custom: boolean, layout: string) { + Doc.setNativeView(doc); + if (custom) { + makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined); + } + } + export function iconify(doc: Doc) { + const layoutKey = Cast(doc.layoutKey, "string", null); + Doc.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined); + if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); + } + + export function pileup(selected: Doc[], x: number, y: number) { + const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true }); + let w = 0, h = 0; + selected.forEach((d, i) => { + Doc.iconify(d); + w = Math.max(d[WidthSym](), w); + h = Math.max(d[HeightSym](), h); + }); + h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable + selected.forEach((d, i) => { + d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2; + d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2; + d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + }); + newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; + newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; + newCollection._width = newCollection._height = 110; + //newCollection.borderRounding = "40px"; + newCollection._jitterRotation = 10; + newCollection._backgroundColor = "gray"; + newCollection._overflow = "visible"; + return newCollection; + } + + + export async function addFieldEnumerations(doc: Opt, 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); + Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc); + } + const options = optionsCollection as Doc; + const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc); + const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`; + targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options })); + targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options })); + targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options })); + enumerations.map(enumeration => { + const found = DocListCast(options.data).find(d => d.title === enumeration.title); + if (found) { + found._backgroundColor = enumeration._backgroundColor || found._backgroundColor; + found._color = enumeration.color || found._color; + } else { + Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, enumeration)); + } + }); + return optionsCollection; + } +} + +Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; }); +Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); +Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); +Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); +Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); +Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); +Scripting.addGlobal(function aliasDocs(field: any) { return Doc.aliasDocs(field); }); +Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); +Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); +Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); +Scripting.addGlobal(function deiconifyView(doc: any) { Doc.deiconifyView(doc); }); +Scripting.addGlobal(function undo() { return UndoManager.Undo(); }); +Scripting.addGlobal(function redo() { return UndoManager.Redo(); }); +Scripting.addGlobal(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; }); +Scripting.addGlobal(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); +Scripting.addGlobal(function docCast(doc: FieldResult): any { return DocCastAsync(doc); }); +Scripting.addGlobal(function activePresentationItem() { + const curPres = Doc.UserDoc().activePresentation as Doc; + return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; +}); +Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().activeSelection = new List([doc]); }); +Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { + const docs = DocListCast(Doc.UserDoc().activeSelection). + filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCHOLDER && d.type !== DocumentType.KVP && + (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); + return docs.length ? new List(docs) : prevValue; +}); +Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); }); +Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); }); \ No newline at end of file diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts new file mode 100644 index 000000000..8d040f493 --- /dev/null +++ b/src/fields/FieldSymbols.ts @@ -0,0 +1,12 @@ + +export const Update = Symbol("Update"); +export const Self = Symbol("Self"); +export const SelfProxy = Symbol("SelfProxy"); +export const HandleUpdate = Symbol("HandleUpdate"); +export const Id = Symbol("Id"); +export const OnUpdate = Symbol("OnUpdate"); +export const Parent = Symbol("Parent"); +export const Copy = Symbol("Copy"); +export const ToScriptString = Symbol("ToScriptString"); +export const ToPlainText = Symbol("ToPlainText"); +export const ToString = Symbol("ToString"); diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts new file mode 100644 index 000000000..6e8bba977 --- /dev/null +++ b/src/fields/HtmlField.ts @@ -0,0 +1,26 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, primitive } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString, ToString} from "./FieldSymbols"; + +@Deserializable("html") +export class HtmlField extends ObjectField { + @serializable(primitive()) + readonly html: string; + + constructor(html: string) { + super(); + this.html = html; + } + + [Copy]() { + return new HtmlField(this.html); + } + + [ToScriptString]() { + return "invalid"; + } + [ToString]() { + return this.html; + } +} diff --git a/src/fields/IconField.ts b/src/fields/IconField.ts new file mode 100644 index 000000000..76c4ddf1b --- /dev/null +++ b/src/fields/IconField.ts @@ -0,0 +1,26 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, primitive } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; + +@Deserializable("icon") +export class IconField extends ObjectField { + @serializable(primitive()) + readonly icon: string; + + constructor(icon: string) { + super(); + this.icon = icon; + } + + [Copy]() { + return new IconField(this.icon); + } + + [ToScriptString]() { + return "invalid"; + } + [ToString]() { + return "ICONfield"; + } +} diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts new file mode 100644 index 000000000..bb93de5ac --- /dev/null +++ b/src/fields/InkField.ts @@ -0,0 +1,50 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, custom, createSimpleSchema, list, object, map } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; + +export enum InkTool { + None, + Pen, + Highlighter, + Eraser, + Stamp +} + +export interface PointData { + X: number; + Y: number; +} + +export type InkData = Array; + +const pointSchema = createSimpleSchema({ + X: true, Y: true +}); + +const strokeDataSchema = createSimpleSchema({ + pathData: list(object(pointSchema)), + "*": true +}); + +@Deserializable("ink") +export class InkField extends ObjectField { + @serializable(list(object(strokeDataSchema))) + readonly inkData: InkData; + + constructor(data: InkData) { + super(); + this.inkData = data; + } + + [Copy]() { + return new InkField(this.inkData); + } + + [ToScriptString]() { + return "invalid"; + } + [ToString]() { + return "InkField"; + } +} diff --git a/src/fields/List.ts b/src/fields/List.ts new file mode 100644 index 000000000..fdabea365 --- /dev/null +++ b/src/fields/List.ts @@ -0,0 +1,330 @@ +import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/SerializationHelper"; +import { Field } from "./Doc"; +import { setter, getter, deleteProperty, updateFunction } from "./util"; +import { serializable, alias, list } from "serializr"; +import { observable, action, runInAction } from "mobx"; +import { ObjectField } from "./ObjectField"; +import { RefField } from "./RefField"; +import { ProxyField } from "./Proxy"; +import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy, Id } from "./FieldSymbols"; +import { Scripting } from "../client/util/Scripting"; +import { DocServer } from "../client/DocServer"; + +const listHandlers: any = { + /// Mutator methods + copyWithin() { + throw new Error("copyWithin not supported yet"); + }, + fill(value: any, start?: number, end?: number) { + if (value instanceof RefField) { + throw new Error("fill with RefFields not supported yet"); + } + const res = this[Self].__fields.fill(value, start, end); + this[Update](); + return res; + }, + pop(): any { + const field = toRealField(this[Self].__fields.pop()); + this[Update](); + return field; + }, + push: action(function (this: any, ...items: any[]) { + items = items.map(toObjectField); + const list = this[Self]; + const length = list.__fields.length; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + //TODO Error checking to make sure parent doesn't already exist + if (item instanceof ObjectField) { + item[Parent] = list; + item[OnUpdate] = updateFunction(list, i + length, item, this); + } + } + const res = list.__fields.push(...items); + this[Update](); + return res; + }), + reverse() { + const res = this[Self].__fields.reverse(); + this[Update](); + return res; + }, + shift() { + const res = toRealField(this[Self].__fields.shift()); + this[Update](); + return res; + }, + sort(cmpFunc: any) { + this[Self].__realFields(); // coerce retrieving entire array + const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined); + this[Update](); + return res; + }, + splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) { + this[Self].__realFields(); // coerce retrieving entire array + items = items.map(toObjectField); + const list = this[Self]; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + //TODO Error checking to make sure parent doesn't already exist + //TODO Need to change indices of other fields in array + if (item instanceof ObjectField) { + item[Parent] = list; + item[OnUpdate] = updateFunction(list, i + start, item, this); + } + } + const res = list.__fields.splice(start, deleteCount, ...items); + this[Update](); + return res.map(toRealField); + }), + unshift(...items: any[]) { + items = items.map(toObjectField); + const list = this[Self]; + const length = list.__fields.length; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + //TODO Error checking to make sure parent doesn't already exist + //TODO Need to change indices of other fields in array + if (item instanceof ObjectField) { + item[Parent] = list; + item[OnUpdate] = updateFunction(list, i, item, this); + } + } + const res = this[Self].__fields.unshift(...items); + this[Update](); + return res; + + }, + /// Accessor methods + concat: action(function (this: any, ...items: any[]) { + this[Self].__realFields(); + return this[Self].__fields.map(toRealField).concat(...items); + }), + includes(valueToFind: any, fromIndex: number) { + if (valueToFind instanceof RefField) { + return this[Self].__realFields().includes(valueToFind, fromIndex); + } else { + return this[Self].__fields.includes(valueToFind, fromIndex); + } + }, + indexOf(valueToFind: any, fromIndex: number) { + if (valueToFind instanceof RefField) { + return this[Self].__realFields().indexOf(valueToFind, fromIndex); + } else { + return this[Self].__fields.indexOf(valueToFind, fromIndex); + } + }, + join(separator: any) { + this[Self].__realFields(); + return this[Self].__fields.map(toRealField).join(separator); + }, + lastIndexOf(valueToFind: any, fromIndex: number) { + if (valueToFind instanceof RefField) { + return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex); + } else { + return this[Self].__fields.lastIndexOf(valueToFind, fromIndex); + } + }, + slice(begin: number, end: number) { + this[Self].__realFields(); + return this[Self].__fields.slice(begin, end).map(toRealField); + }, + + /// Iteration methods + entries() { + return this[Self].__realFields().entries(); + }, + every(callback: any, thisArg: any) { + return this[Self].__realFields().every(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + filter(callback: any, thisArg: any) { + return this[Self].__realFields().filter(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + find(callback: any, thisArg: any) { + return this[Self].__realFields().find(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + findIndex(callback: any, thisArg: any) { + return this[Self].__realFields().findIndex(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + forEach(callback: any, thisArg: any) { + return this[Self].__realFields().forEach(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + map(callback: any, thisArg: any) { + return this[Self].__realFields().map(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + reduce(callback: any, initialValue: any) { + return this[Self].__realFields().reduce(callback, initialValue); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + }, + reduceRight(callback: any, initialValue: any) { + return this[Self].__realFields().reduceRight(callback, initialValue); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + }, + some(callback: any, thisArg: any) { + return this[Self].__realFields().some(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + values() { + return this[Self].__realFields().values(); + }, + [Symbol.iterator]() { + return this[Self].__realFields().values(); + } +}; + +function toObjectField(field: Field) { + return field instanceof RefField ? new ProxyField(field) : field; +} + +function toRealField(field: Field) { + return field instanceof ProxyField ? field.value() : field; +} + +function listGetter(target: any, prop: string | number | symbol, receiver: any): any { + if (listHandlers.hasOwnProperty(prop)) { + return listHandlers[prop]; + } + return getter(target, prop, receiver); +} + +interface ListSpliceUpdate { + type: "splice"; + index: number; + added: T[]; + removedCount: number; +} + +interface ListIndexUpdate { + type: "update"; + index: number; + newValue: T; +} + +type ListUpdate = ListSpliceUpdate | ListIndexUpdate; + +type StoredType = T extends RefField ? ProxyField : T; + +@Deserializable("list") +class ListImpl extends ObjectField { + constructor(fields?: T[]) { + super(); + const list = new Proxy(this, { + set: setter, + get: listGetter, + ownKeys: target => Object.keys(target.__fields), + getOwnPropertyDescriptor: (target, prop) => { + if (prop in target.__fields) { + return { + configurable: true,//TODO Should configurable be true? + enumerable: true, + }; + } + return Reflect.getOwnPropertyDescriptor(target, prop); + }, + deleteProperty: deleteProperty, + defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, + }); + this[SelfProxy] = list; + if (fields) { + (list as any).push(...fields); + } + return list; + } + + [key: number]: T | (T extends RefField ? Promise : never); + + // this requests all ProxyFields at the same time to avoid the overhead + // of separate network requests and separate updates to the React dom. + private __realFields() { + const waiting = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()); + const promised = waiting.map(f => f instanceof ProxyField ? f.promisedValue() : ""); + // if we find any ProxyFields that don't have a current value, then + // start the server request for all of them + if (promised.length) { + const promise = DocServer.GetRefFields(promised); + // as soon as we get the fields from the server, set all the list values in one + // action to generate one React dom update. + promise.then(fields => runInAction(() => { + waiting.map((w, i) => w instanceof ProxyField && w.setValue(fields[promised[i]])); + })); + // we also have to mark all lists items with this promise so that any calls to them + // will await the batch request. + // This counts on the handler for 'promise' in the call above being invoked before the + // handler for 'promise' in the lines below. + waiting.map((w, i) => { + w instanceof ProxyField && w.setPromise(promise.then(fields => fields[promised[i]])); + }); + } + return this.__fields.map(toRealField); + } + + @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize }))) + private get __fields() { + return this.___fields; + } + + private set __fields(value) { + this.___fields = value; + for (const key in value) { + const field = value[key]; + if (!(field instanceof ObjectField)) continue; + (field as ObjectField)[Parent] = this[Self]; + (field as ObjectField)[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); + } + } + + [Copy]() { + const copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f); + const deepCopy = new ListImpl(copiedData as any); + return deepCopy; + } + + // @serializable(alias("fields", list(autoObject()))) + @observable + private ___fields: StoredType[] = []; + + private [Update] = (diff: any) => { + // console.log(diff); + const update = this[OnUpdate]; + // update && update(diff); + update?.(); + } + + private [Self] = this; + private [SelfProxy]: any; + + [ToScriptString]() { + return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; + } + [ToString]() { + return "List"; + } +} +export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; +export const List: { new (fields?: T[]): List } = ListImpl as any; + +Scripting.addGlobal("List", List); \ No newline at end of file diff --git a/src/fields/ListSpec.ts b/src/fields/ListSpec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts new file mode 100644 index 000000000..9aa1c9b04 --- /dev/null +++ b/src/fields/ObjectField.ts @@ -0,0 +1,20 @@ +import { RefField } from "./RefField"; +import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols"; +import { Scripting } from "../client/util/Scripting"; + +export abstract class ObjectField { + protected [OnUpdate](diff?: any) { } + private [Parent]?: RefField | ObjectField; + abstract [Copy](): ObjectField; + + abstract [ToScriptString](): string; + abstract [ToString](): string; +} + +export namespace ObjectField { + export function MakeCopy(field: T) { + return field?.[Copy](); + } +} + +Scripting.addGlobal(ObjectField); \ No newline at end of file diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts new file mode 100644 index 000000000..f236a04fd --- /dev/null +++ b/src/fields/PresField.ts @@ -0,0 +1,6 @@ +//insert code here +import { ObjectField } from "./ObjectField"; + +export abstract class PresField extends ObjectField { + +} \ No newline at end of file diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts new file mode 100644 index 000000000..555faaad0 --- /dev/null +++ b/src/fields/Proxy.ts @@ -0,0 +1,126 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { FieldWaiting } from "./Doc"; +import { primitive, serializable } from "serializr"; +import { observable, action, runInAction } from "mobx"; +import { DocServer } from "../client/DocServer"; +import { RefField } from "./RefField"; +import { ObjectField } from "./ObjectField"; +import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols"; +import { scriptingGlobal } from "../client/util/Scripting"; +import { Plugins } from "./util"; + +@Deserializable("proxy") +export class ProxyField extends ObjectField { + constructor(); + constructor(value: T); + constructor(fieldId: string); + constructor(value?: T | string) { + super(); + if (typeof value === "string") { + this.fieldId = value; + } else if (value) { + this.cache = value; + this.fieldId = value[Id]; + } + } + + [Copy]() { + if (this.cache) return new ProxyField(this.cache); + return new ProxyField(this.fieldId); + } + + [ToScriptString]() { + return "invalid"; + } + [ToString]() { + return "ProxyField"; + } + + @serializable(primitive()) + readonly fieldId: string = ""; + + // This getter/setter and nested object thing is + // because mobx doesn't play well with observable proxies + @observable.ref + private _cache: { readonly field: T | undefined } = { field: undefined }; + private get cache(): T | undefined { + return this._cache.field; + } + private set cache(field: T | undefined) { + this._cache = { field }; + } + + private failed = false; + private promise?: Promise; + + value(): T | undefined | FieldWaiting { + if (this.cache) { + return this.cache; + } + if (this.failed) { + return undefined; + } + if (!this.promise) { + const cached = DocServer.GetCachedRefField(this.fieldId); + if (cached !== undefined) { + runInAction(() => this.cache = cached as any); + return cached as any; + } + this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => { + this.promise = undefined; + this.cache = field; + if (field === undefined) this.failed = true; + return field; + })); + } + return this.promise as any; + } + promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; } + setPromise(promise: any) { + this.promise = promise; + } + @action + setValue(field: any) { + this.promise = undefined; + this.cache = field; + if (field === undefined) this.failed = true; + return field; + } +} + +export namespace ProxyField { + let useProxy = true; + export function DisableProxyFields() { + useProxy = false; + } + + export function EnableProxyFields() { + useProxy = true; + } + + export function WithoutProxy(fn: () => T) { + DisableProxyFields(); + try { + return fn(); + } finally { + EnableProxyFields(); + } + } + + export function initPlugin() { + Plugins.addGetterPlugin((doc, _, value) => { + if (useProxy && value instanceof ProxyField) { + return { value: value.value() }; + } + }); + } +} + +function prefetchValue(proxy: PrefetchProxy) { + return proxy.value() as any; +} + +@scriptingGlobal +@Deserializable("prefetch_proxy", prefetchValue) +export class PrefetchProxy extends ProxyField { +} diff --git a/src/fields/RefField.ts b/src/fields/RefField.ts new file mode 100644 index 000000000..b6ef69750 --- /dev/null +++ b/src/fields/RefField.ts @@ -0,0 +1,21 @@ +import { serializable, primitive, alias } from "serializr"; +import { Utils } from "../Utils"; +import { Id, HandleUpdate, ToScriptString, ToString } from "./FieldSymbols"; +import { afterDocDeserialize } from "../client/util/SerializationHelper"; + +export type FieldId = string; +export abstract class RefField { + @serializable(alias("id", primitive({ afterDeserialize: afterDocDeserialize }))) + private __id: FieldId; + readonly [Id]: FieldId; + + constructor(id?: FieldId) { + this.__id = id || Utils.GenerateGuid(); + this[Id] = this.__id; + } + + protected [HandleUpdate]?(diff: any): void | Promise; + + abstract [ToScriptString](): string; + abstract [ToString](): string; +} diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts new file mode 100644 index 000000000..5cf0e0cc3 --- /dev/null +++ b/src/fields/RichTextField.ts @@ -0,0 +1,41 @@ +import { ObjectField } from "./ObjectField"; +import { serializable } from "serializr"; +import { Deserializable } from "../client/util/SerializationHelper"; +import { Copy, ToScriptString, ToPlainText, ToString } from "./FieldSymbols"; +import { scriptingGlobal } from "../client/util/Scripting"; + +@scriptingGlobal +@Deserializable("RichTextField") +export class RichTextField extends ObjectField { + @serializable(true) + readonly Data: string; + + @serializable(true) + readonly Text: string; + + constructor(data: string, text: string = "") { + super(); + this.Data = data; + this.Text = text; + } + + Empty() { + return !(this.Text || this.Data.toString().includes("dashField")); + } + + [Copy]() { + return new RichTextField(this.Data, this.Text); + } + + [ToScriptString]() { + return `new RichTextField("${this.Data}", "${this.Text}")`; + } + [ToString]() { + return this.Text; + } + + public static DashField(fieldKey: string) { + return 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":"${fieldKey}","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`, ""); + } + +} \ No newline at end of file diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts new file mode 100644 index 000000000..c475d0d73 --- /dev/null +++ b/src/fields/RichTextUtils.ts @@ -0,0 +1,519 @@ +import { AssertionError } from "assert"; +import { docs_v1 } from "googleapis"; +import { Fragment, Mark, Node } from "prosemirror-model"; +import { sinkListItem } from "prosemirror-schema-list"; +import { Utils } from "../Utils"; +import { Docs } from "../client/documents/Documents"; +import { schema } from "../client/views/nodes/formattedText/schema_rts"; +import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; +import { DocServer } from "../client/DocServer"; +import { Networking } from "../client/Network"; +import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox"; +import { Doc, Opt } from "./Doc"; +import { Id } from "./FieldSymbols"; +import { RichTextField } from "./RichTextField"; +import { Cast, StrCast } from "./Types"; +import Color = require('color'); +import { EditorState, TextSelection, Transaction } from "prosemirror-state"; +import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; + +export namespace RichTextUtils { + + const delimiter = "\n"; + const joiner = ""; + + + export const Initialize = (initial?: string) => { + const content: any[] = []; + const state = { + doc: { + type: "doc", + content, + }, + selection: { + type: "text", + anchor: 0, + head: 0 + } + }; + if (initial && initial.length) { + content.push({ + type: "paragraph", + content: { + type: "text", + text: initial + } + }); + state.selection.anchor = state.selection.head = initial.length + 1; + } + return JSON.stringify(state); + }; + + export const Synthesize = (plainText: string, oldState?: RichTextField) => { + return new RichTextField(ToProsemirrorState(plainText, oldState), plainText); + }; + + export const ToPlainText = (state: EditorState) => { + // Because we're working with plain text, just concatenate all paragraphs + const content = state.doc.content; + const paragraphs: Node[] = []; + content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node)); + + // Functions to flatten ProseMirror paragraph objects (and their components) to plain text + // Concatentate paragraphs and string the result together + const textParagraphs: string[] = paragraphs.map(paragraph => { + const text: string[] = []; + paragraph.content.forEach(node => node.text && text.push(node.text)); + return text.join(joiner) + delimiter; + }); + const plainText = textParagraphs.join(joiner); + return plainText.substring(0, plainText.length - 1); + }; + + export const ToProsemirrorState = (plainText: string, oldState?: RichTextField) => { + // Remap the text, creating blocks split on newlines + const elements = plainText.split(delimiter); + + // Google Docs adds in an extra carriage return automatically, so this counteracts it + !elements[elements.length - 1].length && elements.pop(); + + // Preserve the current state, but re-write the content to be the blocks + const parsed = JSON.parse(oldState ? oldState.Data : Initialize()); + parsed.doc.content = elements.map(text => { + const paragraph: any = { type: "paragraph" }; + text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break + return paragraph; + }); + + // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it + parsed.selection = { type: "text", anchor: 1, head: 1 }; + + // Export the ProseMirror-compatible state object we've just built + return JSON.stringify(parsed); + }; + + export namespace GoogleDocs { + + export const Export = async (state: EditorState): Promise => { + const nodes: (Node | null)[] = []; + const text = ToPlainText(state); + state.doc.content.forEach(node => { + if (!node.childCount) { + nodes.push(null); + } else { + node.content.forEach(child => nodes.push(child)); + } + }); + const requests = await marksToStyle(nodes); + return { text, requests }; + }; + + interface ImageTemplate { + width: number; + title: string; + url: string; + agnostic: string; + } + + const parseInlineObjects = async (document: docs_v1.Schema$Document): Promise> => { + const inlineObjectMap = new Map(); + const inlineObjects = document.inlineObjects; + + if (inlineObjects) { + const objects = Object.keys(inlineObjects).map(objectId => inlineObjects[objectId]); + const mediaItems: MediaItem[] = objects.map(object => { + const embeddedObject = object.inlineObjectProperties!.embeddedObject!; + return { baseUrl: embeddedObject.imageProperties!.contentUri! }; + }); + + const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems }); + + if (uploads.length !== mediaItems.length) { + throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" }); + } + + for (let i = 0; i < objects.length; i++) { + const object = objects[i]; + const { accessPaths } = uploads[i]; + const { agnostic, _m } = accessPaths; + const embeddedObject = object.inlineObjectProperties!.embeddedObject!; + const size = embeddedObject.size!; + const width = size.width!.magnitude!; + + inlineObjectMap.set(object.objectId!, { + title: embeddedObject.title || `Imported Image from ${document.title}`, + width, + url: Utils.prepend(_m.client), + agnostic: Utils.prepend(agnostic.client) + }); + } + } + return inlineObjectMap; + }; + + type BulletPosition = { value: number, sinks: number }; + + interface MediaItem { + baseUrl: string; + } + + export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId, textNote: Doc): Promise> => { + const document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); + if (!document) { + return undefined; + } + const inlineObjectMap = await parseInlineObjects(document); + const title = document.title!; + const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); + let state = FormattedTextBox.blankState(); + const structured = parseLists(paragraphs); + + let position = 3; + const lists: ListGroup[] = []; + const indentMap = new Map(); + let globalOffset = 0; + const nodes: Node[] = []; + for (const element of structured) { + if (Array.isArray(element)) { + lists.push(element); + const positions: BulletPosition[] = []; + const items = element.map(paragraph => { + const item = listItem(state.schema, paragraph.contents); + const sinks = paragraph.bullet!; + positions.push({ + value: position + globalOffset, + sinks + }); + position += item.nodeSize; + globalOffset += 2 * sinks; + return item; + }); + indentMap.set(element, positions); + nodes.push(list(state.schema, items)); + } else { + if (element.contents.some(child => "inlineObjectId" in child)) { + const group = element.contents; + group.forEach((child, i) => { + let node: Opt>; + if ("inlineObjectId" in child) { + node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); + } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { + node = paragraphNode(state.schema, [child]); + } + if (node) { + position += node.nodeSize; + nodes.push(node); + } + }); + } else { + const paragraph = paragraphNode(state.schema, element.contents); + nodes.push(paragraph); + position += paragraph.nodeSize; + } + } + } + state = state.apply(state.tr.replaceWith(0, 2, nodes)); + + const sink = sinkListItem(state.schema.nodes.list_item); + const dispatcher = (tr: Transaction) => state = state.apply(tr); + for (const list of lists) { + for (const pos of indentMap.get(list)!) { + const resolved = state.doc.resolve(pos.value); + state = state.apply(state.tr.setSelection(new TextSelection(resolved))); + for (let i = 0; i < pos.sinks; i++) { + sink(state, dispatcher); + } + } + } + + return { title, text, state }; + }; + + type Paragraph = GoogleApiClientUtils.Docs.Utils.DeconstructedParagraph; + type ListGroup = Paragraph[]; + type PreparedParagraphs = (ListGroup | Paragraph)[]; + + const parseLists = (paragraphs: ListGroup) => { + const groups: PreparedParagraphs = []; + let group: ListGroup = []; + for (const paragraph of paragraphs) { + if (paragraph.bullet !== undefined) { + group.push(paragraph); + } else { + if (group.length) { + groups.push(group); + group = []; + } + groups.push(paragraph); + } + } + group.length && groups.push(group); + return groups; + }; + + const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { + return schema.node("list_item", null, paragraphNode(schema, runs)); + }; + + const list = (schema: any, items: Node[]): Node => { + return schema.node("bullet_list", null, items); + }; + + const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { + const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined); + const fragment = children.length ? Fragment.from(children) : undefined; + return schema.node("paragraph", null, fragment); + }; + + const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => { + const { url: src, width, agnostic } = image; + let docid: string; + const guid = Utils.GenerateDeterministicGuid(agnostic); + const backingDocId = StrCast(textNote[guid]); + if (!backingDocId) { + const backingDoc = Docs.Create.ImageDocument(agnostic, { _width: 300, _height: 300 }); + Doc.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument); + docid = backingDoc[Id]; + textNote[guid] = docid; + } else { + docid = backingDocId; + } + return schema.node("image", { src, agnostic, width, docid, float: null, location: "onRight" }); + }; + + const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { + const text = run.content!.removeTrailingNewlines(); + return text.length ? schema.text(text, styleToMarks(schema, run.textStyle)) : undefined; + }; + + const StyleToMark = new Map([ + ["bold", "strong"], + ["italic", "em"], + ["foregroundColor", "pFontColor"], + ["fontSize", "pFontSize"] + ]); + + const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { + if (!textStyle) { + return undefined; + } + const marks: Mark[] = []; + Object.keys(textStyle).forEach(key => { + let value: any; + const targeted = key as keyof docs_v1.Schema$TextStyle; + if (value = textStyle[targeted]) { + const attributes: any = {}; + let converted = StyleToMark.get(targeted) || targeted; + + value.url && (attributes.href = value.url); + if (value.color) { + const object = value.color.rgbColor; + attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex(); + } + if (value.magnitude) { + attributes.fontSize = value.magnitude; + } + + if (converted === "weightedFontFamily") { + converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman"; + } + + const mapped = schema.marks[converted]; + if (!mapped) { + alert(`No mapping found for ${converted}!`); + return; + } + + const mark = schema.mark(mapped, attributes); + mark && marks.push(mark); + } + }); + return marks; + }; + + const MarkToStyle = new Map([ + ["strong", "bold"], + ["em", "italic"], + ["pFontColor", "foregroundColor"], + ["pFontSize", "fontSize"], + ["timesNewRoman", "weightedFontFamily"], + ["georgia", "weightedFontFamily"], + ["comicSans", "weightedFontFamily"], + ["tahoma", "weightedFontFamily"], + ["impact", "weightedFontFamily"] + ]); + + const ExportFontFamilyMapping = new Map([ + ["timesNewRoman", "Times New Roman"], + ["arial", "Arial"], + ["georgia", "Georgia"], + ["comicSans", "Comic Sans MS"], + ["tahoma", "Tahoma"], + ["impact", "Impact"] + ]); + + const ImportFontFamilyMapping = new Map([ + ["Times New Roman", "timesNewRoman"], + ["Arial", "arial"], + ["Georgia", "georgia"], + ["Comic Sans MS", "comicSans"], + ["Tahoma", "tahoma"], + ["Impact", "impact"] + ]); + + const ignored = ["user_mark"]; + + const marksToStyle = async (nodes: (Node | null)[]): Promise => { + const requests: docs_v1.Schema$Request[] = []; + let position = 1; + for (const node of nodes) { + if (node === null) { + position += 2; + continue; + } + const { marks, attrs, nodeSize } = node; + const textStyle: docs_v1.Schema$TextStyle = {}; + const information: LinkInformation = { + startIndex: position, + endIndex: position + nodeSize, + textStyle + }; + let mark: Mark; + const markMap = BuildMarkMap(marks); + for (const markName of Object.keys(schema.marks)) { + if (ignored.includes(markName) || !(mark = markMap[markName])) { + continue; + } + let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; + let value: any = true; + if (!converted) { + continue; + } + const { attrs } = mark; + switch (converted) { + case "link": + let url = attrs.href; + const delimiter = "/doc/"; + const alreadyShared = "?sharing=true"; + if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { + const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); + if (linkDoc instanceof Doc) { + let exported = (await Cast(linkDoc.anchor2, Doc))!; + if (!exported.customLayout) { + exported = Doc.MakeAlias(exported); + Doc.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); + linkDoc.anchor2 = exported; + } + url = Utils.shareUrl(exported[Id]); + } + } + value = { url }; + textStyle.foregroundColor = fromRgb.blue; + textStyle.bold = true; + break; + case "fontSize": + value = { magnitude: attrs.fontSize, unit: "PT" }; + break; + case "foregroundColor": + value = fromHex(attrs.color); + break; + case "weightedFontFamily": + value = { fontFamily: ExportFontFamilyMapping.get(markName) }; + } + let matches: RegExpExecArray | null; + if ((matches = /p(\d+)/g.exec(markName)) !== null) { + converted = "fontSize"; + value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" }; + } + textStyle[converted] = value; + } + if (Object.keys(textStyle).length) { + requests.push(EncodeStyleUpdate(information)); + } + if (node.type.name === "image") { + const width = attrs.width; + requests.push(await EncodeImage({ + startIndex: position + nodeSize - 1, + uri: attrs.agnostic, + width: Number(typeof width === "string" ? width.replace("px", "") : width) + })); + } + position += nodeSize; + } + return requests; + }; + + const BuildMarkMap = (marks: Mark[]) => { + const markMap: { [type: string]: Mark } = {}; + marks.forEach(mark => markMap[mark.type.name] = mark); + return markMap; + }; + + interface LinkInformation { + startIndex: number; + endIndex: number; + textStyle: docs_v1.Schema$TextStyle; + } + + interface ImageInformation { + startIndex: number; + width: number; + uri: string; + } + + namespace fromRgb { + + export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { + return { + color: { + rgbColor: { + red: red / 255, + green: green / 255, + blue: blue / 255 + } + } + }; + }; + + export const red = convert(255, 0, 0); + export const green = convert(0, 255, 0); + export const blue = convert(0, 0, 255); + + } + + const fromHex = (color: string): docs_v1.Schema$OptionalColor => { + const c = Color(color); + return fromRgb.convert(c.red(), c.green(), c.blue()); + }; + + const EncodeStyleUpdate = (information: LinkInformation): docs_v1.Schema$Request => { + const { startIndex, endIndex, textStyle } = information; + return { + updateTextStyle: { + fields: "*", + range: { startIndex, endIndex }, + textStyle + } as docs_v1.Schema$UpdateTextStyleRequest + }; + }; + + const EncodeImage = async ({ uri, width, startIndex }: ImageInformation) => { + if (!uri) { + return {}; + } + const source = [Docs.Create.ImageDocument(uri)]; + const baseUrls = await GooglePhotos.Transactions.UploadThenFetch(source); + if (baseUrls) { + return { + insertInlineImage: { + uri: baseUrls[0], + objectSize: { width: { magnitude: width, unit: "PT" } }, + location: { index: startIndex } + } + }; + } + return {}; + }; + } + +} \ No newline at end of file diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts new file mode 100644 index 000000000..72bce283d --- /dev/null +++ b/src/fields/Schema.ts @@ -0,0 +1,120 @@ +import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types"; +import { Doc, Field } from "./Doc"; +import { ObjectField } from "./ObjectField"; +import { RefField } from "./RefField"; +import { SelfProxy } from "./FieldSymbols"; + +type AllToInterface = { + 1: ToInterface> & AllToInterface>, + 0: ToInterface> +}[HasTail extends true ? 1 : 0]; + +export const emptySchema = createSchema({}); +export const Document = makeInterface(emptySchema); +export type Document = makeInterface<[typeof emptySchema]>; + +export interface InterfaceFunc { + (docs: Doc[]): makeInterface[]; + (): makeInterface; + (doc: Doc): makeInterface; +} + +export type makeInterface = AllToInterface & Doc & { proto: Doc | undefined }; +// export function makeInterface(schemas: T): (doc: U) => All; +// export function makeInterface(schema: T): (doc: U) => makeInterface; +export function makeInterface(...schemas: T): InterfaceFunc { + const schema: Interface = {}; + for (const s of schemas) { + for (const key in s) { + schema[key] = s[key]; + } + } + const proto = new Proxy({}, { + get(target: any, prop, receiver) { + const field = receiver.doc[prop]; + if (prop in schema) { + const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should? + if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec + return Cast(field, desc.type, desc.defaultVal); + } else if (typeof desc === "function" && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) { + const doc = Cast(field, Doc); + if (doc === undefined) { + return undefined; + } else if (doc instanceof Doc) { + return desc(doc); + } else { + return doc.then(doc => doc && desc(doc)); + } + } else { + return Cast(field, desc); + } + } + return field; + }, + set(target: any, prop, value, receiver) { + receiver.doc[prop] = value; + return true; + } + }); + const fn = (doc: Doc) => { + doc = doc[SelfProxy]; + // if (!(doc instanceof Doc)) { + // throw new Error("Currently wrapping a schema in another schema isn't supported"); + // } + const obj = Object.create(proto, { doc: { value: doc, writable: false } }); + return obj; + }; + return function (doc?: Doc | Doc[]) { + doc = doc || new Doc; + if (doc instanceof Doc) { + return fn(doc); + } else { + return doc.map(fn); + } + }; +} + +export type makeStrictInterface = Partial>; +export function makeStrictInterface(schema: T): (doc: Doc) => makeStrictInterface { + const proto = {}; + for (const key in schema) { + const type = schema[key]; + Object.defineProperty(proto, key, { + get() { + return Cast(this.__doc[key], type as any); + }, + set(value) { + value = Cast(value, type as any); + if (value !== undefined) { + this.__doc[key] = value; + return; + } + throw new TypeError("Expected type " + type); + } + }); + } + return function (doc: any) { + if (!(doc instanceof Doc)) { + throw new Error("Currently wrapping a schema in another schema isn't supported"); + } + const obj = Object.create(proto); + obj.__doc = doc; + return obj; + }; +} + +export function createSchema(schema: T): T & { proto: ToConstructor } { + (schema as any).proto = Doc; + return schema as any; +} + +export function listSpec>(type: U): ListSpec> { + return { List: type as any };//TODO Types +} + +export function defaultSpec>(type: T, defaultVal: ToType): DefaultFieldConstructor> { + return { + type: type as any, + defaultVal + }; +} \ No newline at end of file diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts new file mode 100644 index 000000000..07c90f5a2 --- /dev/null +++ b/src/fields/SchemaHeaderField.ts @@ -0,0 +1,122 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, primitive } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString, ToString, OnUpdate } from "./FieldSymbols"; +import { scriptingGlobal } from "../client/util/Scripting"; +import { ColumnType } from "../client/views/collections/CollectionSchemaView"; + +export const PastelSchemaPalette = new Map([ + // ["pink1", "#FFB4E8"], + ["pink2", "#ff9cee"], + ["pink3", "#ffccf9"], + ["pink4", "#fcc2ff"], + ["pink5", "#f6a6ff"], + ["purple1", "#b28dff"], + ["purple2", "#c5a3ff"], + ["purple3", "#d5aaff"], + ["purple4", "#ecd4ff"], + // ["purple5", "#fb34ff"], + ["purple6", "#dcd3ff"], + ["purple7", "#a79aff"], + ["purple8", "#b5b9ff"], + ["purple9", "#97a2ff"], + ["bluegreen1", "#afcbff"], + ["bluegreen2", "#aff8db"], + ["bluegreen3", "#c4faf8"], + ["bluegreen4", "#85e3ff"], + ["bluegreen5", "#ace7ff"], + // ["bluegreen6", "#6eb5ff"], + ["bluegreen7", "#bffcc6"], + ["bluegreen8", "#dbffd6"], + ["yellow1", "#f3ffe3"], + ["yellow2", "#e7ffac"], + ["yellow3", "#ffffd1"], + ["yellow4", "#fff5ba"], + // ["red1", "#ffc9de"], + ["red2", "#ffabab"], + ["red3", "#ffbebc"], + ["red4", "#ffcbc1"], + ["orange1", "#ffd5b3"], +]); + +export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; + +export const DarkPastelSchemaPalette = new Map([ + ["pink2", "#c932b0"], + ["purple4", "#913ad6"], + ["bluegreen1", "#3978ed"], + ["bluegreen7", "#2adb3e"], + ["bluegreen5", "#21b0eb"], + ["yellow4", "#edcc0c"], + ["red2", "#eb3636"], + ["orange1", "#f2740f"], +]); + +@scriptingGlobal +@Deserializable("schemaheader") +export class SchemaHeaderField extends ObjectField { + @serializable(primitive()) + heading: string; + @serializable(primitive()) + color: string; + @serializable(primitive()) + type: number; + @serializable(primitive()) + width: number; + @serializable(primitive()) + collapsed: boolean | undefined; + @serializable(primitive()) + desc: boolean | undefined; // boolean determines sort order, undefined when no sort + + constructor(heading: string = "", color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) { + super(); + + this.heading = heading; + this.color = color; + this.type = type ? type : 0; + this.width = width ? width : -1; + this.desc = desc; + this.collapsed = collapsed; + } + + setHeading(heading: string) { + this.heading = heading; + this[OnUpdate](); + } + + setColor(color: string) { + this.color = color; + this[OnUpdate](); + } + + setType(type: ColumnType) { + this.type = type; + this[OnUpdate](); + } + + setWidth(width: number) { + this.width = width; + this[OnUpdate](); + } + + setDesc(desc: boolean | undefined) { + this.desc = desc; + this[OnUpdate](); + } + + setCollapsed(collapsed: boolean | undefined) { + this.collapsed = collapsed; + this[OnUpdate](); + } + + [Copy]() { + return new SchemaHeaderField(this.heading, this.color, this.type); + } + + [ToScriptString]() { + return `invalid`; + } + [ToString]() { + return `SchemaHeaderField`; + } +} \ No newline at end of file diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts new file mode 100644 index 000000000..f05f431ac --- /dev/null +++ b/src/fields/ScriptField.ts @@ -0,0 +1,193 @@ +import { ObjectField } from "./ObjectField"; +import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions, CompileError, CompileResult } from "../client/util/Scripting"; +import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols"; +import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; +import { Deserializable, autoObject } from "../client/util/SerializationHelper"; +import { Doc, Field } from "./Doc"; +import { Plugins, setter } from "./util"; +import { computedFn } from "mobx-utils"; +import { ProxyField } from "./Proxy"; +import { Cast } from "./Types"; + +function optional(propSchema: PropSchema) { + return custom(value => { + if (value !== undefined) { + return propSchema.serializer(value); + } + return SKIP; + }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => { + if (jsonValue !== undefined) { + return propSchema.deserializer(jsonValue, callback, context, oldValue); + } + return SKIP; + }); +} + +const optionsSchema = createSimpleSchema({ + requiredType: true, + addReturn: true, + typecheck: true, + editable: true, + readonly: true, + params: optional(map(primitive())) +}); + +const scriptSchema = createSimpleSchema({ + options: object(optionsSchema), + originalScript: true +}); + +async function deserializeScript(script: ScriptField) { + const captures: ProxyField = (script as any).captures; + if (captures) { + const doc = (await captures.value())!; + const captured: any = {}; + const keys = Object.keys(doc); + const vals = await Promise.all(keys.map(key => doc[key]) as any); + keys.forEach((key, i) => captured[key] = vals[i]); + (script.script.options as any).capturedVariables = captured; + } + const comp = CompileScript(script.script.originalScript, script.script.options); + if (!comp.compiled) { + throw new Error("Couldn't compile loaded script"); + } + (script as any).script = comp; +} + +@scriptingGlobal +@Deserializable("script", deserializeScript) +export class ScriptField extends ObjectField { + @serializable(object(scriptSchema)) + readonly script: CompiledScript; + @serializable(object(scriptSchema)) + readonly setterscript: CompiledScript | undefined; + + @serializable(autoObject()) + private captures?: ProxyField; + + constructor(script: CompiledScript, setterscript?: CompileResult) { + super(); + + if (script?.options.capturedVariables) { + const doc = Doc.assign(new Doc, script.options.capturedVariables); + this.captures = new ProxyField(doc); + } + this.setterscript = setterscript?.compiled ? setterscript : undefined; + this.script = script; + } + + // init(callback: (res: Field) => any) { + // const options = this.options!; + // const keys = Object.keys(options.options.capturedIds); + // Server.GetFields(keys).then(fields => { + // let captured: { [name: string]: Field } = {}; + // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); + // const opts: ScriptOptions = { + // addReturn: options.options.addReturn, + // params: options.options.params, + // requiredType: options.options.requiredType, + // capturedVariables: captured + // }; + // const script = CompileScript(options.script, opts); + // if (!script.compiled) { + // throw new Error("Can't compile script"); + // } + // this._script = script; + // callback(this); + // }); + // } + + [Copy](): ObjectField { + return new ScriptField(this.script); + } + toString() { + return `${this.script.originalScript}`; + } + + [ToScriptString]() { + return "script field"; + } + [ToString]() { + return this.script.originalScript; + } + public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) { + const compiled = CompileScript(script, { + params: { this: Doc.name, self: Doc.name, _last_: "any", ...params }, + typecheck: false, + editable: true, + addReturn: addReturn, + capturedVariables + }); + return compiled; + } + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) { + const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); + return compiled.compiled ? new ScriptField(compiled) : undefined; + } + + public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) { + const compiled = ScriptField.CompileScript(script, params, false, capturedVariables); + return compiled.compiled ? new ScriptField(compiled) : undefined; + } +} + +@scriptingGlobal +@Deserializable("computed", deserializeScript) +export class ComputedField extends ScriptField { + _lastComputedResult: any; + //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc + value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); + _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result; + + + constructor(script: CompiledScript, setterscript?: CompiledScript) { + super(script, + !setterscript && script?.originalScript.includes("self.timecode") ? + ScriptField.CompileScript("self['x' + self.timecode] = value", { value: "any" }, true) : setterscript); + } + + public static MakeScript(script: string, params: object = {}) { + const compiled = ScriptField.CompileScript(script, params, false); + return compiled.compiled ? new ComputedField(compiled) : undefined; + } + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }, setterScript?: string) { + const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); + const setCompiled = setterScript ? ScriptField.CompileScript(setterScript, params, true, capturedVariables) : undefined; + return compiled.compiled ? new ComputedField(compiled, setCompiled?.compiled ? setCompiled : undefined) : undefined; + } + public static MakeInterpolated(fieldKey: string, interpolatorKey: string) { + const getField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}]`, {}, true, {}); + const setField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}] = value`, { value: "any" }, true, {}); + return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; + } +} + +export namespace ComputedField { + let useComputed = true; + export function DisableComputedFields() { + useComputed = false; + } + + export function EnableComputedFields() { + useComputed = true; + } + + export const undefined = "__undefined"; + + export function WithoutComputed(fn: () => T) { + DisableComputedFields(); + try { + return fn(); + } finally { + EnableComputedFields(); + } + } + + export function initPlugin() { + Plugins.addGetterPlugin((doc, _, value) => { + if (useComputed && value instanceof ComputedField) { + return { value: value._valueOutsideReaction(doc), shouldReturn: true }; + } + }); + } +} \ No newline at end of file diff --git a/src/fields/Types.ts b/src/fields/Types.ts new file mode 100644 index 000000000..3d784448d --- /dev/null +++ b/src/fields/Types.ts @@ -0,0 +1,108 @@ +import { Field, Opt, FieldResult, Doc } from "./Doc"; +import { List } from "./List"; +import { RefField } from "./RefField"; +import { DateField } from "./DateField"; +import { ScriptField } from "./ScriptField"; + +export type ToType = + T extends "string" ? string : + T extends "number" ? number : + T extends "boolean" ? boolean : + T extends ListSpec ? List : + // T extends { new(...args: any[]): infer R } ? (R | Promise) : never; + T extends DefaultFieldConstructor ? never : + T extends { new(...args: any[]): List } ? never : + T extends { new(...args: any[]): infer R } ? R : + T extends (doc?: Doc) => infer R ? R : never; + +export type ToConstructor = + T extends string ? "string" : + T extends number ? "number" : + T extends boolean ? "boolean" : + T extends List ? ListSpec : + new (...args: any[]) => T; + +export type ToInterface = { + [P in Exclude]: T[P] extends DefaultFieldConstructor ? Exclude, undefined> : FieldResult>; +}; + +// type ListSpec = { List: ToContructor> | ListSpec> }; +export type ListSpec = { List: ToConstructor }; + +export type DefaultFieldConstructor = { + type: ToConstructor, + defaultVal: T +}; + +// type ListType = { 0: List>>, 1: ToType> }[HasTail extends true ? 0 : 1]; + +export type Head = T extends [any, ...any[]] ? T[0] : never; +export type Tail = + ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : []; +export type HasTail = T extends ([] | [any]) ? false : true; + +export type InterfaceValue = ToConstructor | ListSpec | DefaultFieldConstructor | ((doc?: Doc) => any); +//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial +export interface Interface { + [key: string]: InterfaceValue; + // [key: string]: ToConstructor | ListSpec; +} +export type WithoutRefField = T extends RefField ? never : T; + +export type CastCtor = ToConstructor | ListSpec; + +export function Cast(field: FieldResult, ctor: T): FieldResult>; +export function Cast(field: FieldResult, ctor: T, defaultVal: WithoutList>> | null): WithoutList>; +export function Cast(field: FieldResult, ctor: T, defaultVal?: ToType | null): FieldResult> | undefined { + if (field instanceof Promise) { + return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal; + } + if (field !== undefined && !(field instanceof Promise)) { + if (typeof ctor === "string") { + if (typeof field === ctor) { + return field as ToType; + } + } else if (typeof ctor === "object") { + if (field instanceof List) { + return field as any; + } + } else if (field instanceof (ctor as any)) { + return field as ToType; + } + } + return defaultVal === null ? undefined : defaultVal; +} + +export function NumCast(field: FieldResult, defaultVal: number | null = 0) { + return Cast(field, "number", defaultVal); +} + +export function StrCast(field: FieldResult, defaultVal: string | null = "") { + return Cast(field, "string", defaultVal); +} + +export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) { + return Cast(field, "boolean", defaultVal); +} +export function DateCast(field: FieldResult) { + return Cast(field, DateField, null); +} + +export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { + return Cast(field, ScriptField, defaultVal); +} + +type WithoutList = T extends List ? (R extends RefField ? (R | Promise)[] : R[]) : T; + +export function FieldValue>(field: FieldResult, defaultValue: U): WithoutList; +export function FieldValue(field: FieldResult): Opt; +export function FieldValue(field: FieldResult, defaultValue?: T): Opt { + return (field instanceof Promise || field === undefined) ? defaultValue : field; +} + +export interface PromiseLike { + then(callback: (field: Opt) => void): void; +} +export function PromiseValue(field: FieldResult): PromiseLike> { + return field instanceof Promise ? field : { then(cb: ((field: Opt) => void)) { return cb(field); } }; +} \ No newline at end of file diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts new file mode 100644 index 000000000..fb71160ca --- /dev/null +++ b/src/fields/URLField.ts @@ -0,0 +1,53 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, custom } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { ToScriptString, ToString, Copy } from "./FieldSymbols"; +import { Scripting, scriptingGlobal } from "../client/util/Scripting"; + +function url() { + return custom( + function (value: URL) { + return value.href; + }, + function (jsonValue: string) { + return new URL(jsonValue); + } + ); +} + +export abstract class URLField extends ObjectField { + @serializable(url()) + readonly url: URL; + + constructor(url: string); + constructor(url: URL); + constructor(url: URL | string) { + super(); + if (typeof url === "string") { + url = new URL(url); + } + this.url = url; + } + + [ToScriptString]() { + return `new ${this.constructor.name}("${this.url.href}")`; + } + [ToString]() { + return this.url.href; + } + + [Copy](): this { + return new (this.constructor as any)(this.url); + } +} + +export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg"; + +@scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { } +@scriptingGlobal @Deserializable("image") export class ImageField extends URLField { } +@scriptingGlobal @Deserializable("video") export class VideoField extends URLField { } +@scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } +@scriptingGlobal @Deserializable("web") export class WebField extends URLField { } +@scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } +@scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } + diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts new file mode 100644 index 000000000..cacba43b6 --- /dev/null +++ b/src/fields/documentSchemas.ts @@ -0,0 +1,100 @@ +import { makeInterface, createSchema, listSpec } from "./Schema"; +import { ScriptField } from "./ScriptField"; +import { Doc } from "./Doc"; +import { DateField } from "./DateField"; + +export const documentSchema = createSchema({ + // content properties + type: "string", // enumerated type of document -- should be template-specific (ie, start with an '_') + title: "string", // document title (can be on either data document or layout) + isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field + creationDate: DateField, // when the document was created + links: listSpec(Doc), // computed (readonly) list of links associated with this document + + // "Location" properties in a very general sense + currentTimecode: "number", // current play back time of a temporal document (video / audio) + displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently + x: "number", // x coordinate when in a freeform view + y: "number", // y coordinate when in a freeform view + z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview + zIndex: "number", // zIndex of a document in a freeform view + scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) + scrollTop: "number", // scroll position of a scrollable document (pdf, text, web) + + // appearance properties on the layout + _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents + _nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set + _nativeHeight: "number", // " + _width: "number", // width of document in its container's coordinate system + _height: "number", // " + _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set + _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set + _xMargin: "number", // margin added on left/right of most documents to add separation from their container + _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container + _overflow: "string", // sets overflow behvavior for CollectionFreeForm views + _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document + _showTitle: "string", // the fieldkey whose contents should be displayed at the top of the document + _showTitleHover: "string", // the showTitle should be shown only on hover + _showAudio: "boolean", // whether to show the audio record icon on documents + _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents + _LODdisable: "boolean", // whether to disbale LOD switching for CollectionFreeFormViews + _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis + _replacedChrome: "string", // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's. + _chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed' + _fontSize: "number", + _fontFamily: "string", + _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar + + // appearance properties on the data document + backgroundColor: "string", // background color of document + borderRounding: "string", // border radius rounding of document + boxShadow: "string", // the amount of shadow around the perimeter of a document + color: "string", // foreground color of document + fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view + fontSize: "string", + layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below + layoutKey: "string", // holds the field key for the field that actually holds the current lyoat + letterSpacing: "string", + opacity: "number", // opacity of document + strokeWidth: "number", + textTransform: "string", + treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden + treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree + treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) + + // interaction and linking properties + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) + onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. + followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) + isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations + isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked + isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee) + lockedPosition: "boolean", // whether the document can be moved (dragged) + lockedTransform: "boolean", // whether the document can be panned/zoomed + + // drag drop properties + 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' + childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy") + removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped +}); + + +export const collectionSchema = createSchema({ + childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox + childLayoutTemplate: Doc, // layout template to use to render children of a collecion + childLayoutString: "string", //layout string to use to render children of a collection + childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template) + dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc. + onChildClick: ScriptField, // script to run for each child when its clicked + onChildDoubleClick: ScriptField, // script to run for each child when its clicked + onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view +}); + +export type Document = makeInterface<[typeof documentSchema]>; +export const Document = makeInterface(documentSchema); diff --git a/src/fields/util.ts b/src/fields/util.ts new file mode 100644 index 000000000..a287b0210 --- /dev/null +++ b/src/fields/util.ts @@ -0,0 +1,199 @@ +import { UndoManager } from "../client/util/UndoManager"; +import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym } from "./Doc"; +import { SerializationHelper } from "../client/util/SerializationHelper"; +import { ProxyField, PrefetchProxy } from "./Proxy"; +import { RefField } from "./RefField"; +import { ObjectField } from "./ObjectField"; +import { action, trace } from "mobx"; +import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; +import { DocServer } from "../client/DocServer"; +import { ComputedField } from "./ScriptField"; +import { ScriptCast } from "./Types"; + +function _readOnlySetter(): never { + throw new Error("Documents can't be modified in read-only mode"); +} + +const tracing = false; +export function TraceMobx() { + tracing && trace(); +} + +export interface GetterResult { + value: FieldResult; + shouldReturn?: boolean; +} +export type GetterPlugin = (receiver: any, prop: string | number, currentValue: any) => GetterResult | undefined; +const getterPlugins: GetterPlugin[] = []; + +export namespace Plugins { + export function addGetterPlugin(plugin: GetterPlugin) { + getterPlugins.push(plugin); + } +} + +const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { + //console.log("-set " + target[SelfProxy].title + "(" + target[SelfProxy][prop] + ")." + prop.toString() + " = " + value); + if (SerializationHelper.IsSerializing()) { + target[prop] = value; + return true; + } + + if (typeof prop === "symbol") { + target[prop] = value; + return true; + } + if (value !== undefined) { + value = value[SelfProxy] || value; + } + const curValue = target.__fields[prop]; + if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) { + // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically + // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way + return true; + } + if (value instanceof RefField) { + value = new ProxyField(value); + } + if (value instanceof ObjectField) { + if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) { + throw new Error("Can't put the same object in multiple documents at the same time"); + } + value[Parent] = receiver; + value[OnUpdate] = updateFunction(target, prop, value, receiver); + } + if (curValue instanceof ObjectField) { + delete curValue[Parent]; + delete curValue[OnUpdate]; + } + const writeMode = DocServer.getFieldWriteMode(prop as string); + const fromServer = target[UpdatingFromServer]; + const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); + const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly); + const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default); + if (writeToDoc) { + if (value === undefined) { + delete target.__fields[prop]; + } else { + target.__fields[prop] = value; + } + if (typeof value === "object" && !(value instanceof ObjectField)) debugger; + if (writeToServer) { + if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } }); + else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); + } else { + DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); + } + UndoManager.AddEvent({ + redo: () => receiver[prop] = value, + undo: () => receiver[prop] = curValue + }); + } + return true; +}); + +let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; + +export function makeReadOnly() { + _setter = _readOnlySetter; +} + +export function makeEditable() { + _setter = _setterImpl; +} + +const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", + "LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; +export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { + let prop = in_prop; + if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { + if (!prop.startsWith("_")) { + console.log(prop + " is deprecated - switch to _" + prop); + prop = "_" + prop; + } + if (target.__LAYOUT__) { + target.__LAYOUT__[prop] = value; + return true; + } + } + if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript) { + return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; + } + return _setter(target, prop, value, receiver); +} + +export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { + let prop = in_prop; + if (prop === LayoutSym) { + return target.__LAYOUT__; + } + if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { + if (!prop.startsWith("_")) { + console.log(prop + " is deprecated - switch to _" + prop); + prop = "_" + prop; + } + if (target.__LAYOUT__) return target.__LAYOUT__[prop]; + } + if (prop === "then") {//If we're being awaited + return undefined; + } + if (typeof prop === "symbol") { + return target.__fields[prop] || target[prop]; + } + if (SerializationHelper.IsSerializing()) { + return target[prop]; + } + return getFieldImpl(target, prop, receiver); +} + +function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { + receiver = receiver || target[SelfProxy]; + let field = target.__fields[prop]; + for (const plugin of getterPlugins) { + const res = plugin(receiver, prop, field); + if (res === undefined) continue; + if (res.shouldReturn) { + return res.value; + } else { + field = res.value; + } + } + if (field === undefined && !ignoreProto && prop !== "proto") { + const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters + if (proto instanceof Doc) { + return getFieldImpl(proto[Self], prop, receiver, ignoreProto); + } + return undefined; + } + return field; + +} +export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any { + return getFieldImpl(target, prop, undefined, ignoreProto); +} + +export function deleteProperty(target: any, prop: string | number | symbol) { + if (typeof prop === "symbol") { + delete target[prop]; + return true; + } + target[SelfProxy][prop] = undefined; + return true; +} + +export function updateFunction(target: any, prop: any, value: any, receiver: any) { + let current = ObjectField.MakeCopy(value); + return (diff?: any) => { + if (true || !diff) { + diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; + const oldValue = current; + const newValue = ObjectField.MakeCopy(value); + current = newValue; + UndoManager.AddEvent({ + redo() { receiver[prop] = newValue; }, + undo() { receiver[prop] = oldValue; } + }); + } + target[Update](diff); + }; +} \ No newline at end of file diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index f30e9869a..231870531 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -4,10 +4,10 @@ import { Docs } from '../client/documents/Documents'; import "./ImageUpload.scss"; import React = require('react'); import { DocServer } from '../client/DocServer'; -import { Opt, Doc } from '../new_fields/Doc'; -import { Cast } from '../new_fields/Types'; -import { listSpec } from '../new_fields/Schema'; -import { List } from '../new_fields/List'; +import { Opt, Doc } from '../fields/Doc'; +import { Cast } from '../fields/Types'; +import { listSpec } from '../fields/Schema'; +import { List } from '../fields/List'; import { observer } from 'mobx-react'; import { observable } from 'mobx'; import { Utils } from '../Utils'; diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx index 1537ae034..973931615 100644 --- a/src/mobile/MobileInkOverlay.tsx +++ b/src/mobile/MobileInkOverlay.tsx @@ -4,11 +4,11 @@ import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition import { observable, action } from "mobx"; import { GestureUtils } from "../pen-gestures/GestureUtils"; import "./MobileInkOverlay.scss"; -import { StrCast, Cast } from '../new_fields/Types'; +import { StrCast, Cast } from '../fields/Types'; import { DragManager } from "../client/util/DragManager"; import { DocServer } from '../client/DocServer'; -import { Doc, DocListCastAsync } from '../new_fields/Doc'; -import { listSpec } from '../new_fields/Schema'; +import { Doc, DocListCastAsync } from '../fields/Doc'; +import { listSpec } from '../fields/Schema'; @observer diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 9d4d58ad1..6c2e797d6 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -16,12 +16,12 @@ import { InkingControl } from '../client/views/InkingControl'; import { DocumentView } from '../client/views/nodes/DocumentView'; import { RadialMenu } from '../client/views/nodes/RadialMenu'; import { PreviewCursor } from '../client/views/PreviewCursor'; -import { Doc, DocListCast, FieldResult } from '../new_fields/Doc'; -import { Id } from '../new_fields/FieldSymbols'; -import { InkTool } from '../new_fields/InkField'; -import { listSpec } from '../new_fields/Schema'; -import { Cast, FieldValue } from '../new_fields/Types'; -import { WebField } from "../new_fields/URLField"; +import { Doc, DocListCast, FieldResult } from '../fields/Doc'; +import { Id } from '../fields/FieldSymbols'; +import { InkTool } from '../fields/InkField'; +import { listSpec } from '../fields/Schema'; +import { Cast, FieldValue } from '../fields/Types'; +import { WebField } from "../fields/URLField"; import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from '../Utils'; import "./MobileInterface.scss"; diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts deleted file mode 100644 index 28467377b..000000000 --- a/src/new_fields/CursorField.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ObjectField } from "./ObjectField"; -import { observable } from "mobx"; -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, createSimpleSchema, object, date } from "serializr"; -import { OnUpdate, ToScriptString, ToString, Copy } from "./FieldSymbols"; - -export type CursorPosition = { - x: number, - y: number -}; - -export type CursorMetadata = { - id: string, - identifier: string, - timestamp: number -}; - -export type CursorData = { - metadata: CursorMetadata, - position: CursorPosition -}; - -const PositionSchema = createSimpleSchema({ - x: true, - y: true -}); - -const MetadataSchema = createSimpleSchema({ - id: true, - identifier: true, - timestamp: true -}); - -const CursorSchema = createSimpleSchema({ - metadata: object(MetadataSchema), - position: object(PositionSchema) -}); - -@Deserializable("cursor") -export default class CursorField extends ObjectField { - - @serializable(object(CursorSchema)) - readonly data: CursorData; - - constructor(data: CursorData) { - super(); - this.data = data; - } - - setPosition(position: CursorPosition) { - this.data.position = position; - this.data.metadata.timestamp = Date.now(); - this[OnUpdate](); - } - - [Copy]() { - return new CursorField(this.data); - } - - [ToScriptString]() { - return "invalid"; - } - [ToString]() { - return "invalid"; - } -} \ No newline at end of file diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts deleted file mode 100644 index a925148c2..000000000 --- a/src/new_fields/DateField.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, date } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString } from "./FieldSymbols"; -import { scriptingGlobal, Scripting } from "../client/util/Scripting"; - -@scriptingGlobal -@Deserializable("date") -export class DateField extends ObjectField { - @serializable(date()) - readonly date: Date; - - constructor(date: Date = new Date()) { - super(); - this.date = date; - } - - [Copy]() { - return new DateField(this.date); - } - - toString() { - return `${this.date.toISOString()}`; - } - - [ToScriptString]() { - return `new DateField(new Date(${this.date.toISOString()}))`; - } - [ToString]() { - return this.date.toISOString(); - } -} - -Scripting.addGlobal(function d(...dateArgs: any[]) { - return new DateField(new (Date as any)(...dateArgs)); -}); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts deleted file mode 100644 index a1e1e11b1..000000000 --- a/src/new_fields/Doc.ts +++ /dev/null @@ -1,1109 +0,0 @@ -import { action, computed, observable, ObservableMap, runInAction } from "mobx"; -import { computedFn } from "mobx-utils"; -import { alias, map, serializable } from "serializr"; -import { DocServer } from "../client/DocServer"; -import { DocumentType } from "../client/documents/DocumentTypes"; -import { Scripting, scriptingGlobal } from "../client/util/Scripting"; -import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; -import { UndoManager } from "../client/util/UndoManager"; -import { intersectRect, Utils } from "../Utils"; -import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols"; -import { List } from "./List"; -import { ObjectField } from "./ObjectField"; -import { PrefetchProxy, ProxyField } from "./Proxy"; -import { FieldId, RefField } from "./RefField"; -import { RichTextField } from "./RichTextField"; -import { listSpec } from "./Schema"; -import { ComputedField, ScriptField } from "./ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types"; -import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; -import { Docs, DocumentOptions } from "../client/documents/Documents"; -import { PdfField, VideoField, AudioField, ImageField } from "./URLField"; -import { LinkManager } from "../client/util/LinkManager"; - -export namespace Field { - export function toKeyValueString(doc: Doc, key: string): string { - const onDelegate = Object.keys(doc).includes(key); - - const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (Field.IsField(field)) { - return (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); - } - return ""; - } - export function toScriptString(field: Field): string { - if (typeof field === "string") { - return `"${field}"`; - } else if (typeof field === "number" || typeof field === "boolean") { - return String(field); - } else { - return field[ToScriptString](); - } - } - export function toString(field: Field): string { - if (typeof field === "string") { - return field; - } else if (typeof field === "number" || typeof field === "boolean") { - return String(field); - } else if (field instanceof ObjectField) { - return field[ToString](); - } else if (field instanceof RefField) { - return field[ToString](); - } - return ""; - } - export function IsField(field: any): field is Field; - export function IsField(field: any, includeUndefined: true): field is Field | undefined; - export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined { - return (typeof field === "string") - || (typeof field === "number") - || (typeof field === "boolean") - || (field instanceof ObjectField) - || (field instanceof RefField) - || (includeUndefined && field === undefined); - } -} -export type Field = number | string | boolean | ObjectField | RefField; -export type Opt = T | undefined; -export type FieldWaiting = T extends undefined ? never : Promise; -export type FieldResult = Opt | FieldWaiting>; - -/** - * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs. - * If a default value is given, that will be returned instead of undefined. - * If a default value is given, the returned value should not be modified as it might be a temporary value. - * If no default value is given, and the returned value is not undefined, it can be safely modified. - */ -export function DocListCastAsync(field: FieldResult): Promise; -export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise; -export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { - const list = Cast(field, listSpec(Doc)); - return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); -} - -export async function DocCastAsync(field: FieldResult): Promise> { - return Cast(field, Doc); -} - -export function DocListCast(field: FieldResult): Doc[] { - return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; -} - -export const WidthSym = Symbol("Width"); -export const HeightSym = Symbol("Height"); -export const DataSym = Symbol("Data"); -export const LayoutSym = Symbol("Layout"); -export const UpdatingFromServer = Symbol("UpdatingFromServer"); -const CachedUpdates = Symbol("Cached updates"); - - -function fetchProto(doc: Doc) { - const proto = doc.proto; - if (proto instanceof Promise) { - return proto; - } -} - -@scriptingGlobal -@Deserializable("Doc", fetchProto).withFields(["id"]) -export class Doc extends RefField { - constructor(id?: FieldId, forceSave?: boolean) { - super(id); - const doc = new Proxy(this, { - set: setter, - get: getter, - // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter - has: (target, key) => key in target.__fields, - ownKeys: target => { - const obj = {} as any; - Object.assign(obj, target.___fields); - runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); - return Object.keys(obj); - }, - getOwnPropertyDescriptor: (target, prop) => { - if (prop.toString() === "__LAYOUT__") { - return Reflect.getOwnPropertyDescriptor(target, prop); - } - if (prop in target.__fields) { - return { - configurable: true,//TODO Should configurable be true? - enumerable: true, - value: target.__fields[prop] - }; - } - return Reflect.getOwnPropertyDescriptor(target, prop); - }, - deleteProperty: deleteProperty, - defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, - }); - this[SelfProxy] = doc; - if (!id || forceSave) { - DocServer.CreateField(doc); - } - return doc; - } - - proto: Opt; - [key: string]: FieldResult; - - @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize }))) - private get __fields() { return this.___fields; } - private set __fields(value) { - this.___fields = value; - for (const key in value) { - const field = value[key]; - if (!(field instanceof ObjectField)) continue; - field[Parent] = this[Self]; - field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); - } - } - - @observable - //{ [key: string]: Field | FieldWaiting | undefined } - private ___fields: any = {}; - - private [UpdatingFromServer]: boolean = false; - - private [Update] = (diff: any) => { - !this[UpdatingFromServer] && DocServer.UpdateField(this[Id], diff); - } - - private [Self] = this; - private [SelfProxy]: any; - public [WidthSym] = () => NumCast(this[SelfProxy]._width); - public [HeightSym] = () => NumCast(this[SelfProxy]._height); - public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } - public get [DataSym]() { - const self = this[SelfProxy]; - return self.resolvedDataDoc && !self.isTemplateForField ? self : - Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); - } - @computed get __LAYOUT__() { - const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); - if (templateLayoutDoc) { - let renderFieldKey: any; - const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")]; - if (typeof layoutField === "string") { - renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1]; - } else { - return Cast(layoutField, Doc, null); - } - return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc; - } - return undefined; - } - - [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } - [ToString]() { return `Doc(${this.title})`; } - - private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - public static CurrentUserEmail: string = ""; - public async [HandleUpdate](diff: any) { - const set = diff.$set; - const sameAuthor = this.author === Doc.CurrentUserEmail; - if (set) { - for (const key in set) { - if (!key.startsWith("fields.")) { - continue; - } - const fKey = key.substring(7); - const fn = async () => { - const value = await SerializationHelper.Deserialize(set[key]); - this[UpdatingFromServer] = true; - this[fKey] = value; - this[UpdatingFromServer] = false; - }; - if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { - delete this[CachedUpdates][fKey]; - await fn(); - } else { - this[CachedUpdates][fKey] = fn; - } - } - } - const unset = diff.$unset; - if (unset) { - for (const key in unset) { - if (!key.startsWith("fields.")) { - continue; - } - const fKey = key.substring(7); - const fn = () => { - this[UpdatingFromServer] = true; - delete this[fKey]; - this[UpdatingFromServer] = false; - }; - if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { - delete this[CachedUpdates][fKey]; - await fn(); - } else { - this[CachedUpdates][fKey] = fn; - } - } - } - } -} - -export namespace Doc { - // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise { - // const self = doc[Self]; - // return new Promise(res => getField(self, key, ignoreProto, res)); - // } - // export function GetTAsync(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): Promise { - // return new Promise(async res => { - // const field = await GetAsync(doc, key, ignoreProto); - // return Cast(field, ctor); - // }); - // } - - export function RunCachedUpdate(doc: Doc, field: string) { - const update = doc[CachedUpdates][field]; - if (update) { - update(); - delete doc[CachedUpdates][field]; - } - } - export function AddCachedUpdate(doc: Doc, field: string, oldValue: any) { - const val = oldValue; - doc[CachedUpdates][field] = () => { - doc[UpdatingFromServer] = true; - doc[field] = val; - doc[UpdatingFromServer] = false; - }; - } - export function MakeReadOnly(): { end(): void } { - makeReadOnly(); - return { - end() { - makeEditable(); - } - }; - } - - export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult { - try { - return getField(doc[Self], key, ignoreProto); - } catch { - return doc; - } - } - export function GetT(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): FieldResult { - return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult; - } - export function IsPrototype(doc: Doc) { - return GetT(doc, "isPrototype", "boolean", true); - } - export function IsBaseProto(doc: Doc) { - return GetT(doc, "baseProto", "boolean", true); - } - export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { - const hasProto = doc.proto instanceof Doc; - const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; - const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; - if (onDeleg || !hasProto || (!onProto && !defaultProto)) { - doc[key] = value; - } else doc.proto![key] = value; - } - export async function SetOnPrototype(doc: Doc, key: string, value: Field) { - const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc; - - if (proto) { - proto[key] = value; - } - } - export function GetAllPrototypes(doc: Doc): Doc[] { - const protos: Doc[] = []; - let d: Opt = doc; - while (d) { - protos.push(d); - d = FieldValue(d.proto); - } - return protos; - } - - /** - * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies - * the values of the properties of a source object into the target. - * - * This is just a specific, Dash-authored version that serves the same role for our - * Doc class. - * - * @param doc the target document into which you'd like to insert the new fields - * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key - * to a potentially undefined field, where each entry in this mapping is optional. - */ - export function assign(doc: Doc, fields: Partial>>, skipUndefineds: boolean = false) { - for (const key in fields) { - if (fields.hasOwnProperty(key)) { - const value = fields[key]; - if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds? - doc[key] = value; - } - } - } - return doc; - } - - // compare whether documents or their protos match - export function AreProtosEqual(doc?: Doc, other?: Doc) { - if (!doc || !other) return false; - const r = (doc === other); - const r2 = (Doc.GetProto(doc) === other); - const r3 = (Doc.GetProto(other) === doc); - const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined); - return r || r2 || r3 || r4; - } - - // Gets the data document for the document. Note: this is mis-named -- it does not specifically - // return the doc's proto, but rather recursively searches through the proto inheritance chain - // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype'). - export function GetProto(doc: Doc): Doc { - if (doc instanceof Promise) { - console.log("GetProto: error: got Promise insead of Doc"); - } - const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); - return proto === doc ? proto : Doc.GetProto(proto); - } - export function GetDataDoc(doc: Doc): Doc { - const proto = Doc.GetProto(doc); - return proto === doc ? proto : Doc.GetDataDoc(proto); - } - - export function allKeys(doc: Doc): string[] { - const results: Set = new Set; - - let proto: Doc | undefined = doc; - while (proto) { - Object.keys(proto).forEach(key => results.add(key)); - proto = proto.proto; - } - - return Array.from(results); - } - - export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) { - let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1); - index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1); - return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); - } - export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) { - const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); - if (listDoc[key] === undefined) { - Doc.GetProto(listDoc)[key] = new List(); - } - const list = Cast(listDoc[key], listSpec(Doc)); - if (list) { - const ind = list.indexOf(doc); - if (ind !== -1) { - list.splice(ind, 1); - return true; - } - } - return false; - } - export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { - const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); - if (listDoc[key] === undefined) { - Doc.GetProto(listDoc)[key] = new List(); - } - const list = Cast(listDoc[key], listSpec(Doc)); - if (list) { - if (allowDuplicates !== true) { - const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); - if (pind !== -1) { - list.splice(pind, 1); - } - } - if (first) { - list.splice(0, 0, doc); - } - else { - const ind = relativeTo ? list.indexOf(relativeTo) : -1; - if (ind === -1) { - if (reversed) list.splice(0, 0, doc); - else list.push(doc); - } - else { - if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc); - else list.splice(before ? ind : ind + 1, 0, doc); - } - } - return true; - } - return false; - } - - // - // Computes the bounds of the contents of a set of documents. - // - export function ComputeContentBounds(docList: Doc[]) { - const bounds = docList.reduce((bounds, doc) => { - const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }); - return bounds; - } - - export function MakeAlias(doc: Doc, id?: string) { - const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); - const layout = Doc.LayoutField(alias); - if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) { - Doc.SetLayout(alias, Doc.MakeAlias(layout)); - } - alias.aliasOf = doc; - alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); - return alias; - } - - // - // Determines whether the layout needs to be expanded (as a template). - // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template - // - export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) { - return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc; - } - - const _pendingMap: Map = new Map(); - // - // Returns an expanded template layout for a target data document if there is a template relationship - // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties - // of the original layout while allowing for individual layout properties to be overridden in the expanded layout. - // templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key. - // NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field - // so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and - // the derefence will then occur on the rootDocument (the original document). - // in the future, field references could be written as @ and then arguments would be passed in the layout key as: - // layout_mytemplate(somparam=somearg). - // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument - export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) { - const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS); - if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc; - - const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders - // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc - // using the template layout doc's id as the field key. - // If it doesn't find the expanded layout, then it makes a delegate of the template layout and - // saves it on the data doc indexed by the template layout's id. - // - const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS"; - const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); - const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]"; - let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; - - if (templateLayoutDoc.resolvedDataDoc instanceof Promise) { - expandedTemplateLayout = undefined; - _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); - } - else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { - if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) { - expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params - } else { - _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true); - templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype - setTimeout(action(() => { - if (!targetDoc[expandedLayoutFieldKey]) { - const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"); - // the template's arguments are stored in params which is derefenced to find - // the actual field key where the parameterized template data is stored. - newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances - newLayoutDoc.rootDocument = targetDoc; - targetDoc[expandedLayoutFieldKey] = newLayoutDoc; - const dataDoc = Doc.GetProto(targetDoc); - newLayoutDoc.resolvedDataDoc = dataDoc; - if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List) { - dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc }); - } - _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); - } - }), 0); - } - } - return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending. - } - - // if the childDoc is a template for a field, then this will return the expanded layout with its data doc. - // otherwise, it just returns the childDoc - export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt, childDoc: Doc) { - if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) { - console.log("No, no, no!"); - return { layout: childDoc, data: childDoc }; - } - const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc); - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc }; - } - - export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { - Object.keys(doc).forEach(key => { - const field = ProxyField.WithoutProxy(() => doc[key]); - if (key === "proto" && copyProto) { - if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) { - overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto); - } - } else { - if (field instanceof RefField) { - overwrite[key] = field; - } else if (field instanceof ObjectField) { - overwrite[key] = ObjectField.MakeCopy(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happend... - } else { - overwrite[key] = field; - } - } - }); - - return overwrite; - } - - export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { - const copy = new Doc(copyProtoId, true); - const exclude = Cast(doc.excludeFields, listSpec("string"), []); - Object.keys(doc).forEach(key => { - if (exclude.includes(key)) return; - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); - if (key === "proto" && copyProto) { - if (doc[key] instanceof Doc) { - copy[key] = Doc.MakeCopy(doc[key]!, false); - } - } else { - if (field instanceof RefField) { - copy[key] = field; - } else if (cfield instanceof ComputedField) { - copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - } else if (field instanceof ObjectField) { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields - ObjectField.MakeCopy(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happend... - } else { - copy[key] = field; - } - } - }); - - return copy; - } - - export function MakeClone(doc: Doc): Doc { - const cloneMap = new Map(); - const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = Doc.makeClone(doc, cloneMap, rtfMap); - rtfMap.map(({ copy, key, field }) => { - const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { - const mapped = cloneMap.get(id); - return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; - }; - const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { - const mapped = cloneMap.get(id); - return href + (mapped ? mapped[Id] : id); - }; - const regex = `(${Utils.prepend("/doc/")})([^"]*)`; - const re = new RegExp(regex, "g"); - copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); - }); - return copy; - } - - export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { - if (Doc.IsBaseProto(doc)) return doc; - if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = new Doc(undefined, true); - cloneMap.set(doc[Id], copy); - if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); - const exclude = Cast(doc.excludeFields, listSpec("string"), []); - Object.keys(doc).forEach(key => { - if (exclude.includes(key)) return; - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); - const copyObjectField = (field: ObjectField) => { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); - } else if (doc[key] instanceof Doc) { - copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields - } else { - copy[key] = ObjectField.MakeCopy(field); - if (field instanceof RichTextField) { - if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { - rtfs.push({ copy, key, field }); - } - } - } - }; - if (key === "proto") { - if (doc[key] instanceof Doc) { - copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); - } - } else { - if (field instanceof RefField) { - copy[key] = field; - } else if (cfield instanceof ComputedField) { - copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - (key === "links" && field instanceof ObjectField) && copyObjectField(field); - } else if (field instanceof ObjectField) { - copyObjectField(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happend... - } else { - copy[key] = field; - } - } - }); - Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); - copy.cloneOf = doc; - cloneMap.set(doc[Id], copy); - return copy; - } - - export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc; - export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt; - export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt { - if (doc) { - const delegate = new Doc(id, true); - delegate.proto = doc; - title && (delegate.title = title); - return delegate; - } - return undefined; - } - - let _applyCount: number = 0; - export function ApplyTemplate(templateDoc: Doc) { - if (templateDoc) { - const target = Doc.MakeDelegate(new Doc()); - const targetKey = StrCast(templateDoc.layoutKey, "layout"); - const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); - target.layoutKey = targetKey; - applied && (Doc.GetProto(applied).type = templateDoc.type); - return applied; - } - return undefined; - } - export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) { - if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) { - if (target.resolvedDataDoc) { - target[targetKey] = new PrefetchProxy(templateDoc); - } else { - titleTarget && (Doc.GetProto(target).title = titleTarget); - Doc.GetProto(target)[targetKey] = new PrefetchProxy(templateDoc); - } - } - return target; - } - - // - // This function converts a generic field layout display into a field layout that displays a specific - // metadata field indicated by the title of the template field (not the default field that it was rendering) - // - export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt): boolean { - - // find the metadata field key that this template field doc will display (indicated by its title) - const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ""); - - // update the original template to mark it as a template - templateField.isTemplateForField = metadataFieldKey; - templateField.title = metadataFieldKey; - - const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)]; - const templateCaptionValue = templateField.caption; - // move any data that the template field had been rendering over to the template doc so that things will still be rendered - // when the template field is adjusted to point to the new metadatafield key. - // note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well. - // note 2: this will not overwrite any field that already exists on the template doc at the field key - if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) { - Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc)); - (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue)); - } - // copy the textTemplates from 'this' (not 'self') because the layout contains the template info, not the original doc - if (templateCaptionValue instanceof RichTextField && !templateCaptionValue.Empty()) { - templateField["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`); - } - if (templateFieldValue instanceof RichTextField && !templateFieldValue.Empty()) { - templateField[metadataFieldKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${metadataFieldKey})`); - } - - // get the layout string that the template uses to specify its layout - const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField))); - - // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document. - Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`); - - // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino) - Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc); - - return true; - } - - export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { - const doc2Layout = Doc.Layout(doc2); - const doc1Layout = Doc.Layout(doc1); - const x2 = NumCast(doc2.x) - clusterDistance; - const y2 = NumCast(doc2.y) - clusterDistance; - const w2 = NumCast(doc2Layout._width) + clusterDistance; - const h2 = NumCast(doc2Layout._height) + clusterDistance; - const x = NumCast(doc1.x) - clusterDistance; - const y = NumCast(doc1.y) - clusterDistance; - const w = NumCast(doc1Layout._width) + clusterDistance; - const h = NumCast(doc1Layout._height) + clusterDistance; - return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); - } - - export function isBrushedHighlightedDegree(doc: Doc) { - if (Doc.IsHighlighted(doc)) { - return 6; - } - else { - return Doc.IsBrushedDegree(doc); - } - } - - export class DocBrush { - BrushedDoc: ObservableMap = new ObservableMap(); - } - const brushManager = new DocBrush(); - - export class DocData { - @observable _user_doc: Doc = undefined!; - @observable _searchQuery: string = ""; - } - - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field or 'layout' is given. - export function Layout(doc: Doc, layout?: Doc): Doc { - const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null); - return overrideLayout || doc[LayoutSym] || doc; - } - export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; } - export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } - export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; } - const manager = new DocData(); - export function SearchQuery(): string { return manager._searchQuery; } - export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } - export function UserDoc(): Doc { return manager._user_doc; } - export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } - export function IsBrushed(doc: Doc) { - return computedFn(function IsBrushed(doc: Doc) { - return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); - })(doc); - } - // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) - export function IsBrushedDegreeUnmemoized(doc: Doc) { - return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; - } - export function IsBrushedDegree(doc: Doc) { - return computedFn(function IsBrushDegree(doc: Doc) { - return Doc.IsBrushedDegreeUnmemoized(doc); - })(doc); - } - export function BrushDoc(doc: Doc) { - brushManager.BrushedDoc.set(doc, true); - brushManager.BrushedDoc.set(Doc.GetProto(doc), true); - return doc; - } - export function UnBrushDoc(doc: Doc) { - brushManager.BrushedDoc.delete(doc); - brushManager.BrushedDoc.delete(Doc.GetProto(doc)); - return doc; - } - - - export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; } - export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; } - - export function linkFollowUnhighlight() { - Doc.UnhighlightAll(); - document.removeEventListener("pointerdown", linkFollowUnhighlight); - } - - let _lastDate = 0; - export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) { - linkFollowUnhighlight(); - Doc.HighlightDoc(destDoc, dataAndDisplayDocs); - document.removeEventListener("pointerdown", linkFollowUnhighlight); - document.addEventListener("pointerdown", linkFollowUnhighlight); - const lastDate = _lastDate = Date.now(); - window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); - } - - export class HighlightBrush { - @observable HighlightedDoc: Map = new Map(); - } - const highlightManager = new HighlightBrush(); - export function IsHighlighted(doc: Doc) { - return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); - } - export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { - runInAction(() => { - highlightManager.HighlightedDoc.set(doc, true); - dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true); - }); - } - export function UnHighlightDoc(doc: Doc) { - runInAction(() => { - highlightManager.HighlightedDoc.set(doc, false); - highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false); - }); - } - export function UnhighlightAll() { - const mapEntries = highlightManager.HighlightedDoc.keys(); - let docEntry: IteratorResult; - while (!(docEntry = mapEntries.next()).done) { - const targetDoc = docEntry.value; - targetDoc && Doc.UnHighlightDoc(targetDoc); - } - - } - export function UnBrushAllDocs() { - brushManager.BrushedDoc.clear(); - } - - export function getDocTemplate(doc?: Doc) { - return doc?.isTemplateDoc ? doc : - Cast(doc?.dragFactory, Doc, null)?.isTemplateDoc ? doc?.dragFactory : - Cast(doc?.layout, Doc, null)?.isTemplateDoc ? doc?.layout : undefined; - } - - export function matchFieldValue(doc: Doc, key: string, value: any): boolean { - const fieldVal = doc[key]; - if (Cast(fieldVal, listSpec("string"), []).length) { - const vals = Cast(fieldVal, listSpec("string"), []); - return vals.some(v => v === value); - } - const fieldStr = Field.toString(fieldVal as Field); - return fieldStr === value; - } - - export function deiconifyView(doc: any) { - StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); - } - - export function setNativeView(doc: any) { - const prevLayout = StrCast(doc.layoutKey).split("_")[1]; - const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : ""; - prevLayout === "icon" && (doc.deiconifyLayout = undefined); - doc.layoutKey = deiconify || "layout"; - } - export function setDocFilterRange(target: Doc, key: string, range?: number[]) { - const docRangeFilters = Cast(target._docRangeFilters, listSpec("string"), []); - for (let i = 0; i < docRangeFilters.length; i += 3) { - if (docRangeFilters[i] === key) { - docRangeFilters.splice(i, 3); - break; - } - } - if (range !== undefined) { - docRangeFilters.push(key); - docRangeFilters.push(range[0].toString()); - docRangeFilters.push(range[1].toString()); - target._docRangeFilters = new List(docRangeFilters); - } - } - - export function aliasDocs(field: any) { - return new List(field.map((d: any) => Doc.MakeAlias(d))); - } - - // filters document in a container collection: - // all documents with the specified value for the specified key are included/excluded - // based on the modifiers :"check", "x", undefined - export function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { - const docFilters = Cast(container._docFilters, listSpec("string"), []); - for (let i = 0; i < docFilters.length; i += 3) { - if (docFilters[i] === key && docFilters[i + 1] === value) { - docFilters.splice(i, 3); - break; - } - } - if (typeof modifiers === "string") { - docFilters.push(key); - docFilters.push(value); - docFilters.push(modifiers); - container._docFilters = new List(docFilters); - } - } - export function readDocRangeFilter(doc: Doc, key: string) { - const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []); - for (let i = 0; i < docRangeFilters.length; i += 3) { - if (docRangeFilters[i] === key) { - return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])]; - } - } - } - export function assignDocToField(doc: Doc, field: string, id: string) { - DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout)); - return id; - } - - export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) { - runInAction(() => { - if (layoutDoc._nativeWidth || layoutDoc._nativeHeight) { - layoutDoc.scale = NumCast(layoutDoc.scale, 1) * contentScale; - layoutDoc._nativeWidth = undefined; - layoutDoc._nativeHeight = undefined; - } - else { - layoutDoc._autoHeight = false; - if (!layoutDoc._nativeWidth) { - layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); - layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight); - } - } - }); - } - - export function isDocPinned(doc: Doc) { - //add this new doc to props.Document - const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; - if (curPres) { - return DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; - } - return false; - } - - // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) - export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) { - const batch = UndoManager.StartBatch("makeCustomViewClicked"); - runInAction(() => { - doc.layoutKey = "layout_" + templateSignature; - if (doc[doc.layoutKey] === undefined) { - createCustomView(doc, creator, templateSignature, docLayoutTemplate); - } - }); - batch.end(); - } - export function findTemplate(templateName: string, type: string, signature: string) { - let docLayoutTemplate: Opt; - const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data); - const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); - const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data); - const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data); - const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc); - // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized - // first try to find a template that matches the specific document type (_). otherwise, fallback to a general match on - !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc)); - !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc)); - return docLayoutTemplate; - } - export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) { - const templateName = templateSignature.replace(/\(.*\)/, ""); - docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature); - - const customName = "layout_" + templateSignature; - const _width = NumCast(doc._width); - const _height = NumCast(doc._height); - const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; - - let fieldTemplate: Opt; - if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { - fieldTemplate = Docs.Create.TextDocument("", options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - } - const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); - docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); - } - export function makeCustomView(doc: Doc, custom: boolean, layout: string) { - Doc.setNativeView(doc); - if (custom) { - makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined); - } - } - export function iconify(doc: Doc) { - const layoutKey = Cast(doc.layoutKey, "string", null); - Doc.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined); - if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); - } - - export function pileup(selected: Doc[], x: number, y: number) { - const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true }); - let w = 0, h = 0; - selected.forEach((d, i) => { - Doc.iconify(d); - w = Math.max(d[WidthSym](), w); - h = Math.max(d[HeightSym](), h); - }); - h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable - selected.forEach((d, i) => { - d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2; - d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2; - d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; - newCollection._width = newCollection._height = 110; - //newCollection.borderRounding = "40px"; - newCollection._jitterRotation = 10; - newCollection._backgroundColor = "gray"; - newCollection._overflow = "visible"; - return newCollection; - } - - - export async function addFieldEnumerations(doc: Opt, 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); - Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc); - } - const options = optionsCollection as Doc; - const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc); - const docFind = `options.data.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`; - targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options })); - targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options })); - targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options })); - enumerations.map(enumeration => { - const found = DocListCast(options.data).find(d => d.title === enumeration.title); - if (found) { - found._backgroundColor = enumeration._backgroundColor || found._backgroundColor; - found._color = enumeration.color || found._color; - } else { - Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, enumeration)); - } - }); - return optionsCollection; - } -} - -Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; }); -Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); -Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); -Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); -Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); -Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); -Scripting.addGlobal(function aliasDocs(field: any) { return Doc.aliasDocs(field); }); -Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); -Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); -Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -Scripting.addGlobal(function deiconifyView(doc: any) { Doc.deiconifyView(doc); }); -Scripting.addGlobal(function undo() { return UndoManager.Undo(); }); -Scripting.addGlobal(function redo() { return UndoManager.Redo(); }); -Scripting.addGlobal(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; }); -Scripting.addGlobal(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); -Scripting.addGlobal(function docCast(doc: FieldResult): any { return DocCastAsync(doc); }); -Scripting.addGlobal(function activePresentationItem() { - const curPres = Doc.UserDoc().activePresentation as Doc; - return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; -}); -Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().activeSelection = new List([doc]); }); -Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { - const docs = DocListCast(Doc.UserDoc().activeSelection). - filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCHOLDER && d.type !== DocumentType.KVP && - (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); - return docs.length ? new List(docs) : prevValue; -}); -Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); }); -Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); }); \ No newline at end of file diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts deleted file mode 100644 index 8d040f493..000000000 --- a/src/new_fields/FieldSymbols.ts +++ /dev/null @@ -1,12 +0,0 @@ - -export const Update = Symbol("Update"); -export const Self = Symbol("Self"); -export const SelfProxy = Symbol("SelfProxy"); -export const HandleUpdate = Symbol("HandleUpdate"); -export const Id = Symbol("Id"); -export const OnUpdate = Symbol("OnUpdate"); -export const Parent = Symbol("Parent"); -export const Copy = Symbol("Copy"); -export const ToScriptString = Symbol("ToScriptString"); -export const ToPlainText = Symbol("ToPlainText"); -export const ToString = Symbol("ToString"); diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts deleted file mode 100644 index 6e8bba977..000000000 --- a/src/new_fields/HtmlField.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, primitive } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString} from "./FieldSymbols"; - -@Deserializable("html") -export class HtmlField extends ObjectField { - @serializable(primitive()) - readonly html: string; - - constructor(html: string) { - super(); - this.html = html; - } - - [Copy]() { - return new HtmlField(this.html); - } - - [ToScriptString]() { - return "invalid"; - } - [ToString]() { - return this.html; - } -} diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts deleted file mode 100644 index 76c4ddf1b..000000000 --- a/src/new_fields/IconField.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, primitive } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString } from "./FieldSymbols"; - -@Deserializable("icon") -export class IconField extends ObjectField { - @serializable(primitive()) - readonly icon: string; - - constructor(icon: string) { - super(); - this.icon = icon; - } - - [Copy]() { - return new IconField(this.icon); - } - - [ToScriptString]() { - return "invalid"; - } - [ToString]() { - return "ICONfield"; - } -} diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts deleted file mode 100644 index bb93de5ac..000000000 --- a/src/new_fields/InkField.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, custom, createSimpleSchema, list, object, map } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString } from "./FieldSymbols"; - -export enum InkTool { - None, - Pen, - Highlighter, - Eraser, - Stamp -} - -export interface PointData { - X: number; - Y: number; -} - -export type InkData = Array; - -const pointSchema = createSimpleSchema({ - X: true, Y: true -}); - -const strokeDataSchema = createSimpleSchema({ - pathData: list(object(pointSchema)), - "*": true -}); - -@Deserializable("ink") -export class InkField extends ObjectField { - @serializable(list(object(strokeDataSchema))) - readonly inkData: InkData; - - constructor(data: InkData) { - super(); - this.inkData = data; - } - - [Copy]() { - return new InkField(this.inkData); - } - - [ToScriptString]() { - return "invalid"; - } - [ToString]() { - return "InkField"; - } -} diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts deleted file mode 100644 index fdabea365..000000000 --- a/src/new_fields/List.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/SerializationHelper"; -import { Field } from "./Doc"; -import { setter, getter, deleteProperty, updateFunction } from "./util"; -import { serializable, alias, list } from "serializr"; -import { observable, action, runInAction } from "mobx"; -import { ObjectField } from "./ObjectField"; -import { RefField } from "./RefField"; -import { ProxyField } from "./Proxy"; -import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy, Id } from "./FieldSymbols"; -import { Scripting } from "../client/util/Scripting"; -import { DocServer } from "../client/DocServer"; - -const listHandlers: any = { - /// Mutator methods - copyWithin() { - throw new Error("copyWithin not supported yet"); - }, - fill(value: any, start?: number, end?: number) { - if (value instanceof RefField) { - throw new Error("fill with RefFields not supported yet"); - } - const res = this[Self].__fields.fill(value, start, end); - this[Update](); - return res; - }, - pop(): any { - const field = toRealField(this[Self].__fields.pop()); - this[Update](); - return field; - }, - push: action(function (this: any, ...items: any[]) { - items = items.map(toObjectField); - const list = this[Self]; - const length = list.__fields.length; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - //TODO Error checking to make sure parent doesn't already exist - if (item instanceof ObjectField) { - item[Parent] = list; - item[OnUpdate] = updateFunction(list, i + length, item, this); - } - } - const res = list.__fields.push(...items); - this[Update](); - return res; - }), - reverse() { - const res = this[Self].__fields.reverse(); - this[Update](); - return res; - }, - shift() { - const res = toRealField(this[Self].__fields.shift()); - this[Update](); - return res; - }, - sort(cmpFunc: any) { - this[Self].__realFields(); // coerce retrieving entire array - const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined); - this[Update](); - return res; - }, - splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) { - this[Self].__realFields(); // coerce retrieving entire array - items = items.map(toObjectField); - const list = this[Self]; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - //TODO Error checking to make sure parent doesn't already exist - //TODO Need to change indices of other fields in array - if (item instanceof ObjectField) { - item[Parent] = list; - item[OnUpdate] = updateFunction(list, i + start, item, this); - } - } - const res = list.__fields.splice(start, deleteCount, ...items); - this[Update](); - return res.map(toRealField); - }), - unshift(...items: any[]) { - items = items.map(toObjectField); - const list = this[Self]; - const length = list.__fields.length; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - //TODO Error checking to make sure parent doesn't already exist - //TODO Need to change indices of other fields in array - if (item instanceof ObjectField) { - item[Parent] = list; - item[OnUpdate] = updateFunction(list, i, item, this); - } - } - const res = this[Self].__fields.unshift(...items); - this[Update](); - return res; - - }, - /// Accessor methods - concat: action(function (this: any, ...items: any[]) { - this[Self].__realFields(); - return this[Self].__fields.map(toRealField).concat(...items); - }), - includes(valueToFind: any, fromIndex: number) { - if (valueToFind instanceof RefField) { - return this[Self].__realFields().includes(valueToFind, fromIndex); - } else { - return this[Self].__fields.includes(valueToFind, fromIndex); - } - }, - indexOf(valueToFind: any, fromIndex: number) { - if (valueToFind instanceof RefField) { - return this[Self].__realFields().indexOf(valueToFind, fromIndex); - } else { - return this[Self].__fields.indexOf(valueToFind, fromIndex); - } - }, - join(separator: any) { - this[Self].__realFields(); - return this[Self].__fields.map(toRealField).join(separator); - }, - lastIndexOf(valueToFind: any, fromIndex: number) { - if (valueToFind instanceof RefField) { - return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex); - } else { - return this[Self].__fields.lastIndexOf(valueToFind, fromIndex); - } - }, - slice(begin: number, end: number) { - this[Self].__realFields(); - return this[Self].__fields.slice(begin, end).map(toRealField); - }, - - /// Iteration methods - entries() { - return this[Self].__realFields().entries(); - }, - every(callback: any, thisArg: any) { - return this[Self].__realFields().every(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - filter(callback: any, thisArg: any) { - return this[Self].__realFields().filter(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - find(callback: any, thisArg: any) { - return this[Self].__realFields().find(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - findIndex(callback: any, thisArg: any) { - return this[Self].__realFields().findIndex(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - forEach(callback: any, thisArg: any) { - return this[Self].__realFields().forEach(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - map(callback: any, thisArg: any) { - return this[Self].__realFields().map(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - reduce(callback: any, initialValue: any) { - return this[Self].__realFields().reduce(callback, initialValue); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); - }, - reduceRight(callback: any, initialValue: any) { - return this[Self].__realFields().reduceRight(callback, initialValue); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); - }, - some(callback: any, thisArg: any) { - return this[Self].__realFields().some(callback, thisArg); - // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. - // If we don't want to support the array parameter, we should use this version instead - // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); - }, - values() { - return this[Self].__realFields().values(); - }, - [Symbol.iterator]() { - return this[Self].__realFields().values(); - } -}; - -function toObjectField(field: Field) { - return field instanceof RefField ? new ProxyField(field) : field; -} - -function toRealField(field: Field) { - return field instanceof ProxyField ? field.value() : field; -} - -function listGetter(target: any, prop: string | number | symbol, receiver: any): any { - if (listHandlers.hasOwnProperty(prop)) { - return listHandlers[prop]; - } - return getter(target, prop, receiver); -} - -interface ListSpliceUpdate { - type: "splice"; - index: number; - added: T[]; - removedCount: number; -} - -interface ListIndexUpdate { - type: "update"; - index: number; - newValue: T; -} - -type ListUpdate = ListSpliceUpdate | ListIndexUpdate; - -type StoredType = T extends RefField ? ProxyField : T; - -@Deserializable("list") -class ListImpl extends ObjectField { - constructor(fields?: T[]) { - super(); - const list = new Proxy(this, { - set: setter, - get: listGetter, - ownKeys: target => Object.keys(target.__fields), - getOwnPropertyDescriptor: (target, prop) => { - if (prop in target.__fields) { - return { - configurable: true,//TODO Should configurable be true? - enumerable: true, - }; - } - return Reflect.getOwnPropertyDescriptor(target, prop); - }, - deleteProperty: deleteProperty, - defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, - }); - this[SelfProxy] = list; - if (fields) { - (list as any).push(...fields); - } - return list; - } - - [key: number]: T | (T extends RefField ? Promise : never); - - // this requests all ProxyFields at the same time to avoid the overhead - // of separate network requests and separate updates to the React dom. - private __realFields() { - const waiting = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()); - const promised = waiting.map(f => f instanceof ProxyField ? f.promisedValue() : ""); - // if we find any ProxyFields that don't have a current value, then - // start the server request for all of them - if (promised.length) { - const promise = DocServer.GetRefFields(promised); - // as soon as we get the fields from the server, set all the list values in one - // action to generate one React dom update. - promise.then(fields => runInAction(() => { - waiting.map((w, i) => w instanceof ProxyField && w.setValue(fields[promised[i]])); - })); - // we also have to mark all lists items with this promise so that any calls to them - // will await the batch request. - // This counts on the handler for 'promise' in the call above being invoked before the - // handler for 'promise' in the lines below. - waiting.map((w, i) => { - w instanceof ProxyField && w.setPromise(promise.then(fields => fields[promised[i]])); - }); - } - return this.__fields.map(toRealField); - } - - @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize }))) - private get __fields() { - return this.___fields; - } - - private set __fields(value) { - this.___fields = value; - for (const key in value) { - const field = value[key]; - if (!(field instanceof ObjectField)) continue; - (field as ObjectField)[Parent] = this[Self]; - (field as ObjectField)[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); - } - } - - [Copy]() { - const copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f); - const deepCopy = new ListImpl(copiedData as any); - return deepCopy; - } - - // @serializable(alias("fields", list(autoObject()))) - @observable - private ___fields: StoredType[] = []; - - private [Update] = (diff: any) => { - // console.log(diff); - const update = this[OnUpdate]; - // update && update(diff); - update?.(); - } - - private [Self] = this; - private [SelfProxy]: any; - - [ToScriptString]() { - return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; - } - [ToString]() { - return "List"; - } -} -export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; -export const List: { new (fields?: T[]): List } = ListImpl as any; - -Scripting.addGlobal("List", List); \ No newline at end of file diff --git a/src/new_fields/ListSpec.ts b/src/new_fields/ListSpec.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts deleted file mode 100644 index 9aa1c9b04..000000000 --- a/src/new_fields/ObjectField.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RefField } from "./RefField"; -import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols"; -import { Scripting } from "../client/util/Scripting"; - -export abstract class ObjectField { - protected [OnUpdate](diff?: any) { } - private [Parent]?: RefField | ObjectField; - abstract [Copy](): ObjectField; - - abstract [ToScriptString](): string; - abstract [ToString](): string; -} - -export namespace ObjectField { - export function MakeCopy(field: T) { - return field?.[Copy](); - } -} - -Scripting.addGlobal(ObjectField); \ No newline at end of file diff --git a/src/new_fields/PresField.ts b/src/new_fields/PresField.ts deleted file mode 100644 index f236a04fd..000000000 --- a/src/new_fields/PresField.ts +++ /dev/null @@ -1,6 +0,0 @@ -//insert code here -import { ObjectField } from "./ObjectField"; - -export abstract class PresField extends ObjectField { - -} \ No newline at end of file diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts deleted file mode 100644 index 555faaad0..000000000 --- a/src/new_fields/Proxy.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { FieldWaiting } from "./Doc"; -import { primitive, serializable } from "serializr"; -import { observable, action, runInAction } from "mobx"; -import { DocServer } from "../client/DocServer"; -import { RefField } from "./RefField"; -import { ObjectField } from "./ObjectField"; -import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols"; -import { scriptingGlobal } from "../client/util/Scripting"; -import { Plugins } from "./util"; - -@Deserializable("proxy") -export class ProxyField extends ObjectField { - constructor(); - constructor(value: T); - constructor(fieldId: string); - constructor(value?: T | string) { - super(); - if (typeof value === "string") { - this.fieldId = value; - } else if (value) { - this.cache = value; - this.fieldId = value[Id]; - } - } - - [Copy]() { - if (this.cache) return new ProxyField(this.cache); - return new ProxyField(this.fieldId); - } - - [ToScriptString]() { - return "invalid"; - } - [ToString]() { - return "ProxyField"; - } - - @serializable(primitive()) - readonly fieldId: string = ""; - - // This getter/setter and nested object thing is - // because mobx doesn't play well with observable proxies - @observable.ref - private _cache: { readonly field: T | undefined } = { field: undefined }; - private get cache(): T | undefined { - return this._cache.field; - } - private set cache(field: T | undefined) { - this._cache = { field }; - } - - private failed = false; - private promise?: Promise; - - value(): T | undefined | FieldWaiting { - if (this.cache) { - return this.cache; - } - if (this.failed) { - return undefined; - } - if (!this.promise) { - const cached = DocServer.GetCachedRefField(this.fieldId); - if (cached !== undefined) { - runInAction(() => this.cache = cached as any); - return cached as any; - } - this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => { - this.promise = undefined; - this.cache = field; - if (field === undefined) this.failed = true; - return field; - })); - } - return this.promise as any; - } - promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; } - setPromise(promise: any) { - this.promise = promise; - } - @action - setValue(field: any) { - this.promise = undefined; - this.cache = field; - if (field === undefined) this.failed = true; - return field; - } -} - -export namespace ProxyField { - let useProxy = true; - export function DisableProxyFields() { - useProxy = false; - } - - export function EnableProxyFields() { - useProxy = true; - } - - export function WithoutProxy(fn: () => T) { - DisableProxyFields(); - try { - return fn(); - } finally { - EnableProxyFields(); - } - } - - export function initPlugin() { - Plugins.addGetterPlugin((doc, _, value) => { - if (useProxy && value instanceof ProxyField) { - return { value: value.value() }; - } - }); - } -} - -function prefetchValue(proxy: PrefetchProxy) { - return proxy.value() as any; -} - -@scriptingGlobal -@Deserializable("prefetch_proxy", prefetchValue) -export class PrefetchProxy extends ProxyField { -} diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts deleted file mode 100644 index b6ef69750..000000000 --- a/src/new_fields/RefField.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { serializable, primitive, alias } from "serializr"; -import { Utils } from "../Utils"; -import { Id, HandleUpdate, ToScriptString, ToString } from "./FieldSymbols"; -import { afterDocDeserialize } from "../client/util/SerializationHelper"; - -export type FieldId = string; -export abstract class RefField { - @serializable(alias("id", primitive({ afterDeserialize: afterDocDeserialize }))) - private __id: FieldId; - readonly [Id]: FieldId; - - constructor(id?: FieldId) { - this.__id = id || Utils.GenerateGuid(); - this[Id] = this.__id; - } - - protected [HandleUpdate]?(diff: any): void | Promise; - - abstract [ToScriptString](): string; - abstract [ToString](): string; -} diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts deleted file mode 100644 index 5cf0e0cc3..000000000 --- a/src/new_fields/RichTextField.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ObjectField } from "./ObjectField"; -import { serializable } from "serializr"; -import { Deserializable } from "../client/util/SerializationHelper"; -import { Copy, ToScriptString, ToPlainText, ToString } from "./FieldSymbols"; -import { scriptingGlobal } from "../client/util/Scripting"; - -@scriptingGlobal -@Deserializable("RichTextField") -export class RichTextField extends ObjectField { - @serializable(true) - readonly Data: string; - - @serializable(true) - readonly Text: string; - - constructor(data: string, text: string = "") { - super(); - this.Data = data; - this.Text = text; - } - - Empty() { - return !(this.Text || this.Data.toString().includes("dashField")); - } - - [Copy]() { - return new RichTextField(this.Data, this.Text); - } - - [ToScriptString]() { - return `new RichTextField("${this.Data}", "${this.Text}")`; - } - [ToString]() { - return this.Text; - } - - public static DashField(fieldKey: string) { - return 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":"${fieldKey}","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`, ""); - } - -} \ No newline at end of file diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts deleted file mode 100644 index c475d0d73..000000000 --- a/src/new_fields/RichTextUtils.ts +++ /dev/null @@ -1,519 +0,0 @@ -import { AssertionError } from "assert"; -import { docs_v1 } from "googleapis"; -import { Fragment, Mark, Node } from "prosemirror-model"; -import { sinkListItem } from "prosemirror-schema-list"; -import { Utils } from "../Utils"; -import { Docs } from "../client/documents/Documents"; -import { schema } from "../client/views/nodes/formattedText/schema_rts"; -import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; -import { DocServer } from "../client/DocServer"; -import { Networking } from "../client/Network"; -import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox"; -import { Doc, Opt } from "./Doc"; -import { Id } from "./FieldSymbols"; -import { RichTextField } from "./RichTextField"; -import { Cast, StrCast } from "./Types"; -import Color = require('color'); -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; -import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; - -export namespace RichTextUtils { - - const delimiter = "\n"; - const joiner = ""; - - - export const Initialize = (initial?: string) => { - const content: any[] = []; - const state = { - doc: { - type: "doc", - content, - }, - selection: { - type: "text", - anchor: 0, - head: 0 - } - }; - if (initial && initial.length) { - content.push({ - type: "paragraph", - content: { - type: "text", - text: initial - } - }); - state.selection.anchor = state.selection.head = initial.length + 1; - } - return JSON.stringify(state); - }; - - export const Synthesize = (plainText: string, oldState?: RichTextField) => { - return new RichTextField(ToProsemirrorState(plainText, oldState), plainText); - }; - - export const ToPlainText = (state: EditorState) => { - // Because we're working with plain text, just concatenate all paragraphs - const content = state.doc.content; - const paragraphs: Node[] = []; - content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node)); - - // Functions to flatten ProseMirror paragraph objects (and their components) to plain text - // Concatentate paragraphs and string the result together - const textParagraphs: string[] = paragraphs.map(paragraph => { - const text: string[] = []; - paragraph.content.forEach(node => node.text && text.push(node.text)); - return text.join(joiner) + delimiter; - }); - const plainText = textParagraphs.join(joiner); - return plainText.substring(0, plainText.length - 1); - }; - - export const ToProsemirrorState = (plainText: string, oldState?: RichTextField) => { - // Remap the text, creating blocks split on newlines - const elements = plainText.split(delimiter); - - // Google Docs adds in an extra carriage return automatically, so this counteracts it - !elements[elements.length - 1].length && elements.pop(); - - // Preserve the current state, but re-write the content to be the blocks - const parsed = JSON.parse(oldState ? oldState.Data : Initialize()); - parsed.doc.content = elements.map(text => { - const paragraph: any = { type: "paragraph" }; - text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break - return paragraph; - }); - - // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it - parsed.selection = { type: "text", anchor: 1, head: 1 }; - - // Export the ProseMirror-compatible state object we've just built - return JSON.stringify(parsed); - }; - - export namespace GoogleDocs { - - export const Export = async (state: EditorState): Promise => { - const nodes: (Node | null)[] = []; - const text = ToPlainText(state); - state.doc.content.forEach(node => { - if (!node.childCount) { - nodes.push(null); - } else { - node.content.forEach(child => nodes.push(child)); - } - }); - const requests = await marksToStyle(nodes); - return { text, requests }; - }; - - interface ImageTemplate { - width: number; - title: string; - url: string; - agnostic: string; - } - - const parseInlineObjects = async (document: docs_v1.Schema$Document): Promise> => { - const inlineObjectMap = new Map(); - const inlineObjects = document.inlineObjects; - - if (inlineObjects) { - const objects = Object.keys(inlineObjects).map(objectId => inlineObjects[objectId]); - const mediaItems: MediaItem[] = objects.map(object => { - const embeddedObject = object.inlineObjectProperties!.embeddedObject!; - return { baseUrl: embeddedObject.imageProperties!.contentUri! }; - }); - - const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems }); - - if (uploads.length !== mediaItems.length) { - throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" }); - } - - for (let i = 0; i < objects.length; i++) { - const object = objects[i]; - const { accessPaths } = uploads[i]; - const { agnostic, _m } = accessPaths; - const embeddedObject = object.inlineObjectProperties!.embeddedObject!; - const size = embeddedObject.size!; - const width = size.width!.magnitude!; - - inlineObjectMap.set(object.objectId!, { - title: embeddedObject.title || `Imported Image from ${document.title}`, - width, - url: Utils.prepend(_m.client), - agnostic: Utils.prepend(agnostic.client) - }); - } - } - return inlineObjectMap; - }; - - type BulletPosition = { value: number, sinks: number }; - - interface MediaItem { - baseUrl: string; - } - - export const Import = async (documentId: GoogleApiClientUtils.Docs.DocumentId, textNote: Doc): Promise> => { - const document = await GoogleApiClientUtils.Docs.retrieve({ documentId }); - if (!document) { - return undefined; - } - const inlineObjectMap = await parseInlineObjects(document); - const title = document.title!; - const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); - let state = FormattedTextBox.blankState(); - const structured = parseLists(paragraphs); - - let position = 3; - const lists: ListGroup[] = []; - const indentMap = new Map(); - let globalOffset = 0; - const nodes: Node[] = []; - for (const element of structured) { - if (Array.isArray(element)) { - lists.push(element); - const positions: BulletPosition[] = []; - const items = element.map(paragraph => { - const item = listItem(state.schema, paragraph.contents); - const sinks = paragraph.bullet!; - positions.push({ - value: position + globalOffset, - sinks - }); - position += item.nodeSize; - globalOffset += 2 * sinks; - return item; - }); - indentMap.set(element, positions); - nodes.push(list(state.schema, items)); - } else { - if (element.contents.some(child => "inlineObjectId" in child)) { - const group = element.contents; - group.forEach((child, i) => { - let node: Opt>; - if ("inlineObjectId" in child) { - node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); - } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { - node = paragraphNode(state.schema, [child]); - } - if (node) { - position += node.nodeSize; - nodes.push(node); - } - }); - } else { - const paragraph = paragraphNode(state.schema, element.contents); - nodes.push(paragraph); - position += paragraph.nodeSize; - } - } - } - state = state.apply(state.tr.replaceWith(0, 2, nodes)); - - const sink = sinkListItem(state.schema.nodes.list_item); - const dispatcher = (tr: Transaction) => state = state.apply(tr); - for (const list of lists) { - for (const pos of indentMap.get(list)!) { - const resolved = state.doc.resolve(pos.value); - state = state.apply(state.tr.setSelection(new TextSelection(resolved))); - for (let i = 0; i < pos.sinks; i++) { - sink(state, dispatcher); - } - } - } - - return { title, text, state }; - }; - - type Paragraph = GoogleApiClientUtils.Docs.Utils.DeconstructedParagraph; - type ListGroup = Paragraph[]; - type PreparedParagraphs = (ListGroup | Paragraph)[]; - - const parseLists = (paragraphs: ListGroup) => { - const groups: PreparedParagraphs = []; - let group: ListGroup = []; - for (const paragraph of paragraphs) { - if (paragraph.bullet !== undefined) { - group.push(paragraph); - } else { - if (group.length) { - groups.push(group); - group = []; - } - groups.push(paragraph); - } - } - group.length && groups.push(group); - return groups; - }; - - const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { - return schema.node("list_item", null, paragraphNode(schema, runs)); - }; - - const list = (schema: any, items: Node[]): Node => { - return schema.node("bullet_list", null, items); - }; - - const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { - const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined); - const fragment = children.length ? Fragment.from(children) : undefined; - return schema.node("paragraph", null, fragment); - }; - - const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => { - const { url: src, width, agnostic } = image; - let docid: string; - const guid = Utils.GenerateDeterministicGuid(agnostic); - const backingDocId = StrCast(textNote[guid]); - if (!backingDocId) { - const backingDoc = Docs.Create.ImageDocument(agnostic, { _width: 300, _height: 300 }); - Doc.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument); - docid = backingDoc[Id]; - textNote[guid] = docid; - } else { - docid = backingDocId; - } - return schema.node("image", { src, agnostic, width, docid, float: null, location: "onRight" }); - }; - - const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { - const text = run.content!.removeTrailingNewlines(); - return text.length ? schema.text(text, styleToMarks(schema, run.textStyle)) : undefined; - }; - - const StyleToMark = new Map([ - ["bold", "strong"], - ["italic", "em"], - ["foregroundColor", "pFontColor"], - ["fontSize", "pFontSize"] - ]); - - const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { - if (!textStyle) { - return undefined; - } - const marks: Mark[] = []; - Object.keys(textStyle).forEach(key => { - let value: any; - const targeted = key as keyof docs_v1.Schema$TextStyle; - if (value = textStyle[targeted]) { - const attributes: any = {}; - let converted = StyleToMark.get(targeted) || targeted; - - value.url && (attributes.href = value.url); - if (value.color) { - const object = value.color.rgbColor; - attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex(); - } - if (value.magnitude) { - attributes.fontSize = value.magnitude; - } - - if (converted === "weightedFontFamily") { - converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman"; - } - - const mapped = schema.marks[converted]; - if (!mapped) { - alert(`No mapping found for ${converted}!`); - return; - } - - const mark = schema.mark(mapped, attributes); - mark && marks.push(mark); - } - }); - return marks; - }; - - const MarkToStyle = new Map([ - ["strong", "bold"], - ["em", "italic"], - ["pFontColor", "foregroundColor"], - ["pFontSize", "fontSize"], - ["timesNewRoman", "weightedFontFamily"], - ["georgia", "weightedFontFamily"], - ["comicSans", "weightedFontFamily"], - ["tahoma", "weightedFontFamily"], - ["impact", "weightedFontFamily"] - ]); - - const ExportFontFamilyMapping = new Map([ - ["timesNewRoman", "Times New Roman"], - ["arial", "Arial"], - ["georgia", "Georgia"], - ["comicSans", "Comic Sans MS"], - ["tahoma", "Tahoma"], - ["impact", "Impact"] - ]); - - const ImportFontFamilyMapping = new Map([ - ["Times New Roman", "timesNewRoman"], - ["Arial", "arial"], - ["Georgia", "georgia"], - ["Comic Sans MS", "comicSans"], - ["Tahoma", "tahoma"], - ["Impact", "impact"] - ]); - - const ignored = ["user_mark"]; - - const marksToStyle = async (nodes: (Node | null)[]): Promise => { - const requests: docs_v1.Schema$Request[] = []; - let position = 1; - for (const node of nodes) { - if (node === null) { - position += 2; - continue; - } - const { marks, attrs, nodeSize } = node; - const textStyle: docs_v1.Schema$TextStyle = {}; - const information: LinkInformation = { - startIndex: position, - endIndex: position + nodeSize, - textStyle - }; - let mark: Mark; - const markMap = BuildMarkMap(marks); - for (const markName of Object.keys(schema.marks)) { - if (ignored.includes(markName) || !(mark = markMap[markName])) { - continue; - } - let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; - let value: any = true; - if (!converted) { - continue; - } - const { attrs } = mark; - switch (converted) { - case "link": - let url = attrs.href; - const delimiter = "/doc/"; - const alreadyShared = "?sharing=true"; - if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { - const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); - if (linkDoc instanceof Doc) { - let exported = (await Cast(linkDoc.anchor2, Doc))!; - if (!exported.customLayout) { - exported = Doc.MakeAlias(exported); - Doc.makeCustomViewClicked(exported, Docs.Create.FreeformDocument); - linkDoc.anchor2 = exported; - } - url = Utils.shareUrl(exported[Id]); - } - } - value = { url }; - textStyle.foregroundColor = fromRgb.blue; - textStyle.bold = true; - break; - case "fontSize": - value = { magnitude: attrs.fontSize, unit: "PT" }; - break; - case "foregroundColor": - value = fromHex(attrs.color); - break; - case "weightedFontFamily": - value = { fontFamily: ExportFontFamilyMapping.get(markName) }; - } - let matches: RegExpExecArray | null; - if ((matches = /p(\d+)/g.exec(markName)) !== null) { - converted = "fontSize"; - value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" }; - } - textStyle[converted] = value; - } - if (Object.keys(textStyle).length) { - requests.push(EncodeStyleUpdate(information)); - } - if (node.type.name === "image") { - const width = attrs.width; - requests.push(await EncodeImage({ - startIndex: position + nodeSize - 1, - uri: attrs.agnostic, - width: Number(typeof width === "string" ? width.replace("px", "") : width) - })); - } - position += nodeSize; - } - return requests; - }; - - const BuildMarkMap = (marks: Mark[]) => { - const markMap: { [type: string]: Mark } = {}; - marks.forEach(mark => markMap[mark.type.name] = mark); - return markMap; - }; - - interface LinkInformation { - startIndex: number; - endIndex: number; - textStyle: docs_v1.Schema$TextStyle; - } - - interface ImageInformation { - startIndex: number; - width: number; - uri: string; - } - - namespace fromRgb { - - export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { - return { - color: { - rgbColor: { - red: red / 255, - green: green / 255, - blue: blue / 255 - } - } - }; - }; - - export const red = convert(255, 0, 0); - export const green = convert(0, 255, 0); - export const blue = convert(0, 0, 255); - - } - - const fromHex = (color: string): docs_v1.Schema$OptionalColor => { - const c = Color(color); - return fromRgb.convert(c.red(), c.green(), c.blue()); - }; - - const EncodeStyleUpdate = (information: LinkInformation): docs_v1.Schema$Request => { - const { startIndex, endIndex, textStyle } = information; - return { - updateTextStyle: { - fields: "*", - range: { startIndex, endIndex }, - textStyle - } as docs_v1.Schema$UpdateTextStyleRequest - }; - }; - - const EncodeImage = async ({ uri, width, startIndex }: ImageInformation) => { - if (!uri) { - return {}; - } - const source = [Docs.Create.ImageDocument(uri)]; - const baseUrls = await GooglePhotos.Transactions.UploadThenFetch(source); - if (baseUrls) { - return { - insertInlineImage: { - uri: baseUrls[0], - objectSize: { width: { magnitude: width, unit: "PT" } }, - location: { index: startIndex } - } - }; - } - return {}; - }; - } - -} \ No newline at end of file diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts deleted file mode 100644 index 72bce283d..000000000 --- a/src/new_fields/Schema.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types"; -import { Doc, Field } from "./Doc"; -import { ObjectField } from "./ObjectField"; -import { RefField } from "./RefField"; -import { SelfProxy } from "./FieldSymbols"; - -type AllToInterface = { - 1: ToInterface> & AllToInterface>, - 0: ToInterface> -}[HasTail extends true ? 1 : 0]; - -export const emptySchema = createSchema({}); -export const Document = makeInterface(emptySchema); -export type Document = makeInterface<[typeof emptySchema]>; - -export interface InterfaceFunc { - (docs: Doc[]): makeInterface[]; - (): makeInterface; - (doc: Doc): makeInterface; -} - -export type makeInterface = AllToInterface & Doc & { proto: Doc | undefined }; -// export function makeInterface(schemas: T): (doc: U) => All; -// export function makeInterface(schema: T): (doc: U) => makeInterface; -export function makeInterface(...schemas: T): InterfaceFunc { - const schema: Interface = {}; - for (const s of schemas) { - for (const key in s) { - schema[key] = s[key]; - } - } - const proto = new Proxy({}, { - get(target: any, prop, receiver) { - const field = receiver.doc[prop]; - if (prop in schema) { - const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should? - if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec - return Cast(field, desc.type, desc.defaultVal); - } else if (typeof desc === "function" && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) { - const doc = Cast(field, Doc); - if (doc === undefined) { - return undefined; - } else if (doc instanceof Doc) { - return desc(doc); - } else { - return doc.then(doc => doc && desc(doc)); - } - } else { - return Cast(field, desc); - } - } - return field; - }, - set(target: any, prop, value, receiver) { - receiver.doc[prop] = value; - return true; - } - }); - const fn = (doc: Doc) => { - doc = doc[SelfProxy]; - // if (!(doc instanceof Doc)) { - // throw new Error("Currently wrapping a schema in another schema isn't supported"); - // } - const obj = Object.create(proto, { doc: { value: doc, writable: false } }); - return obj; - }; - return function (doc?: Doc | Doc[]) { - doc = doc || new Doc; - if (doc instanceof Doc) { - return fn(doc); - } else { - return doc.map(fn); - } - }; -} - -export type makeStrictInterface = Partial>; -export function makeStrictInterface(schema: T): (doc: Doc) => makeStrictInterface { - const proto = {}; - for (const key in schema) { - const type = schema[key]; - Object.defineProperty(proto, key, { - get() { - return Cast(this.__doc[key], type as any); - }, - set(value) { - value = Cast(value, type as any); - if (value !== undefined) { - this.__doc[key] = value; - return; - } - throw new TypeError("Expected type " + type); - } - }); - } - return function (doc: any) { - if (!(doc instanceof Doc)) { - throw new Error("Currently wrapping a schema in another schema isn't supported"); - } - const obj = Object.create(proto); - obj.__doc = doc; - return obj; - }; -} - -export function createSchema(schema: T): T & { proto: ToConstructor } { - (schema as any).proto = Doc; - return schema as any; -} - -export function listSpec>(type: U): ListSpec> { - return { List: type as any };//TODO Types -} - -export function defaultSpec>(type: T, defaultVal: ToType): DefaultFieldConstructor> { - return { - type: type as any, - defaultVal - }; -} \ No newline at end of file diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts deleted file mode 100644 index 07c90f5a2..000000000 --- a/src/new_fields/SchemaHeaderField.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, primitive } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString, OnUpdate } from "./FieldSymbols"; -import { scriptingGlobal } from "../client/util/Scripting"; -import { ColumnType } from "../client/views/collections/CollectionSchemaView"; - -export const PastelSchemaPalette = new Map([ - // ["pink1", "#FFB4E8"], - ["pink2", "#ff9cee"], - ["pink3", "#ffccf9"], - ["pink4", "#fcc2ff"], - ["pink5", "#f6a6ff"], - ["purple1", "#b28dff"], - ["purple2", "#c5a3ff"], - ["purple3", "#d5aaff"], - ["purple4", "#ecd4ff"], - // ["purple5", "#fb34ff"], - ["purple6", "#dcd3ff"], - ["purple7", "#a79aff"], - ["purple8", "#b5b9ff"], - ["purple9", "#97a2ff"], - ["bluegreen1", "#afcbff"], - ["bluegreen2", "#aff8db"], - ["bluegreen3", "#c4faf8"], - ["bluegreen4", "#85e3ff"], - ["bluegreen5", "#ace7ff"], - // ["bluegreen6", "#6eb5ff"], - ["bluegreen7", "#bffcc6"], - ["bluegreen8", "#dbffd6"], - ["yellow1", "#f3ffe3"], - ["yellow2", "#e7ffac"], - ["yellow3", "#ffffd1"], - ["yellow4", "#fff5ba"], - // ["red1", "#ffc9de"], - ["red2", "#ffabab"], - ["red3", "#ffbebc"], - ["red4", "#ffcbc1"], - ["orange1", "#ffd5b3"], -]); - -export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; - -export const DarkPastelSchemaPalette = new Map([ - ["pink2", "#c932b0"], - ["purple4", "#913ad6"], - ["bluegreen1", "#3978ed"], - ["bluegreen7", "#2adb3e"], - ["bluegreen5", "#21b0eb"], - ["yellow4", "#edcc0c"], - ["red2", "#eb3636"], - ["orange1", "#f2740f"], -]); - -@scriptingGlobal -@Deserializable("schemaheader") -export class SchemaHeaderField extends ObjectField { - @serializable(primitive()) - heading: string; - @serializable(primitive()) - color: string; - @serializable(primitive()) - type: number; - @serializable(primitive()) - width: number; - @serializable(primitive()) - collapsed: boolean | undefined; - @serializable(primitive()) - desc: boolean | undefined; // boolean determines sort order, undefined when no sort - - constructor(heading: string = "", color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) { - super(); - - this.heading = heading; - this.color = color; - this.type = type ? type : 0; - this.width = width ? width : -1; - this.desc = desc; - this.collapsed = collapsed; - } - - setHeading(heading: string) { - this.heading = heading; - this[OnUpdate](); - } - - setColor(color: string) { - this.color = color; - this[OnUpdate](); - } - - setType(type: ColumnType) { - this.type = type; - this[OnUpdate](); - } - - setWidth(width: number) { - this.width = width; - this[OnUpdate](); - } - - setDesc(desc: boolean | undefined) { - this.desc = desc; - this[OnUpdate](); - } - - setCollapsed(collapsed: boolean | undefined) { - this.collapsed = collapsed; - this[OnUpdate](); - } - - [Copy]() { - return new SchemaHeaderField(this.heading, this.color, this.type); - } - - [ToScriptString]() { - return `invalid`; - } - [ToString]() { - return `SchemaHeaderField`; - } -} \ No newline at end of file diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts deleted file mode 100644 index 4b790f483..000000000 --- a/src/new_fields/ScriptField.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { ObjectField } from "./ObjectField"; -import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions, CompileError, CompileResult } from "../client/util/Scripting"; -import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols"; -import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; -import { Deserializable, autoObject } from "../client/util/SerializationHelper"; -import { Doc, Field } from "../new_fields/Doc"; -import { Plugins, setter } from "./util"; -import { computedFn } from "mobx-utils"; -import { ProxyField } from "./Proxy"; -import { Cast } from "./Types"; - -function optional(propSchema: PropSchema) { - return custom(value => { - if (value !== undefined) { - return propSchema.serializer(value); - } - return SKIP; - }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => { - if (jsonValue !== undefined) { - return propSchema.deserializer(jsonValue, callback, context, oldValue); - } - return SKIP; - }); -} - -const optionsSchema = createSimpleSchema({ - requiredType: true, - addReturn: true, - typecheck: true, - editable: true, - readonly: true, - params: optional(map(primitive())) -}); - -const scriptSchema = createSimpleSchema({ - options: object(optionsSchema), - originalScript: true -}); - -async function deserializeScript(script: ScriptField) { - const captures: ProxyField = (script as any).captures; - if (captures) { - const doc = (await captures.value())!; - const captured: any = {}; - const keys = Object.keys(doc); - const vals = await Promise.all(keys.map(key => doc[key]) as any); - keys.forEach((key, i) => captured[key] = vals[i]); - (script.script.options as any).capturedVariables = captured; - } - const comp = CompileScript(script.script.originalScript, script.script.options); - if (!comp.compiled) { - throw new Error("Couldn't compile loaded script"); - } - (script as any).script = comp; -} - -@scriptingGlobal -@Deserializable("script", deserializeScript) -export class ScriptField extends ObjectField { - @serializable(object(scriptSchema)) - readonly script: CompiledScript; - @serializable(object(scriptSchema)) - readonly setterscript: CompiledScript | undefined; - - @serializable(autoObject()) - private captures?: ProxyField; - - constructor(script: CompiledScript, setterscript?: CompileResult) { - super(); - - if (script?.options.capturedVariables) { - const doc = Doc.assign(new Doc, script.options.capturedVariables); - this.captures = new ProxyField(doc); - } - this.setterscript = setterscript?.compiled ? setterscript : undefined; - this.script = script; - } - - // init(callback: (res: Field) => any) { - // const options = this.options!; - // const keys = Object.keys(options.options.capturedIds); - // Server.GetFields(keys).then(fields => { - // let captured: { [name: string]: Field } = {}; - // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); - // const opts: ScriptOptions = { - // addReturn: options.options.addReturn, - // params: options.options.params, - // requiredType: options.options.requiredType, - // capturedVariables: captured - // }; - // const script = CompileScript(options.script, opts); - // if (!script.compiled) { - // throw new Error("Can't compile script"); - // } - // this._script = script; - // callback(this); - // }); - // } - - [Copy](): ObjectField { - return new ScriptField(this.script); - } - toString() { - return `${this.script.originalScript}`; - } - - [ToScriptString]() { - return "script field"; - } - [ToString]() { - return this.script.originalScript; - } - public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) { - const compiled = CompileScript(script, { - params: { this: Doc.name, self: Doc.name, _last_: "any", ...params }, - typecheck: false, - editable: true, - addReturn: addReturn, - capturedVariables - }); - return compiled; - } - public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) { - const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); - return compiled.compiled ? new ScriptField(compiled) : undefined; - } - - public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) { - const compiled = ScriptField.CompileScript(script, params, false, capturedVariables); - return compiled.compiled ? new ScriptField(compiled) : undefined; - } -} - -@scriptingGlobal -@Deserializable("computed", deserializeScript) -export class ComputedField extends ScriptField { - _lastComputedResult: any; - //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc - value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result; - - - constructor(script: CompiledScript, setterscript?: CompiledScript) { - super(script, - !setterscript && script?.originalScript.includes("self.timecode") ? - ScriptField.CompileScript("self['x' + self.timecode] = value", { value: "any" }, true) : setterscript); - } - - public static MakeScript(script: string, params: object = {}) { - const compiled = ScriptField.CompileScript(script, params, false); - return compiled.compiled ? new ComputedField(compiled) : undefined; - } - public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }, setterScript?: string) { - const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); - const setCompiled = setterScript ? ScriptField.CompileScript(setterScript, params, true, capturedVariables) : undefined; - return compiled.compiled ? new ComputedField(compiled, setCompiled?.compiled ? setCompiled : undefined) : undefined; - } - public static MakeInterpolated(fieldKey: string, interpolatorKey: string) { - const getField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}]`, {}, true, {}); - const setField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}] = value`, { value: "any" }, true, {}); - return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; - } -} - -export namespace ComputedField { - let useComputed = true; - export function DisableComputedFields() { - useComputed = false; - } - - export function EnableComputedFields() { - useComputed = true; - } - - export const undefined = "__undefined"; - - export function WithoutComputed(fn: () => T) { - DisableComputedFields(); - try { - return fn(); - } finally { - EnableComputedFields(); - } - } - - export function initPlugin() { - Plugins.addGetterPlugin((doc, _, value) => { - if (useComputed && value instanceof ComputedField) { - return { value: value._valueOutsideReaction(doc), shouldReturn: true }; - } - }); - } -} \ No newline at end of file diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts deleted file mode 100644 index 3d784448d..000000000 --- a/src/new_fields/Types.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Field, Opt, FieldResult, Doc } from "./Doc"; -import { List } from "./List"; -import { RefField } from "./RefField"; -import { DateField } from "./DateField"; -import { ScriptField } from "./ScriptField"; - -export type ToType = - T extends "string" ? string : - T extends "number" ? number : - T extends "boolean" ? boolean : - T extends ListSpec ? List : - // T extends { new(...args: any[]): infer R } ? (R | Promise) : never; - T extends DefaultFieldConstructor ? never : - T extends { new(...args: any[]): List } ? never : - T extends { new(...args: any[]): infer R } ? R : - T extends (doc?: Doc) => infer R ? R : never; - -export type ToConstructor = - T extends string ? "string" : - T extends number ? "number" : - T extends boolean ? "boolean" : - T extends List ? ListSpec : - new (...args: any[]) => T; - -export type ToInterface = { - [P in Exclude]: T[P] extends DefaultFieldConstructor ? Exclude, undefined> : FieldResult>; -}; - -// type ListSpec = { List: ToContructor> | ListSpec> }; -export type ListSpec = { List: ToConstructor }; - -export type DefaultFieldConstructor = { - type: ToConstructor, - defaultVal: T -}; - -// type ListType = { 0: List>>, 1: ToType> }[HasTail extends true ? 0 : 1]; - -export type Head = T extends [any, ...any[]] ? T[0] : never; -export type Tail = - ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : []; -export type HasTail = T extends ([] | [any]) ? false : true; - -export type InterfaceValue = ToConstructor | ListSpec | DefaultFieldConstructor | ((doc?: Doc) => any); -//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial -export interface Interface { - [key: string]: InterfaceValue; - // [key: string]: ToConstructor | ListSpec; -} -export type WithoutRefField = T extends RefField ? never : T; - -export type CastCtor = ToConstructor | ListSpec; - -export function Cast(field: FieldResult, ctor: T): FieldResult>; -export function Cast(field: FieldResult, ctor: T, defaultVal: WithoutList>> | null): WithoutList>; -export function Cast(field: FieldResult, ctor: T, defaultVal?: ToType | null): FieldResult> | undefined { - if (field instanceof Promise) { - return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal; - } - if (field !== undefined && !(field instanceof Promise)) { - if (typeof ctor === "string") { - if (typeof field === ctor) { - return field as ToType; - } - } else if (typeof ctor === "object") { - if (field instanceof List) { - return field as any; - } - } else if (field instanceof (ctor as any)) { - return field as ToType; - } - } - return defaultVal === null ? undefined : defaultVal; -} - -export function NumCast(field: FieldResult, defaultVal: number | null = 0) { - return Cast(field, "number", defaultVal); -} - -export function StrCast(field: FieldResult, defaultVal: string | null = "") { - return Cast(field, "string", defaultVal); -} - -export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) { - return Cast(field, "boolean", defaultVal); -} -export function DateCast(field: FieldResult) { - return Cast(field, DateField, null); -} - -export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { - return Cast(field, ScriptField, defaultVal); -} - -type WithoutList = T extends List ? (R extends RefField ? (R | Promise)[] : R[]) : T; - -export function FieldValue>(field: FieldResult, defaultValue: U): WithoutList; -export function FieldValue(field: FieldResult): Opt; -export function FieldValue(field: FieldResult, defaultValue?: T): Opt { - return (field instanceof Promise || field === undefined) ? defaultValue : field; -} - -export interface PromiseLike { - then(callback: (field: Opt) => void): void; -} -export function PromiseValue(field: FieldResult): PromiseLike> { - return field instanceof Promise ? field : { then(cb: ((field: Opt) => void)) { return cb(field); } }; -} \ No newline at end of file diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts deleted file mode 100644 index fb71160ca..000000000 --- a/src/new_fields/URLField.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, custom } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { ToScriptString, ToString, Copy } from "./FieldSymbols"; -import { Scripting, scriptingGlobal } from "../client/util/Scripting"; - -function url() { - return custom( - function (value: URL) { - return value.href; - }, - function (jsonValue: string) { - return new URL(jsonValue); - } - ); -} - -export abstract class URLField extends ObjectField { - @serializable(url()) - readonly url: URL; - - constructor(url: string); - constructor(url: URL); - constructor(url: URL | string) { - super(); - if (typeof url === "string") { - url = new URL(url); - } - this.url = url; - } - - [ToScriptString]() { - return `new ${this.constructor.name}("${this.url.href}")`; - } - [ToString]() { - return this.url.href; - } - - [Copy](): this { - return new (this.constructor as any)(this.url); - } -} - -export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg"; - -@scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { } -@scriptingGlobal @Deserializable("image") export class ImageField extends URLField { } -@scriptingGlobal @Deserializable("video") export class VideoField extends URLField { } -@scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } -@scriptingGlobal @Deserializable("web") export class WebField extends URLField { } -@scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } -@scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } - diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts deleted file mode 100644 index cacba43b6..000000000 --- a/src/new_fields/documentSchemas.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { makeInterface, createSchema, listSpec } from "./Schema"; -import { ScriptField } from "./ScriptField"; -import { Doc } from "./Doc"; -import { DateField } from "./DateField"; - -export const documentSchema = createSchema({ - // content properties - type: "string", // enumerated type of document -- should be template-specific (ie, start with an '_') - title: "string", // document title (can be on either data document or layout) - isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field - creationDate: DateField, // when the document was created - links: listSpec(Doc), // computed (readonly) list of links associated with this document - - // "Location" properties in a very general sense - currentTimecode: "number", // current play back time of a temporal document (video / audio) - displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) - inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently - x: "number", // x coordinate when in a freeform view - y: "number", // y coordinate when in a freeform view - z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview - zIndex: "number", // zIndex of a document in a freeform view - scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) - scrollTop: "number", // scroll position of a scrollable document (pdf, text, web) - - // appearance properties on the layout - _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents - _nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set - _nativeHeight: "number", // " - _width: "number", // width of document in its container's coordinate system - _height: "number", // " - _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set - _xMargin: "number", // margin added on left/right of most documents to add separation from their container - _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container - _overflow: "string", // sets overflow behvavior for CollectionFreeForm views - _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document - _showTitle: "string", // the fieldkey whose contents should be displayed at the top of the document - _showTitleHover: "string", // the showTitle should be shown only on hover - _showAudio: "boolean", // whether to show the audio record icon on documents - _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents - _LODdisable: "boolean", // whether to disbale LOD switching for CollectionFreeFormViews - _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis - _replacedChrome: "string", // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's. - _chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed' - _fontSize: "number", - _fontFamily: "string", - _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar - - // appearance properties on the data document - backgroundColor: "string", // background color of document - borderRounding: "string", // border radius rounding of document - boxShadow: "string", // the amount of shadow around the perimeter of a document - color: "string", // foreground color of document - fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view - fontSize: "string", - layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below - layoutKey: "string", // holds the field key for the field that actually holds the current lyoat - letterSpacing: "string", - opacity: "number", // opacity of document - strokeWidth: "number", - textTransform: "string", - treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden - treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree - treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) - - // interaction and linking properties - ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) - onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) - onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) - onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) - onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) - isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations - isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked - isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee) - lockedPosition: "boolean", // whether the document can be moved (dragged) - lockedTransform: "boolean", // whether the document can be panned/zoomed - - // drag drop properties - 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' - childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy") - removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped -}); - - -export const collectionSchema = createSchema({ - childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox - childLayoutTemplate: Doc, // layout template to use to render children of a collecion - childLayoutString: "string", //layout string to use to render children of a collection - childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template) - dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc. - onChildClick: ScriptField, // script to run for each child when its clicked - onChildDoubleClick: ScriptField, // script to run for each child when its clicked - onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view -}); - -export type Document = makeInterface<[typeof documentSchema]>; -export const Document = makeInterface(documentSchema); diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts deleted file mode 100644 index a287b0210..000000000 --- a/src/new_fields/util.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { UndoManager } from "../client/util/UndoManager"; -import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym } from "./Doc"; -import { SerializationHelper } from "../client/util/SerializationHelper"; -import { ProxyField, PrefetchProxy } from "./Proxy"; -import { RefField } from "./RefField"; -import { ObjectField } from "./ObjectField"; -import { action, trace } from "mobx"; -import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; -import { DocServer } from "../client/DocServer"; -import { ComputedField } from "./ScriptField"; -import { ScriptCast } from "./Types"; - -function _readOnlySetter(): never { - throw new Error("Documents can't be modified in read-only mode"); -} - -const tracing = false; -export function TraceMobx() { - tracing && trace(); -} - -export interface GetterResult { - value: FieldResult; - shouldReturn?: boolean; -} -export type GetterPlugin = (receiver: any, prop: string | number, currentValue: any) => GetterResult | undefined; -const getterPlugins: GetterPlugin[] = []; - -export namespace Plugins { - export function addGetterPlugin(plugin: GetterPlugin) { - getterPlugins.push(plugin); - } -} - -const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { - //console.log("-set " + target[SelfProxy].title + "(" + target[SelfProxy][prop] + ")." + prop.toString() + " = " + value); - if (SerializationHelper.IsSerializing()) { - target[prop] = value; - return true; - } - - if (typeof prop === "symbol") { - target[prop] = value; - return true; - } - if (value !== undefined) { - value = value[SelfProxy] || value; - } - const curValue = target.__fields[prop]; - if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) { - // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically - // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way - return true; - } - if (value instanceof RefField) { - value = new ProxyField(value); - } - if (value instanceof ObjectField) { - if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) { - throw new Error("Can't put the same object in multiple documents at the same time"); - } - value[Parent] = receiver; - value[OnUpdate] = updateFunction(target, prop, value, receiver); - } - if (curValue instanceof ObjectField) { - delete curValue[Parent]; - delete curValue[OnUpdate]; - } - const writeMode = DocServer.getFieldWriteMode(prop as string); - const fromServer = target[UpdatingFromServer]; - const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); - const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly); - const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default); - if (writeToDoc) { - if (value === undefined) { - delete target.__fields[prop]; - } else { - target.__fields[prop] = value; - } - if (typeof value === "object" && !(value instanceof ObjectField)) debugger; - if (writeToServer) { - if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } }); - else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); - } else { - DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); - } - UndoManager.AddEvent({ - redo: () => receiver[prop] = value, - undo: () => receiver[prop] = curValue - }); - } - return true; -}); - -let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; - -export function makeReadOnly() { - _setter = _readOnlySetter; -} - -export function makeEditable() { - _setter = _setterImpl; -} - -const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", - "LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; -export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { - let prop = in_prop; - if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { - if (!prop.startsWith("_")) { - console.log(prop + " is deprecated - switch to _" + prop); - prop = "_" + prop; - } - if (target.__LAYOUT__) { - target.__LAYOUT__[prop] = value; - return true; - } - } - if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript) { - return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; - } - return _setter(target, prop, value, receiver); -} - -export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { - let prop = in_prop; - if (prop === LayoutSym) { - return target.__LAYOUT__; - } - if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { - if (!prop.startsWith("_")) { - console.log(prop + " is deprecated - switch to _" + prop); - prop = "_" + prop; - } - if (target.__LAYOUT__) return target.__LAYOUT__[prop]; - } - if (prop === "then") {//If we're being awaited - return undefined; - } - if (typeof prop === "symbol") { - return target.__fields[prop] || target[prop]; - } - if (SerializationHelper.IsSerializing()) { - return target[prop]; - } - return getFieldImpl(target, prop, receiver); -} - -function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { - receiver = receiver || target[SelfProxy]; - let field = target.__fields[prop]; - for (const plugin of getterPlugins) { - const res = plugin(receiver, prop, field); - if (res === undefined) continue; - if (res.shouldReturn) { - return res.value; - } else { - field = res.value; - } - } - if (field === undefined && !ignoreProto && prop !== "proto") { - const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters - if (proto instanceof Doc) { - return getFieldImpl(proto[Self], prop, receiver, ignoreProto); - } - return undefined; - } - return field; - -} -export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any { - return getFieldImpl(target, prop, undefined, ignoreProto); -} - -export function deleteProperty(target: any, prop: string | number | symbol) { - if (typeof prop === "symbol") { - delete target[prop]; - return true; - } - target[SelfProxy][prop] = undefined; - return true; -} - -export function updateFunction(target: any, prop: any, value: any, receiver: any) { - let current = ObjectField.MakeCopy(value); - return (diff?: any) => { - if (true || !diff) { - diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; - const oldValue = current; - const newValue = ObjectField.MakeCopy(value); - current = newValue; - UndoManager.AddEvent({ - redo() { receiver[prop] = newValue; }, - undo() { receiver[prop] = oldValue; } - }); - } - target[Update](diff); - }; -} \ No newline at end of file diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index b8a82ab4d..3b6170f68 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -1,9 +1,9 @@ import { NDollarRecognizer } from "./ndollar"; import { Type } from "typescript"; -import { InkField, PointData } from "../new_fields/InkField"; +import { InkField, PointData } from "../fields/InkField"; import { Docs } from "../client/documents/Documents"; -import { Doc, WidthSym, HeightSym } from "../new_fields/Doc"; -import { NumCast } from "../new_fields/Types"; +import { Doc, WidthSym, HeightSym } from "../fields/Doc"; +import { NumCast } from "../fields/Types"; import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView"; import { Rect } from "react-measure"; import { Scripting } from "../client/util/Scripting"; diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 11841a603..be17b698e 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -3,7 +3,7 @@ import { Method, _error, _success, _invalid } from "../RouteManager"; import * as path from "path"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; import { BatchedArray, TimeUnit } from "array-batcher"; -import { Opt } from "../../new_fields/Doc"; +import { Opt } from "../../fields/Doc"; import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; import { Database } from "../database"; import { red } from "colors"; diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index 68b3107ae..0d1d8f218 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -3,7 +3,7 @@ import { Method } from "../RouteManager"; import { Database } from "../database"; import { msToTime } from "../ActionUtilities"; import * as bcrypt from "bcrypt-nodejs"; -import { Opt } from "../../new_fields/Doc"; +import { Opt } from "../../fields/Doc"; export const timeMap: { [id: string]: number } = {}; interface ActivityUnit { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 8567631cd..b74904ada 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import * as sharp from 'sharp'; import request = require('request-promise'); import { ExifImage } from 'exif'; -import { Opt } from '../new_fields/Doc'; +import { Opt } from '../fields/Doc'; import { AcceptibleMedia, Upload } from './SharedMediaTypes'; import { filesDirectory, publicDirectory } from '.'; import { File } from 'formidable'; diff --git a/src/server/Message.ts b/src/server/Message.ts index 01aae5de7..80f372733 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,6 +1,6 @@ import { Utils } from "../Utils"; import { Point } from "../pen-gestures/ndollar"; -import { Doc } from "../new_fields/Doc"; +import { Doc } from "../fields/Doc"; import { Image } from "canvas"; import { AnalysisResult, ImportResults } from "../scraping/buxton/final/BuxtonImporter"; diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index aacdb4053..423ce9b46 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -1,6 +1,6 @@ -// //import { Doc } from "../new_fields/Doc"; -// //import { StrCast } from "../new_fields/Types"; -// //import { List } from "../new_fields/List"; +// //import { Doc } from "../fields/Doc"; +// //import { StrCast } from "../fields/Types"; +// //import { List } from "../fields/List"; // //import { CognitiveServices } from "../client/cognitive_services/CognitiveServices"; // // var w2v = require('word2vec'); diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 2e4811c86..20f96f432 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -1,6 +1,6 @@ import { google } from "googleapis"; import { OAuth2Client, Credentials, OAuth2ClientOptions } from "google-auth-library"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt } from "../../../fields/Doc"; import { GaxiosResponse } from "gaxios"; import request = require('request-promise'); import * as qs from "query-string"; diff --git a/src/server/database.ts b/src/server/database.ts index ed9a246e3..a5f23c4b1 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,6 +1,6 @@ import * as mongodb from 'mongodb'; import { Transferable } from './Message'; -import { Opt } from '../new_fields/Doc'; +import { Opt } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { Credentials } from 'google-auth-library'; import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; diff --git a/test/test.ts b/test/test.ts index 245733e9b..5fc156b46 100644 --- a/test/test.ts +++ b/test/test.ts @@ -8,10 +8,10 @@ const dom = new JSDOM("", { import { autorun, reaction } from "mobx"; -import { Doc } from '../src/new_fields/Doc'; -import { Cast } from '../src/new_fields/Types'; -import { createSchema, makeInterface, defaultSpec } from '../src/new_fields/Schema'; -import { ImageField } from '../src/new_fields/URLField'; +import { Doc } from '../src/fields/Doc'; +import { Cast } from '../src/fields/Types'; +import { createSchema, makeInterface, defaultSpec } from '../src/fields/Schema'; +import { ImageField } from '../src/fields/URLField'; describe("Document", () => { it('should hold fields', () => { const key = "Test"; -- cgit v1.2.3-70-g09d2 From 61dcd6a0e23384e68a7d7f2f9e0143741f5cbf56 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 May 2020 17:36:39 -0400 Subject: moving toward a proper slide transition system --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/MainView.tsx | 5 +- .../views/collections/CollectionCarouselView.tsx | 5 +- .../CollectionFreeFormLayoutEngines.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 118 ++++++++++----------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 58 +++++++++- src/fields/ScriptField.ts | 8 +- 7 files changed, 128 insertions(+), 72 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index fc7c16296..6639f1cce 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -483,7 +483,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
    {`${this.selectionTitle}`}
    - + ; bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 978cf7868..f58313f06 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { - faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, + faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, @@ -121,6 +121,8 @@ export class MainView extends React.Component { library.add(faCamera); library.add(faExpand); library.add(faCaretDown); + library.add(faCaretUp); + library.add(faCaretLeft); library.add(faCaretRight); library.add(faCaretSquareDown); library.add(faCaretSquareRight); @@ -168,7 +170,6 @@ export class MainView extends React.Component { library.add(faThumbtack); library.add(faLongArrowAltRight); library.add(faCheck); - library.add(faCaretUp); library.add(faFilter); library.add(faBullseye); library.add(faArrowLeft); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 39bb9bc23..f65a89422 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -9,7 +9,6 @@ import { DragManager } from '../../util/DragManager'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import "./CollectionCarouselView.scss"; import { CollectionSubView } from './CollectionSubView'; -import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { Doc } from '../../../fields/Doc'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { ContextMenu } from '../ContextMenu'; @@ -76,10 +75,10 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) @computed get buttons() { return <>
    - +
    - +
    ; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 3860ce2d7..a4fd5384f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -25,6 +25,7 @@ export interface ViewDefBounds { fontSize?: number; highlight?: boolean; color?: string; + opacity?: number; replica?: string; pair?: { layout: Doc, data?: Doc }; } @@ -37,6 +38,7 @@ export interface PoolData { width?: number; height?: number; color?: string; + opacity?: number; transition?: string; highlight?: boolean; replica: string; @@ -416,7 +418,7 @@ function normalizeResults( height: newPosRaw.height! * scale, pair: ele[1].pair }; - poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos }); + poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "all 1s", ...newPos }); } }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bf679309c..751ee03d2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -46,6 +46,7 @@ import React = require("react"); import { CollectionViewType } from "../CollectionView"; import { Timeline } from "../../animationtimeline/Timeline"; import { SnappingManager } from "../../../util/SnappingManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -53,6 +54,7 @@ export const panZoomSchema = createSchema({ _panX: "number", _panY: "number", scale: "number", + timecode: "number", arrangeScript: ScriptField, arrangeInit: ScriptField, useClusters: "boolean", @@ -124,15 +126,8 @@ export class CollectionFreeFormView extends CollectionSubView { - const timecode = Cast(this.props.Document.timecode, "number", null); - if (timecode !== undefined) { - ((newBox instanceof Doc) ? [newBox] : newBox).map(doc => { - doc["x-indexed"] = new List(numberRange(timecode + 1).map(i => NumCast(doc.x))); - doc["y-indexed"] = new List(numberRange(timecode + 1).map(i => NumCast(doc.y))); - doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document }); - doc.x = ComputedField.MakeInterpolated("x", "timecode"); - doc.y = ComputedField.MakeInterpolated("y", "timecode"); - }); + if (this.Document.timecode !== undefined) { + CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.timecode, this.props.Document); } if (newBox instanceof Doc) { @@ -145,6 +140,25 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this.props.Document.timecode === undefined) { + this.props.Document.timecode = 0; + CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document); + } + const timecode = NumCast(this.props.Document.timecode); + CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, timecode); + this.props.Document.timecode = Math.max(0, timecode + 1); + } + @undoBatch + @action + prevKeyframe = (): void => { + CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1); + } + private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); @@ -185,8 +199,12 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.props.Document.timecode === undefined) { - this.childDocs.map(doc => { - this.props.Document.timecode = 0; - doc["x-indexed"] = new List([NumCast(doc.x)]); - doc["y-indexed"] = new List([NumCast(doc.y)]); - doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document }); - doc.x = ComputedField.MakeInterpolated("x", "timecode"); - doc.y = ComputedField.MakeInterpolated("y", "timecode"); - }); - } - const timecode = NumCast(this.props.Document.timecode); - this.childDocs.map(doc => { - const xindexed = Cast(doc['x-indexed'], listSpec("number"), null); - const yindexed = Cast(doc['y-indexed'], listSpec("number"), null); - xindexed.length <= timecode + 1 && xindexed.push(NumCast(doc.x)); - yindexed.length <= timecode + 1 && yindexed.push(NumCast(doc.y)); - }); - this.childDocs.map(doc => doc.transition = "transform 1s"); - this.props.Document.timecode = Math.max(0, timecode + 1); - setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010); - } - @undoBatch - @action - backupInterpolated = (): void => { - this.childDocs.map(doc => doc.transition = "transform 1s"); - this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1); - setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010); - } - - private thumbIdentifier?: number; onContextMenu = (e: React.MouseEvent) => { @@ -1185,8 +1175,8 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? this.placeholder : this.marqueeView} - + {this.isAnnotationOverlay ? (null) : + <> +
    + +
    +
    + {NumCast(this.props.Document.timecode)} +
    +
    + +
    + }
    { x: number, y: number, zIndex?: number, highlight?: boolean, z: number, transition?: string } | undefined; + dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined; zIndex?: number; highlight?: boolean; @@ -35,6 +40,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (i <= time && x !== undefined) || p === undefined ? x : p, undefined as any as number), + y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= time && y !== undefined) || p === undefined ? y : p, undefined as any as number), + opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= time || p === undefined ? o : p, undefined as any as number), + }); + } + + public static setValues(timecode: number, d: Doc, x?: number, y?: number, opacity?: number) { + Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number; + Cast(d["y-indexed"], listSpec("number"), null)[timecode] = y as any as number; + Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number; + } + public static updateKeyframe(docs: Doc[], timecode: number) { + docs.forEach(doc => { + const xindexed = Cast(doc['x-indexed'], listSpec("number"), null); + const yindexed = Cast(doc['y-indexed'], listSpec("number"), null); + const opacityindexed = Cast(doc['opacity-indexed'], listSpec("number"), null); + 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); + doc.transition = "all 1s"; + }); + setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010); + } + + public static gotoKeyframe(docs: Doc[]) { + docs.forEach(doc => doc.transition = "all 1s"); + setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010); + } + + public static setupKeyframes(docs: Doc[], timecode: number, collection: Doc) { + docs.forEach(doc => { + doc["x-indexed"] = new List(numberRange(timecode).map(i => undefined) as any as number[]); + doc["y-indexed"] = new List(numberRange(timecode).map(i => undefined) as any as number[]); + doc["opacity-indexed"] = new List(numberRange(timecode).map(i => 0)); + (doc["x-indexed"] as any).push(NumCast(doc.x)); + (doc["y-indexed"] as any).push(NumCast(doc.y)); + (doc["opacity-indexed"] as any).push(NumCast(doc.opacity, 1)); + doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection }); + doc.x = ComputedField.MakeInterpolated("x", "timecode"); + doc.y = ComputedField.MakeInterpolated("y", "timecode"); + doc.opacity = ComputedField.MakeInterpolated("opacity", "timecode"); + }); + } + nudge = (x: number, y: number) => { this.props.Document.x = NumCast(this.props.Document.x) + x; this.props.Document.y = NumCast(this.props.Document.y) + y; @@ -79,7 +132,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any) +}); + export namespace ComputedField { let useComputed = true; export function DisableComputedFields() { -- cgit v1.2.3-70-g09d2