From dced40cd975732c2c98f230a6c750af54ffdabd4 Mon Sep 17 00:00:00 2001 From: Mehek Jethani Date: Sat, 17 Jul 2021 15:04:27 -0400 Subject: basic trim ui complete - switching laptops --- src/client/views/AudioWaveform.tsx | 165 ++- .../collections/CollectionStackedTimeline.scss | 140 ++- .../collections/CollectionStackedTimeline.tsx | 1147 +++++++++++++------- src/client/views/nodes/AudioBox.scss | 334 +++--- src/client/views/nodes/AudioBox.tsx | 990 +++++++++++------ 5 files changed, 1794 insertions(+), 982 deletions(-) (limited to 'src') diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 7ff9c1071..313d0062e 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -6,56 +6,135 @@ import Waveform from "react-audio-waveform"; import { Doc } from "../../fields/Doc"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; +import { Cast, NumCast } from "../../fields/Types"; import { numberRange } from "../../Utils"; import "./AudioWaveform.scss"; export interface AudioWaveformProps { - duration: number; - mediaPath: string; - dataDoc: Doc; - PanelHeight: () => number; + duration: number; + mediaPath: string; + dataDoc: Doc; + trimming: boolean; + PanelHeight: () => number; } @observer export class AudioWaveform extends React.Component { - public static NUMBER_OF_BUCKETS = 100; - @computed get _waveHeight() { return Math.max(50, this.props.PanelHeight()); } - componentDidMount() { - const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []); - if (!audioBuckets.length) { - this.props.dataDoc.audioBuckets = new List([0, 0]); /// "lock" to prevent other views from computing the same data - setTimeout(this.createWaveformBuckets); - } - } - // decodes the audio file into peaks for generating the waveform - createWaveformBuckets = async () => { - axios({ url: this.props.mediaPath, responseType: "arraybuffer" }) - .then(response => { - const context = new window.AudioContext(); - context.decodeAudioData(response.data, - action(buffer => { - const decodedAudioData = buffer.getChannelData(0); - const bucketDataSize = Math.floor(decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS); - const brange = Array.from(Array(bucketDataSize)); - this.props.dataDoc.audioBuckets = new List( - numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map((i: number) => - brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2)); - })); - }); + public static NUMBER_OF_BUCKETS = 100; + @computed get _waveHeight() { + return Math.max(50, this.props.PanelHeight()); + } + componentDidMount() { + const audioBuckets = Cast( + this.props.dataDoc.audioBuckets, + listSpec("number"), + [] + ); + if (!audioBuckets.length) { + this.props.dataDoc.audioBuckets = new List([0, 0]); /// "lock" to prevent other views from computing the same data + setTimeout(this.createWaveformBuckets); } - render() { - const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []); - return
- -
; - } -} \ No newline at end of file + // const trimBuckets = Cast( + // this.props.dataDoc.trimBuckets, + // listSpec("number"), + // [] + // ); + // if (!trimBuckets.length) { + // this.props.dataDoc.trimBuckets = new List([0, 0]); /// "lock" to prevent other views from computing the same data + // this.createTrimBuckets(); + // } + } + // decodes the audio file into peaks for generating the waveform + createWaveformBuckets = async () => { + axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then( + (response) => { + const context = new window.AudioContext(); + context.decodeAudioData( + response.data, + action((buffer) => { + const decodedAudioData = buffer.getChannelData(0); + + const bucketDataSize = Math.floor( + decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS + ); + const brange = Array.from(Array(bucketDataSize)); + this.props.dataDoc.audioBuckets = new List( + numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map( + (i: number) => + brange.reduce( + (p, x, j) => + Math.abs( + Math.max(p, decodedAudioData[i * bucketDataSize + j]) + ), + 0 + ) / 2 + ) + ); + }) + ); + } + ); + }; + + @action + createTrimBuckets = () => { + const audioBuckets = Cast( + this.props.dataDoc.audioBuckets, + listSpec("number"), + [] + ); + + const start = Math.floor( + (NumCast(this.props.dataDoc.clipStart) / this.props.duration) * 100 + ); + const end = Math.floor( + (NumCast(this.props.dataDoc.clipEnd) / this.props.duration) * 100 + ); + return audioBuckets.slice(start, end); + }; + + render() { + const audioBuckets = Cast( + this.props.dataDoc.audioBuckets, + listSpec("number"), + [] + ); + + const trimBuckets = Cast( + this.props.dataDoc.trimBuckets, + listSpec("number"), + [] + ); + + return ( +
+ {this.props.trimming ? ( + + ) : ( + + )} +
+ ); + } +} diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index e456c0664..92476e298 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,70 +1,92 @@ .collectionStackedTimeline { + position: absolute; + width: 100%; + height: 100%; + border: gray solid 1px; + border-radius: 3px; + z-index: 1000; + overflow: hidden; + top: 0px; + + .collectionStackedTimeline-trim-shade { position: absolute; - width: 100%; height: 100%; - border: gray solid 1px; - border-radius: 3px; - z-index: 1000; - overflow: hidden; - top: 0px; + background-color: black; + opacity: 0.3; + } - .collectionStackedTimeline-selector { - position: absolute; - width: 10px; - top: 2.5%; - height: 95%; - background: lightblue; - border-radius: 5px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } + .collectionStackedTimeline-trim-controls { + height: 100%; + position: absolute; + border: 2px solid yellow; + display: flex; + justify-content: space-between; - .collectionStackedTimeline-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - pointer-events: none; + .collectionStackedTimeline-trim-handle { + background-color: yellow; + height: 100%; + width: 5px; + cursor: ew-resize; } + } + + .collectionStackedTimeline-selector { + position: absolute; + width: 10px; + top: 2.5%; + height: 95%; + background: lightblue; + border-radius: 5px; + opacity: 0.3; + z-index: 500; + border-style: solid; + border-color: darkblue; + border-width: 1px; + } - .collectionStackedTimeline-marker-timeline { - position: absolute; - top: 2.5%; - height: 95%; - border-radius: 4px; - &:hover { - opacity: 1; - } + .collectionStackedTimeline-current { + width: 1px; + height: 100%; + background-color: red; + position: absolute; + top: 0px; + pointer-events: none; + } - .collectionStackedTimeline-left-resizer, - .collectionStackedTimeline-resizer { - background: dimgrey; - position: absolute; - top: 0; - height: 100%; - width: 10px; - pointer-events: all; - cursor: ew-resize; - z-index: 100; - } - .collectionStackedTimeline-resizer { - right: 0; - } - .collectionStackedTimeline-left-resizer { - left: 0; - } + .collectionStackedTimeline-marker-timeline { + position: absolute; + top: 2.5%; + height: 95%; + border-radius: 4px; + &:hover { + opacity: 1; } - .collectionStackedTimeline-waveform { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - pointer-events: none; + .collectionStackedTimeline-left-resizer, + .collectionStackedTimeline-resizer { + background: dimgrey; + position: absolute; + top: 0; + height: 100%; + width: 10px; + pointer-events: all; + cursor: ew-resize; + z-index: 100; + } + .collectionStackedTimeline-resizer { + right: 0; + } + .collectionStackedTimeline-left-resizer { + left: 0; } -} \ No newline at end of file + } + + .collectionStackedTimeline-waveform { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + pointer-events: none; + } +} diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index a2c95df6e..230238ba8 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,5 +1,12 @@ import React = require("react"); -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { + action, + computed, + IReactionDisposer, + observable, + reaction, + runInAction, +} from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { Doc, DocListCast } from "../../../fields/Doc"; @@ -8,7 +15,16 @@ import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; -import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent, returnTrue } from "../../../Utils"; +import { + emptyFunction, + formatTime, + OmitKeys, + returnFalse, + returnOne, + setupMoveUpEvents, + StopEvent, + returnTrue, +} from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { LinkManager } from "../../util/LinkManager"; import { Scripting } from "../../util/Scripting"; @@ -18,412 +34,799 @@ import { undoBatch } from "../../util/UndoManager"; import { AudioWaveform } from "../AudioWaveform"; import { CollectionSubView } from "../collections/CollectionSubView"; import { LightboxView } from "../LightboxView"; -import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; +import { + DocAfterFocusFunc, + DocFocusFunc, + DocumentView, + DocumentViewProps, +} from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; type PanZoomDocument = makeInterface<[]>; const PanZoomDocument = makeInterface(); export type CollectionStackedTimelineProps = { - duration: number; - Play: () => void; - Pause: () => void; - playLink: (linkDoc: Doc) => void; - playFrom: (seekTimeInSeconds: number, endTime?: number) => void; - playing: () => boolean; - setTime: (time: number) => void; - startTag: string; - endTag: string; - mediaPath: string; - dictationKey: string; + duration: number; + Play: () => void; + Pause: () => void; + playLink: (linkDoc: Doc) => void; + playFrom: (seekTimeInSeconds: number, endTime?: number) => void; + playing: () => boolean; + setTime: (time: number) => void; + startTag: string; + endTag: string; + mediaPath: string; + dictationKey: string; + trimming: boolean; + trimBounds: { start: number; end: number }; }; @observer -export class CollectionStackedTimeline extends CollectionSubView(PanZoomDocument) { - @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; - static RangeScript: ScriptField; - static LabelScript: ScriptField; - static RangePlayScript: ScriptField; - static LabelPlayScript: ScriptField; - - private _timeline: HTMLDivElement | null = null; - private _markerStart: number = 0; - @observable _markerEnd: number = 0; - - get duration() { return this.props.duration; } - @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); } - @computed get selectionContainer() { - return CollectionStackedTimeline.SelectingRegion !== this ? (null) :
; +export class CollectionStackedTimeline extends CollectionSubView< + PanZoomDocument, + CollectionStackedTimelineProps +>(PanZoomDocument) { + @observable static SelectingRegion: + | CollectionStackedTimeline + | undefined = undefined; + static RangeScript: ScriptField; + static LabelScript: ScriptField; + static RangePlayScript: ScriptField; + static LabelPlayScript: ScriptField; + + private _timeline: HTMLDivElement | null = null; + private _markerStart: number = 0; + @observable _markerEnd: number = 0; + + get minLength() { + const rect = this._timeline?.getBoundingClientRect(); + if (rect) { + return 0.05 * this.duration; } + return 0; + } + + get trimStart() { + return this.props.trimBounds.start; + } + + get trimEnd() { + return this.props.trimBounds.end; + } + + set trimStart(start: number) { + this.props.trimBounds.start = start; + } + + set trimEnd(end: number) { + this.props.trimBounds.end = end; + } + + get duration() { + return this.props.duration; + } + @computed get currentTime() { + return NumCast(this.layoutDoc._currentTimecode); + } + @computed get selectionContainer() { + return CollectionStackedTimeline.SelectingRegion !== this ? null : ( +
+ ); + } + + constructor(props: any) { + super(props); + // onClick play scripts + CollectionStackedTimeline.RangeScript = + CollectionStackedTimeline.RangeScript || + ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { + self: Doc.name, + scriptContext: "any", + clientX: "number", + })!; + CollectionStackedTimeline.RangePlayScript = + CollectionStackedTimeline.RangePlayScript || + ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { + self: Doc.name, + scriptContext: "any", + clientX: "number", + })!; + } + + componentDidMount() { + document.addEventListener("keydown", this.keyEvents, true); + } + componentWillUnmount() { + document.removeEventListener("keydown", this.keyEvents, true); + if (CollectionStackedTimeline.SelectingRegion === this) + runInAction( + () => (CollectionStackedTimeline.SelectingRegion = undefined) + ); + } + + anchorStart = (anchor: Doc) => + NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); + anchorEnd = (anchor: Doc, val: any = null) => { + const endVal = NumCast(anchor[this.props.endTag], val); + return NumCast( + anchor._timecodeToHide, + endVal === undefined ? null : endVal + ); + }; + toTimeline = (screen_delta: number, width: number) => + Math.max( + 0, + Math.min(this.duration, (screen_delta / width) * this.duration) + ); + rangeClickScript = () => CollectionStackedTimeline.RangeScript; + rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; - constructor(props: any) { - super(props); - // onClick play scripts - CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!; - CollectionStackedTimeline.RangePlayScript = CollectionStackedTimeline.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!; + // for creating key anchors with key events + @action + keyEvents = (e: KeyboardEvent) => { + if ( + !(e.target instanceof HTMLInputElement) && + this.props.isSelected(true) + ) { + switch (e.key) { + case " ": + if (!CollectionStackedTimeline.SelectingRegion) { + this._markerStart = this._markerEnd = this.currentTime; + CollectionStackedTimeline.SelectingRegion = this; + } else { + CollectionStackedTimeline.createAnchor( + this.rootDoc, + this.dataDoc, + this.props.fieldKey, + this.props.startTag, + this.props.endTag, + this.currentTime + ); + CollectionStackedTimeline.SelectingRegion = undefined; + } + } } + }; - componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); } - componentWillUnmount() { - document.removeEventListener("keydown", this.keyEvents, true); - if (CollectionStackedTimeline.SelectingRegion === this) runInAction(() => CollectionStackedTimeline.SelectingRegion = undefined); + getLinkData(l: Doc) { + let la1 = l.anchor1 as Doc; + let la2 = l.anchor2 as Doc; + const linkTime = NumCast( + la2[this.props.startTag], + NumCast(la1[this.props.startTag]) + ); + if (Doc.AreProtosEqual(la1, this.dataDoc)) { + la1 = l.anchor2 as Doc; + la2 = l.anchor1 as Doc; } + return { la1, la2, linkTime }; + } - anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); - anchorEnd = (anchor: Doc, val: any = null) => { - const endVal = NumCast(anchor[this.props.endTag], val); - return NumCast(anchor._timecodeToHide, endVal === undefined ? null : endVal); + // starting the drag event for anchor resizing + @action + onPointerDownTimeline = (e: React.PointerEvent): void => { + const rect = this._timeline?.getBoundingClientRect(); + const clientX = e.clientX; + if (rect && this.props.isContentActive()) { + const wasPlaying = this.props.playing(); + if (wasPlaying) this.props.Pause(); + const wasSelecting = CollectionStackedTimeline.SelectingRegion === this; + setupMoveUpEvents( + this, + e, + action((e) => { + if ( + !wasSelecting && + CollectionStackedTimeline.SelectingRegion !== this + ) { + this._markerStart = this._markerEnd = this.toTimeline( + clientX - rect.x, + rect.width + ); + CollectionStackedTimeline.SelectingRegion = this; + } + this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); + return false; + }), + action((e, movement, isClick) => { + this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); + if (this._markerEnd < this._markerStart) { + const tmp = this._markerStart; + this._markerStart = this._markerEnd; + this._markerEnd = tmp; + } + if ( + !isClick && + CollectionStackedTimeline.SelectingRegion === this && + Math.abs(movement[0]) > 15 && + !this.props.trimming + ) { + CollectionStackedTimeline.createAnchor( + this.rootDoc, + this.dataDoc, + this.props.fieldKey, + this.props.startTag, + this.props.endTag, + this._markerStart, + this._markerEnd + ); + } + (!isClick || !wasSelecting) && + (CollectionStackedTimeline.SelectingRegion = undefined); + }), + (e, doubleTap) => { + this.props.select(false); + e.shiftKey && + CollectionStackedTimeline.createAnchor( + this.rootDoc, + this.dataDoc, + this.props.fieldKey, + this.props.startTag, + this.props.endTag, + this.currentTime + ); + !wasPlaying && doubleTap && this.props.Play(); + }, + this.props.isSelected(true) || this.props.isContentActive(), + undefined, + () => + !wasPlaying && + this.props.setTime(((clientX - rect.x) / rect.width) * this.duration) + ); } - toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration)); - rangeClickScript = () => CollectionStackedTimeline.RangeScript; - rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; - - // for creating key anchors with key events - @action - keyEvents = (e: KeyboardEvent) => { - if (!(e.target instanceof HTMLInputElement) && this.props.isSelected(true)) { - switch (e.key) { - case " ": - if (!CollectionStackedTimeline.SelectingRegion) { - this._markerStart = this._markerEnd = this.currentTime; - CollectionStackedTimeline.SelectingRegion = this; - } else { - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime); - CollectionStackedTimeline.SelectingRegion = undefined; - } - } - } + }; + + @action + startTrimLeft = (e: React.PointerEvent): void => { + document.addEventListener("pointermove", this.dragTrimLeft); + document.addEventListener("pointerup", this.endTrimLeft); + }; + + @action + startTrimRight = (e: React.PointerEvent): void => { + document.addEventListener("pointermove", this.dragTrimRight); + document.addEventListener("pointerup", this.endTrimRight); + }; + + @action + dragTrimLeft = (e: MouseEvent) => { + const rect = this._timeline?.getBoundingClientRect(); + if (rect && this.props.isContentActive()) { + this.trimStart = Math.min( + Math.max( + this.trimStart + (e.movementX / rect.width) * this.duration, + 0 + ), + this.trimEnd - this.minLength + ); } + }; - getLinkData(l: Doc) { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag])); - if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; - } - return { la1, la2, linkTime }; + @action + dragTrimRight = (e: MouseEvent) => { + const rect = this._timeline?.getBoundingClientRect(); + if (rect && this.props.isContentActive()) { + this.trimEnd = Math.max( + Math.min( + this.trimEnd + (e.movementX / rect.width) * this.duration, + this.duration + ), + this.trimStart + this.minLength + ); } + }; - // starting the drag event for anchor resizing - @action - onPointerDownTimeline = (e: React.PointerEvent): void => { - const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.clientX; - if (rect && this.props.isContentActive()) { - const wasPlaying = this.props.playing(); - if (wasPlaying) this.props.Pause(); - const wasSelecting = CollectionStackedTimeline.SelectingRegion === this; - setupMoveUpEvents(this, e, - action(e => { - if (!wasSelecting && CollectionStackedTimeline.SelectingRegion !== this) { - this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); - CollectionStackedTimeline.SelectingRegion = this; - } - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); - return false; - }), - action((e, movement, isClick) => { - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); - if (this._markerEnd < this._markerStart) { - const tmp = this._markerStart; - this._markerStart = this._markerEnd; - this._markerEnd = tmp; - } - if (!isClick && CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15)) { - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, - this._markerStart, this._markerEnd); - } - (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined); - }), - (e, doubleTap) => { - this.props.select(false); - e.shiftKey && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime); - !wasPlaying && doubleTap && this.props.Play(); - }, - this.props.isSelected(true) || this.props.isContentActive(), undefined, - () => !wasPlaying && this.props.setTime((clientX - rect.x) / rect.width * this.duration)); - } + endTrimLeft = () => { + document.removeEventListener("pointermove", this.dragTrimLeft); + document.removeEventListener("pointerup", this.endTrimLeft); + }; + + endTrimRight = () => { + document.removeEventListener("pointermove", this.dragTrimRight); + document.removeEventListener("pointerup", this.endTrimRight); + }; + + @undoBatch + @action + static createAnchor( + rootDoc: Doc, + dataDoc: Doc, + fieldKey: string, + startTag: string, + endTag: string, + anchorStartTime?: number, + anchorEndTime?: number + ) { + if (anchorStartTime === undefined) return rootDoc; + const anchor = Docs.Create.LabelDocument({ + title: ComputedField.MakeFunction( + `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])` + ) as any, + useLinkSmallAnchor: true, + hideLinkButton: true, + annotationOn: rootDoc, + _timelineLabel: true, + }); + Doc.GetProto(anchor)[startTag] = anchorStartTime; + Doc.GetProto(anchor)[endTag] = anchorEndTime; + if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) { + Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor); + } else { + dataDoc[fieldKey] = new List([anchor]); } + return anchor; + } - @undoBatch - @action - static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number) { - if (anchorStartTime === undefined) return rootDoc; - const anchor = Docs.Create.LabelDocument({ - title: ComputedField.MakeFunction(`"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`) as any, - useLinkSmallAnchor: true, - hideLinkButton: true, - annotationOn: rootDoc, - _timelineLabel: true - }); - Doc.GetProto(anchor)[startTag] = anchorStartTime; - Doc.GetProto(anchor)[endTag] = anchorEndTime; - if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) { - Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor); + @action + playOnClick = (anchorDoc: Doc, clientX: number) => { + const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; + const endTime = this.anchorEnd(anchorDoc); + if (this.layoutDoc.autoPlayAnchors) { + if (this.props.playing()) this.props.Pause(); + else this.props.playFrom(seekTimeInSeconds, endTime); + } else { + if ( + seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && + endTime > NumCast(this.layoutDoc._currentTimecode) + ) { + if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) { + this.props.Pause(); } else { - dataDoc[fieldKey] = new List([anchor]); + this.props.Play(); } - return anchor; + } else { + this.props.playFrom(seekTimeInSeconds, endTime); + } } + return { select: true }; + }; - @action - playOnClick = (anchorDoc: Doc, clientX: number) => { - const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; - const endTime = this.anchorEnd(anchorDoc); - if (this.layoutDoc.autoPlayAnchors) { - if (this.props.playing()) this.props.Pause(); - else this.props.playFrom(seekTimeInSeconds, endTime); - } else { - if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) { - if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) { - this.props.Pause(); - } else { - this.props.Play(); - } - } else { - this.props.playFrom(seekTimeInSeconds, endTime); - } - } - return { select: true }; + @action + clickAnchor = (anchorDoc: Doc, clientX: number) => { + if (anchorDoc.isLinkButton) + LinkManager.FollowLink(undefined, anchorDoc, this.props, false); + const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; + const endTime = this.anchorEnd(anchorDoc); + if ( + seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && + endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4 + ) { + if (this.props.playing()) this.props.Pause(); + else if (this.layoutDoc.autoPlayAnchors) this.props.Play(); + else if (!this.layoutDoc.autoPlayAnchors) { + const rect = this._timeline?.getBoundingClientRect(); + rect && + this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); + } + } else { + if (this.layoutDoc.autoPlayAnchors) + this.props.playFrom(seekTimeInSeconds, endTime); + else this.props.setTime(seekTimeInSeconds); } + return { select: true }; + }; - @action - clickAnchor = (anchorDoc: Doc, clientX: number) => { - if (anchorDoc.isLinkButton) LinkManager.FollowLink(undefined, anchorDoc, this.props, false); - const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; - const endTime = this.anchorEnd(anchorDoc); - if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) { - if (this.props.playing()) this.props.Pause(); - else if (this.layoutDoc.autoPlayAnchors) this.props.Play(); - else if (!this.layoutDoc.autoPlayAnchors) { - const rect = this._timeline?.getBoundingClientRect(); - rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); - } - } else { - if (this.layoutDoc.autoPlayAnchors) this.props.playFrom(seekTimeInSeconds, endTime); - else this.props.setTime(seekTimeInSeconds); + // makes sure no anchors overlaps each other by setting the correct position and width + getLevel = ( + m: Doc, + placed: { anchorStartTime: number; anchorEndTime: number; level: number }[] + ) => { + const timelineContentWidth = this.props.PanelWidth(); + const x1 = this.anchorStart(m); + const x2 = this.anchorEnd( + m, + x1 + (10 / timelineContentWidth) * this.duration + ); + let max = 0; + const overlappedLevels = new Set( + placed.map((p) => { + const y1 = p.anchorStartTime; + const y2 = p.anchorEndTime; + if ( + (x1 >= y1 && x1 <= y2) || + (x2 >= y1 && x2 <= y2) || + (y1 >= x1 && y1 <= x2) || + (y2 >= x1 && y2 <= x2) + ) { + max = Math.max(max, p.level); + return p.level; } - return { select: true }; - } + }) + ); + let level = max + 1; + for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j); + placed.push({ anchorStartTime: x1, anchorEndTime: x2, level }); + return level; + }; - // makes sure no anchors overlaps each other by setting the correct position and width - getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => { - const timelineContentWidth = this.props.PanelWidth(); - const x1 = this.anchorStart(m); - const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration); - let max = 0; - const overlappedLevels = new Set(placed.map(p => { - const y1 = p.anchorStartTime; - const y2 = p.anchorEndTime; - if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) || - (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) { - max = Math.max(max, p.level); - return p.level; - } - })); - let level = max + 1; - for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j); - - placed.push({ anchorStartTime: x1, anchorEndTime: x2, level }); - return level; - } + dictationHeightPercent = 50; + dictationHeight = () => + (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; + timelineContentHeight = () => + (this.props.PanelHeight() * this.dictationHeightPercent) / 100; + dictationScreenToLocalTransform = () => + this.props + .ScreenToLocalTransform() + .translate(0, -this.timelineContentHeight()); + @computed get renderDictation() { + const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); + return !dictation ? null : ( +
+ +
+ ); + } + @computed get renderAudioWaveform() { + return !this.props.mediaPath ? null : ( +
+ +
+ ); + } - dictationHeightPercent = 50; - dictationHeight = () => this.props.PanelHeight() * (100 - this.dictationHeightPercent) / 100; - timelineContentHeight = () => this.props.PanelHeight() * this.dictationHeightPercent / 100; - dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight()); - @computed get renderDictation() { - const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); - return !dictation ? (null) :
- - -
; - } - @computed get renderAudioWaveform() { - return !this.props.mediaPath ? (null) : -
- -
; - } - currentTimecode = () => this.currentTime; - render() { - const timelineContentWidth = this.props.PanelWidth(); - const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = []; - const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); - const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - const isActive = this.props.isContentActive() || this.props.isSelected(false); - return
this._timeline = timeline} - onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}> - {drawAnchors.map(d => { - const start = this.anchorStart(d.anchor); - const end = this.anchorEnd(d.anchor, start + 10 / timelineContentWidth * this.duration); - const left = start / this.duration * timelineContentWidth; - const top = d.level / maxLevel * this.timelineContentHeight(); - const timespan = end - start; - return this.props.Document.hideAnchors ? (null) : -
{ this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} > - -
; - })} - {this.selectionContainer} - {this.renderAudioWaveform} - {this.renderDictation} - -
-
; - } + currentTimecode = () => this.currentTime; + render() { + const timelineContentWidth = this.props.PanelWidth(); + const overlaps: { + anchorStartTime: number; + anchorEndTime: number; + level: number; + }[] = []; + const drawAnchors = this.childDocs.map((anchor) => ({ + level: this.getLevel(anchor, overlaps), + anchor, + })); + const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; + const isActive = + this.props.isContentActive() || this.props.isSelected(false); + return ( +
(this._timeline = timeline)} + onClick={(e) => isActive && StopEvent(e)} + onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)} + > + {drawAnchors.map((d) => { + const start = this.anchorStart(d.anchor); + const end = this.anchorEnd( + d.anchor, + start + (10 / timelineContentWidth) * this.duration + ); + const left = (start / this.duration) * timelineContentWidth; + const top = (d.level / maxLevel) * this.timelineContentHeight(); + const timespan = end - start; + return this.props.Document.hideAnchors ? null : ( +
{ + this.props.playFrom(start, this.anchorEnd(d.anchor)); + e.stopPropagation(); + }} + > + +
+ ); + })} + {!this.props.trimming && this.selectionContainer} + {this.renderAudioWaveform} + {this.renderDictation} + +
+ + {this.props.trimming && ( +
+
+ +
+
+
+
+ +
+
+ )} +
+ ); + } } interface StackedTimelineAnchorProps { - mark: Doc; - rangeClickScript: () => ScriptField; - rangePlayScript: () => ScriptField; - left: number; - top: number; - width: number; - height: number; - toTimeline: (screen_delta: number, width: number) => number; - playLink: (linkDoc: Doc) => void; - setTime: (time: number) => void; - startTag: string; - endTag: string; - renderDepth: number; - layoutDoc: Doc; - ScreenToLocalTransform: () => Transform; - _timeline: HTMLDivElement | null; - focus: DocFocusFunc; - currentTimecode: () => number; - isSelected: (outsideReaction?: boolean) => boolean; - stackedTimeline: CollectionStackedTimeline; + mark: Doc; + rangeClickScript: () => ScriptField; + rangePlayScript: () => ScriptField; + left: number; + top: number; + width: number; + height: number; + toTimeline: (screen_delta: number, width: number) => number; + playLink: (linkDoc: Doc) => void; + setTime: (time: number) => void; + startTag: string; + endTag: string; + renderDepth: number; + layoutDoc: Doc; + ScreenToLocalTransform: () => Transform; + _timeline: HTMLDivElement | null; + focus: DocFocusFunc; + currentTimecode: () => number; + isSelected: (outsideReaction?: boolean) => boolean; + stackedTimeline: CollectionStackedTimeline; } @observer class StackedTimelineAnchor extends React.Component { - _lastTimecode: number; - _disposer: IReactionDisposer | undefined; - constructor(props: any) { - super(props); - this._lastTimecode = this.props.currentTimecode(); - } - componentDidMount() { - this._disposer = reaction(() => this.props.currentTimecode(), - (time) => { - const dictationDoc = Cast(this.props.layoutDoc["data-dictation"], Doc, null); - const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc); - if (!LightboxView.LightboxDoc - // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. - // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. - /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/ - && DocListCast(this.props.mark.links).length && - time > NumCast(this.props.mark[this.props.startTag]) && - time < NumCast(this.props.mark[this.props.endTag]) && - this._lastTimecode < NumCast(this.props.mark[this.props.startTag])) { - LinkManager.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true); - } - this._lastTimecode = time; - }); - } - componentWillUnmount() { - this._disposer?.(); - } - // starting the drag event for anchor resizing - onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - this.props._timeline?.setPointerCapture(e.pointerId); - const newTime = (e: PointerEvent) => { - const rect = (e.target as any).getBoundingClientRect(); - return this.props.toTimeline(e.clientX - rect.x, rect.width); - }; - const changeAnchor = (anchor: Doc, left: boolean, time: number) => { - const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; - if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true); - else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; - return false; - }; - setupMoveUpEvents(this, e, - (e) => changeAnchor(anchor, left, newTime(e)), - (e) => { - this.props.setTime(newTime(e)); - this.props._timeline?.releasePointerCapture(e.pointerId); - }, - emptyFunction); - } - renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) { - const anchor = observable({ view: undefined as any }); - const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => { - this.props.playLink(mark); - this.props.focus(doc, { willZoom, scale, afterFocus, docTransform }); - }; - return { - anchor, view: anchor.view = r)} - Document={mark} - DataDoc={undefined} - renderDepth={this.props.renderDepth + 1} - LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutString("data")} - isDocumentActive={returnFalse} - PanelWidth={() => width} - PanelHeight={() => height} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)} - focus={focusFunc} - rootSelected={returnFalse} - onClick={script} - onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} - ignoreAutoHeight={false} - hideResizeHandles={true} - bringToFront={emptyFunction} - scriptContext={this.props.stackedTimeline} /> - }; - }); - render() { - const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.props.left, this.props.top, this.props.width, this.props.height); - return <> - {inner.view} - {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) : - <> -
this.onAnchorDown(e, this.props.mark, true)} /> -
this.onAnchorDown(e, this.props.mark, false)} /> - } - ; - } + _lastTimecode: number; + _disposer: IReactionDisposer | undefined; + constructor(props: any) { + super(props); + this._lastTimecode = this.props.currentTimecode(); + } + componentDidMount() { + this._disposer = reaction( + () => this.props.currentTimecode(), + (time) => { + const dictationDoc = Cast( + this.props.layoutDoc["data-dictation"], + Doc, + null + ); + const isDictation = + dictationDoc && + DocListCast(this.props.mark.links).some( + (link) => + Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc + ); + if ( + !LightboxView.LightboxDoc && + // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. + // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. + /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/ + DocListCast(this.props.mark.links).length && + time > NumCast(this.props.mark[this.props.startTag]) && + time < NumCast(this.props.mark[this.props.endTag]) && + this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) + ) { + LinkManager.FollowLink( + undefined, + this.props.mark, + (this.props as any) as DocumentViewProps, + false, + true + ); + } + this._lastTimecode = time; + } + ); + } + componentWillUnmount() { + this._disposer?.(); + } + // starting the drag event for anchor resizing + onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { + this.props._timeline?.setPointerCapture(e.pointerId); + const newTime = (e: PointerEvent) => { + const rect = (e.target as any).getBoundingClientRect(); + return this.props.toTimeline(e.clientX - rect.x, rect.width); + }; + const changeAnchor = (anchor: Doc, left: boolean, time: number) => { + const timelineOnly = + Cast(anchor[this.props.startTag], "number", null) !== undefined; + if (timelineOnly) + Doc.SetInPlace( + anchor, + left ? this.props.startTag : this.props.endTag, + time, + true + ); + else + left + ? (anchor._timecodeToShow = time) + : (anchor._timecodeToHide = time); + return false; + }; + setupMoveUpEvents( + this, + e, + (e) => changeAnchor(anchor, left, newTime(e)), + (e) => { + this.props.setTime(newTime(e)); + this.props._timeline?.releasePointerCapture(e.pointerId); + }, + emptyFunction + ); + }; + renderInner = computedFn(function ( + this: StackedTimelineAnchor, + mark: Doc, + script: undefined | (() => ScriptField), + doublescript: undefined | (() => ScriptField), + x: number, + y: number, + width: number, + height: number + ) { + const anchor = observable({ view: undefined as any }); + const focusFunc = ( + doc: Doc, + willZoom?: boolean, + scale?: number, + afterFocus?: DocAfterFocusFunc, + docTransform?: Transform + ) => { + this.props.playLink(mark); + this.props.focus(doc, { willZoom, scale, afterFocus, docTransform }); + }; + return { + anchor, + view: ( + (anchor.view = r))} + Document={mark} + DataDoc={undefined} + renderDepth={this.props.renderDepth + 1} + LayoutTemplate={undefined} + LayoutTemplateString={LabelBox.LayoutString("data")} + isDocumentActive={returnFalse} + PanelWidth={() => width} + PanelHeight={() => height} + ScreenToLocalTransform={() => + this.props.ScreenToLocalTransform().translate(-x, -y) + } + focus={focusFunc} + rootSelected={returnFalse} + onClick={script} + onDoubleClick={ + this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript + } + ignoreAutoHeight={false} + hideResizeHandles={true} + bringToFront={emptyFunction} + scriptContext={this.props.stackedTimeline} + /> + ), + }; + }); + render() { + const inner = this.renderInner( + this.props.mark, + this.props.rangeClickScript, + this.props.rangePlayScript, + this.props.left, + this.props.top, + this.props.width, + this.props.height + ); + return ( + <> + {inner.view} + {!inner.anchor.view || + !SelectionManager.IsSelected(inner.anchor.view) ? null : ( + <> +
this.onAnchorDown(e, this.props.mark, true)} + /> +
+ this.onAnchorDown(e, this.props.mark, false) + } + /> + + )} + + ); + } } -Scripting.addGlobal(function formatToTime(time: number): any { return formatTime(time); }); \ No newline at end of file +Scripting.addGlobal(function formatToTime(time: number): any { + return formatTime(time); +}); diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 3fcb024df..0fb0dc70e 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,188 +1,200 @@ .audiobox-container, .audiobox-container-interactive { + width: 100%; + height: 100%; + position: inherit; + display: flex; + position: relative; + cursor: default; + + .audiobox-buttons { + display: flex; width: 100%; + align-items: center; height: 100%; - position: inherit; - display: flex; - position: relative; - cursor: default; - .audiobox-buttons { - display: flex; - width: 100%; - align-items: center; - height: 100%; - - .audiobox-dictation { - position: relative; - width: 30px; - height: 100%; - align-items: center; - display: inherit; - background: dimgray; - left: 0px; - &:hover { - color: white; - cursor: pointer; - } - } + .audiobox-dictation { + position: relative; + width: 30px; + height: 100%; + align-items: center; + display: inherit; + background: dimgray; + left: 0px; + &:hover { + color: white; + cursor: pointer; + } } + } - .audiobox-control, - .audiobox-control-interactive { - top: 0; - max-height: 32px; - width: 100%; - display: inline-block; - pointer-events: none; - } + .audiobox-control, + .audiobox-control-interactive { + top: 0; + max-height: 32px; + width: 100%; + display: inline-block; + pointer-events: none; + } + + .audiobox-control-interactive { + pointer-events: all; + } + + .audiobox-record-interactive, + .audiobox-record { + pointer-events: all; + width: 100%; + height: 100%; + position: relative; + } - .audiobox-control-interactive { - pointer-events: all; + .audiobox-record { + pointer-events: none; + } + + .recording { + margin-top: auto; + margin-bottom: auto; + width: 100%; + height: 100%; + position: relative; + padding-right: 5px; + display: flex; + background-color: red; + + .time { + position: relative; + height: 100%; + width: 100%; + font-size: 20; + text-align: center; + top: 5; } - .audiobox-record-interactive, - .audiobox-record { - pointer-events: all; - width: 100%; - height: 100%; - position: relative; + .buttons { + position: relative; + margin-top: auto; + margin-bottom: auto; + width: 25px; + padding: 5px; + &:hover { + background-color: crimson; + } } + } + + .audiobox-controls { + width: 100%; + height: 100%; + position: relative; + display: flex; + padding-left: 2px; + background: black; - .audiobox-record { - pointer-events: none; + .audiobox-dictation { + position: absolute; + width: 32px; + height: 100%; + align-items: center; + display: inherit; + background: dimgray; + left: 0px; } - .recording { + .audiobox-player { + margin-top: auto; + margin-bottom: auto; + width: 100%; + position: relative; + padding-right: 5px; + display: flex; + flex-direction: column; + + .audiobox-playhead { + position: relative; margin-top: auto; margin-bottom: auto; - width: 100%; - height: 100%; - position: relative; - padding-right: 5px; + margin-right: 2px; + height: 25px; + border-radius: 50%; + background-color: black; + color: white; display: flex; - background-color: red; - - .time { - position: relative; - height: 100%; - width: 100%; - font-size: 20; - text-align: center; - top: 5; + align-items: center; + justify-content: center; + &:hover { + background-color: grey; + color: lightgrey; } - .buttons { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - padding: 5px; - &:hover{ - background-color: crimson; - } + svg { + width: 100%; + position: absolute; + border-width: "thin"; + border-color: "white"; } - } + } - .audiobox-controls { - width: 100%; - height: 100%; + .audiobox-dictation { position: relative; - display: flex; - padding-left: 2px; - background: black; - - .audiobox-dictation { - position: absolute; - width: 30px; - height: 100%; - align-items: center; - display: inherit; - background: dimgray; - left: 0px; - } + margin-top: auto; + margin-bottom: auto; + width: 25px; + padding: 2px; + align-items: center; + display: inherit; + background: dimgray; + } - .audiobox-player { - margin-top: auto; - margin-bottom: auto; - width: 100%; - position: relative; - padding-right: 5px; - display: flex; - - .audiobox-playhead { - position: relative; - margin-top: auto; - margin-bottom: auto; - margin-right: 2px; - height: 25px; - padding: 2px; - border-radius: 50%; - background-color: black; - color: white; - &:hover { - background-color: grey; - color: lightgrey; - } - } - - .audiobox-dictation { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - padding: 2px; - align-items: center; - display: inherit; - background: dimgray; - } - - .audiobox-timeline { - position: absolute; - width: 100%; - border: gray solid 1px; - border-radius: 3px; - z-index: 1000; - overflow: hidden; - } - - .audioBox-total-time, - .audioBox-current-time { - position: absolute; - font-size: 8; - top: 100%; - color: white; - } - .audioBox-current-time { - left: 30px; - } - - .audioBox-total-time { - right: 2px; - } - } + .audiobox-timeline { + position: absolute; + width: 100%; + border: gray solid 1px; + border-radius: 3px; + z-index: 1000; + overflow: hidden; + } + + .audioBox-total-time, + .audioBox-current-time { + position: absolute; + font-size: 8; + top: 100%; + color: white; + } + .audioBox-current-time { + left: 30px; + } + + .audioBox-total-time { + right: 2px; + } } + } } - @media only screen and (max-device-width: 480px) { - .audiobox-dictation { - font-size: 5em; - display: flex; - width: 100; - justify-content: center; - flex-direction: column; - align-items: center; - } - - .audiobox-container .audiobox-record, - .audiobox-container-interactive .audiobox-record { - font-size: 3em; - } - - .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead, - .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation, - .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead { - width: 70px; - } -} \ No newline at end of file + .audiobox-dictation { + font-size: 5em; + display: flex; + width: 100; + justify-content: center; + flex-direction: column; + align-items: center; + } + + .audiobox-container .audiobox-record, + .audiobox-container-interactive .audiobox-record { + font-size: 3em; + } + + .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead, + .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation, + .audiobox-container-interactive + .audiobox-controls + .audiobox-player + .audiobox-playhead { + width: 70px; + } +} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index a2e36f12e..99960e934 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,6 +1,13 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { + action, + computed, + IReactionDisposer, + observable, + reaction, + runInAction, +} from "mobx"; import { observer } from "mobx-react"; import { DateField } from "../../../fields/DateField"; import { Doc, DocListCast, Opt } from "../../../fields/Doc"; @@ -17,376 +24,665 @@ import { SnappingManager } from "../../util/SnappingManager"; import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; +import { + ViewBoxAnnotatableComponent, + ViewBoxAnnotatableProps, +} from "../DocComponent"; import "./AudioBox.scss"; -import { FieldView, FieldViewProps } from './FieldView'; +import { FieldView, FieldViewProps } from "./FieldView"; import { LinkDocPreview } from "./LinkDocPreview"; declare class MediaRecorder { - constructor(e: any); // whatever MediaRecorder has + constructor(e: any); // whatever MediaRecorder has } type AudioDocument = makeInterface<[typeof documentSchema]>; const AudioDocument = makeInterface(documentSchema); @observer -export class AudioBox extends ViewBoxAnnotatableComponent(AudioDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } - public static Enabled = false; - static playheadWidth = 30; // width of playhead - static heightPercent = 80; // height of timeline in percent of height of audioBox. - static Instance: AudioBox; - - _disposers: { [name: string]: IReactionDisposer } = {}; - _ele: HTMLAudioElement | null = null; - _stackedTimeline = React.createRef(); - _recorder: any; - _recordStart = 0; - _pauseStart = 0; - _pauseEnd = 0; - _pausedTime = 0; - _stream: MediaStream | undefined; - _start: number = 0; - _play: any = null; - - @observable static _scrubTime = 0; - @observable _markerEnd: number = 0; - @observable _position: number = 0; - @observable _waveHeight: Opt = this.layoutDoc._height; - @observable _paused: boolean = false; - @computed get mediaState(): undefined | "pendingRecording" | "recording" | "paused" | "playing" { return this.dataDoc.mediaState as (undefined | "pendingRecording" | "recording" | "paused" | "playing"); } - set mediaState(value) { this.dataDoc.mediaState = value; } - public static SetScrubTime = action((timeInMillisFrom1970: number) => { AudioBox._scrubTime = 0; AudioBox._scrubTime = timeInMillisFrom1970; }); - @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } - @computed get duration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); } - @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey]); } - @computed get links() { return DocListCast(this.dataDoc.links); } - @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct time - @computed get heightPercent() { return AudioBox.heightPercent; } - - constructor(props: Readonly) { - super(props); - AudioBox.Instance = this; - - if (this.duration === undefined) { - runInAction(() => this.Document[this.fieldKey + "-duration"] = this.Document.duration); - } - } - - getLinkData(l: Doc) { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1) || 0; - if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; - } - return { la1, la2, linkTime }; - } - - getAnchor = () => { - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, - "_timecodeToShow" /* audioStart */, "_timecodeToHide" /* audioEnd */, this._ele?.currentTime || - Cast(this.props.Document._currentTimecode, "number", null) || (this.mediaState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) - || this.rootDoc; +export class AudioBox extends ViewBoxAnnotatableComponent< + ViewBoxAnnotatableProps & FieldViewProps, + AudioDocument +>(AudioDocument) { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(AudioBox, fieldKey); + } + public static Enabled = false; + static playheadWidth = 30; // width of playhead + static heightPercent = 80; // height of timeline in percent of height of audioBox. + static Instance: AudioBox; + + _disposers: { [name: string]: IReactionDisposer } = {}; + _ele: HTMLAudioElement | null = null; + _stackedTimeline = React.createRef(); + _recorder: any; + _recordStart = 0; + _pauseStart = 0; + _pauseEnd = 0; + _pausedTime = 0; + _stream: MediaStream | undefined; + _start: number = 0; + _play: any = null; + + @observable static _scrubTime = 0; + @observable _markerEnd: number = 0; + @observable _position: number = 0; + @observable _waveHeight: Opt = this.layoutDoc._height; + @observable _paused: boolean = false; + @observable _trimming: boolean = false; + @observable _trimBounds: { start: number; end: number } = { + start: this.dataDoc.clipStart ? this.dataDoc.clipStart : 0, + end: this.dataDoc.clipEnd + ? this.dataDoc.clipEnd + : this.duration + ? this.duration + : undefined, + }; + @computed get mediaState(): + | undefined + | "pendingRecording" + | "recording" + | "paused" + | "playing" { + return this.dataDoc.mediaState as + | undefined + | "pendingRecording" + | "recording" + | "paused" + | "playing"; + } + set mediaState(value) { + this.dataDoc.mediaState = value; + } + public static SetScrubTime = action((timeInMillisFrom1970: number) => { + AudioBox._scrubTime = 0; + AudioBox._scrubTime = timeInMillisFrom1970; + }); + @computed get recordingStart() { + return Cast( + this.dataDoc[this.props.fieldKey + "-recordingStart"], + DateField + )?.date.getTime(); + } + @computed get duration() { + return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); + } + @computed get anchorDocs() { + return DocListCast(this.dataDoc[this.annotationKey]); + } + @computed get links() { + return DocListCast(this.dataDoc.links); + } + @computed get pauseTime() { + return this._pauseEnd - this._pauseStart; + } // total time paused to update the correct time + @computed get heightPercent() { + return AudioBox.heightPercent; + } + + constructor(props: Readonly) { + super(props); + AudioBox.Instance = this; + + if (this.duration === undefined) { + runInAction( + () => + (this.Document[this.fieldKey + "-duration"] = this.Document.duration) + ); } - - componentWillUnmount() { - Object.values(this._disposers).forEach(disposer => disposer?.()); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1)); + } + + getLinkData(l: Doc) { + let la1 = l.anchor1 as Doc; + let la2 = l.anchor2 as Doc; + const linkTime = + this._stackedTimeline.current?.anchorStart(la2) || + this._stackedTimeline.current?.anchorStart(la1) || + 0; + if (Doc.AreProtosEqual(la1, this.dataDoc)) { + la1 = l.anchor2 as Doc; + la2 = l.anchor1 as Doc; } - - @action - componentDidMount() { - this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. - - this.mediaState = this.path ? "paused" : undefined; - - this._disposers.triggerAudio = reaction( - () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined, - start => start !== undefined && setTimeout(() => { - this.playFrom(start); - setTimeout(() => { - this.Document._currentTimecode = start; - this.Document._triggerAudio = undefined; - }, 10); - }), // wait for mainCont and try again to play - { fireImmediately: true } - ); - - this._disposers.audioStop = reaction( - () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? Cast(this.Document._audioStop, "number", null) : undefined, - audioStop => audioStop !== undefined && setTimeout(() => { - this.Pause(); - setTimeout(() => this.Document._audioStop = undefined, 10); - }), // wait for mainCont and try again to play - { fireImmediately: true } + return { la1, la2, linkTime }; + } + + getAnchor = () => { + return ( + CollectionStackedTimeline.createAnchor( + this.rootDoc, + this.dataDoc, + this.annotationKey, + "_timecodeToShow" /* audioStart */, + "_timecodeToHide" /* audioEnd */, + this._ele?.currentTime || + Cast(this.props.Document._currentTimecode, "number", null) || + (this.mediaState === "recording" + ? (Date.now() - (this.recordingStart || 0)) / 1000 + : undefined) + ) || this.rootDoc + ); + }; + + componentWillUnmount() { + Object.values(this._disposers).forEach((disposer) => disposer?.()); + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + } + + @action + componentDidMount() { + this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + + this.mediaState = this.path ? "paused" : undefined; + + this._disposers.triggerAudio = reaction( + () => + !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 + ? NumCast(this.Document._triggerAudio, null) + : undefined, + (start) => + start !== undefined && + setTimeout(() => { + this.playFrom(start); + setTimeout(() => { + this.Document._currentTimecode = start; + this.Document._triggerAudio = undefined; + }, 10); + }), // wait for mainCont and try again to play + { fireImmediately: true } + ); + + this._disposers.audioStop = reaction( + () => + this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo + ? Cast(this.Document._audioStop, "number", null) + : undefined, + (audioStop) => + audioStop !== undefined && + setTimeout(() => { + this.Pause(); + setTimeout(() => (this.Document._audioStop = undefined), 10); + }), // wait for mainCont and try again to play + { fireImmediately: true } + ); + } + + // for updating the timecode + timecodeChanged = () => { + const htmlEle = this._ele; + if (this.mediaState !== "recording" && htmlEle) { + htmlEle.duration && + htmlEle.duration !== Infinity && + runInAction( + () => (this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration) ); + this.links + .map((l) => this.getLinkData(l)) + .forEach(({ la1, la2, linkTime }) => { + if ( + linkTime > NumCast(this.layoutDoc._currentTimecode) && + linkTime < htmlEle.currentTime + ) { + Doc.linkFollowHighlight(la1); + } + }); + this.layoutDoc._currentTimecode = htmlEle.currentTime; } - - // for updating the timecode - timecodeChanged = () => { - const htmlEle = this._ele; - if (this.mediaState !== "recording" && htmlEle) { - htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration); - this.links.map(l => this.getLinkData(l)).forEach(({ la1, la2, linkTime }) => { - if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) { - Doc.linkFollowHighlight(la1); - } - }); - this.layoutDoc._currentTimecode = htmlEle.currentTime; - } - } - - // pause play back - Pause = action(() => { - this._ele!.pause(); - this.mediaState = "paused"; - }); - - // play audio for documents created during recording - playFromTime = (absoluteTime: number) => { - this.recordingStart && this.playFrom((absoluteTime - this.recordingStart) / 1000); - } - - // play back the audio from time - @action - playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => { - clearTimeout(this._play); - if (Number.isNaN(this._ele?.duration)) { - setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); - } else if (this._ele && AudioBox.Enabled) { - if (seekTimeInSeconds < 0) { - if (seekTimeInSeconds > -1) { - setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); - } else { - this.Pause(); - } - } else if (seekTimeInSeconds <= this._ele.duration) { - this._ele.currentTime = seekTimeInSeconds; - this._ele.play(); - runInAction(() => this.mediaState = "playing"); - if (endTime !== this.duration) { - this._play = setTimeout(() => this.Pause(), (endTime - seekTimeInSeconds) * 1000); // use setTimeout to play a specific duration - } - } else { - this.Pause(); - } + }; + + // pause play back + Pause = action(() => { + this._ele!.pause(); + this.mediaState = "paused"; + }); + + // play audio for documents created during recording + playFromTime = (absoluteTime: number) => { + this.recordingStart && + this.playFrom((absoluteTime - this.recordingStart) / 1000); + }; + + // play back the audio from time + @action + playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => { + clearTimeout(this._play); + if (Number.isNaN(this._ele?.duration)) { + setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); + } else if (this._ele && AudioBox.Enabled) { + if (seekTimeInSeconds < 0) { + if (seekTimeInSeconds > -1) { + setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); + } else { + this.Pause(); } - } - - // update the recording time - updateRecordTime = () => { - if (this.mediaState === "recording") { - setTimeout(this.updateRecordTime, 30); - if (this._paused) { - this._pausedTime += (new Date().getTime() - this._recordStart) / 1000; - } else { - this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; - } + } else if (seekTimeInSeconds <= this._ele.duration) { + this._ele.currentTime = seekTimeInSeconds; + this._ele.play(); + runInAction(() => (this.mediaState = "playing")); + if (endTime !== this.duration) { + this._play = setTimeout( + () => this.Pause(), + (endTime - seekTimeInSeconds) * 1000 + ); // use setTimeout to play a specific duration } + } else { + this.Pause(); + } } - - // starts recording - recordAudioAnnotation = async () => { - this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - this._recorder = new MediaRecorder(this._stream); - this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date()); - DocUtils.ActiveRecordings.push(this); - this._recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(e.data); - if (!(result instanceof Error)) { - this.props.Document[this.props.fieldKey] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client)); - } - }; - this._recordStart = new Date().getTime(); - runInAction(() => this.mediaState = "recording"); - setTimeout(this.updateRecordTime, 0); - this._recorder.start(); - setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour - } - - // context menu - specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ description: (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors", event: () => this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors, icon: "expand-arrows-alt" }); - funcs.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + " play when link is selected", event: () => this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks, icon: "expand-arrows-alt" }); - funcs.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors, icon: "expand-arrows-alt" }); - ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + }; + + // update the recording time + updateRecordTime = () => { + if (this.mediaState === "recording") { + setTimeout(this.updateRecordTime, 30); + if (this._paused) { + this._pausedTime += (new Date().getTime() - this._recordStart) / 1000; + } else { + this.layoutDoc._currentTimecode = + (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + } } - - // stops the recording - stopRecording = action(() => { - this._recorder.stop(); - this._recorder = undefined; - this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; - this.mediaState = "paused"; - this._stream?.getAudioTracks()[0].stop(); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1)); + }; + + // starts recording + recordAudioAnnotation = async () => { + this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + this._recorder = new MediaRecorder(this._stream); + this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField( + new Date() + ); + DocUtils.ActiveRecordings.push(this); + this._recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer(e.data); + if (!(result instanceof Error)) { + this.props.Document[this.props.fieldKey] = new AudioField( + Utils.prepend(result.accessPaths.agnostic.client) + ); + } + }; + this._recordStart = new Date().getTime(); + runInAction(() => (this.mediaState = "recording")); + setTimeout(this.updateRecordTime, 0); + this._recorder.start(); + setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour + }; + + // context menu + specificContextMenu = (e: React.MouseEvent): void => { + const funcs: ContextMenuProps[] = []; + funcs.push({ + description: + (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors", + event: () => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors), + icon: "expand-arrows-alt", }); - - // button for starting and stopping the recording - recordClick = (e: React.MouseEvent) => { - if (e.button === 0 && !e.ctrlKey) { - this._recorder ? this.stopRecording() : this.recordAudioAnnotation(); - e.stopPropagation(); - } - } - - // for play button - Play = (e?: any) => { - this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1); - e?.stopPropagation?.(); - } - - // creates a text document for dictation - onFile = (e: any) => { - const newDoc = CurrentUserUtils.GetNewTextDoc("", NumCast(this.props.Document.x), NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, - NumCast(this.props.Document._width), 2 * NumCast(this.props.Document._height)); - Doc.GetProto(newDoc).recordingSource = this.dataDoc; - Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`); - Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState"); - this.props.addDocument?.(newDoc); - e.stopPropagation(); - } - - // ref for updating time - setRef = (e: HTMLAudioElement | null) => { - e?.addEventListener("timeupdate", this.timecodeChanged); - e?.addEventListener("ended", this.Pause); - this._ele = e; + funcs.push({ + description: + (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + + " play when link is selected", + event: () => + (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc + .dontAutoPlayFollowedLinks), + icon: "expand-arrows-alt", + }); + funcs.push({ + description: + (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") + + " anchors onClick", + event: () => + (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), + icon: "expand-arrows-alt", + }); + ContextMenu.Instance?.addItem({ + description: "Options...", + subitems: funcs, + icon: "asterisk", + }); + }; + + // stops the recording + stopRecording = action(() => { + this._recorder.stop(); + this._recorder = undefined; + this.dataDoc[this.fieldKey + "-duration"] = + (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + this.mediaState = "paused"; + this._trimBounds.end = this.duration; + this.dataDoc.clipStart = 0; + this.dataDoc.clipEnd = this.duration; + this._stream?.getAudioTracks()[0].stop(); + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + }); + + // button for starting and stopping the recording + recordClick = (e: React.MouseEvent) => { + if (e.button === 0 && !e.ctrlKey) { + this._recorder ? this.stopRecording() : this.recordAudioAnnotation(); + e.stopPropagation(); } - - // returns the path of the audio file - @computed get path() { - const field = Cast(this.props.Document[this.props.fieldKey], AudioField); - const path = (field instanceof AudioField) ? field.url.href : ""; - return path === nullAudio ? "" : path; + }; + + // for play button + Play = (e?: any) => { + if (this._trimming) { + let end = this._trimBounds.end; + let start = + this._ele!.currentTime > end || + this._ele!.currentTime < this._trimBounds.start + ? -1 + : this._trimBounds.start; + this.playFrom(this._ele!.paused ? this._ele!.currentTime : start, end); + } else { + this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1); } - - // returns the html audio element - @computed get audio() { - return ; + e?.stopPropagation?.(); + }; + + // creates a text document for dictation + onFile = (e: any) => { + const newDoc = CurrentUserUtils.GetNewTextDoc( + "", + NumCast(this.props.Document.x), + NumCast(this.props.Document.y) + + NumCast(this.props.Document._height) + + 10, + NumCast(this.props.Document._width), + 2 * NumCast(this.props.Document._height) + ); + Doc.GetProto(newDoc).recordingSource = this.dataDoc; + Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction( + `self.recordingSource["${this.props.fieldKey}-recordingStart"]` + ); + Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction( + "self.recordingSource.mediaState" + ); + this.props.addDocument?.(newDoc); + e.stopPropagation(); + }; + + // ref for updating time + setRef = (e: HTMLAudioElement | null) => { + e?.addEventListener("timeupdate", this.timecodeChanged); + e?.addEventListener("ended", this.Pause); + this._ele = e; + }; + + // returns the path of the audio file + @computed get path() { + const field = Cast(this.props.Document[this.props.fieldKey], AudioField); + const path = field instanceof AudioField ? field.url.href : ""; + return path === nullAudio ? "" : path; + } + + // returns the html audio element + @computed get audio() { + return ( + + ); + } + + // pause the time during recording phase + @action + recordPause = (e: React.MouseEvent) => { + this._pauseStart = new Date().getTime(); + this._paused = true; + this._recorder.pause(); + e.stopPropagation(); + }; + + // continue the recording + @action + recordPlay = (e: React.MouseEvent) => { + this._pauseEnd = new Date().getTime(); + this._paused = false; + this._recorder.resume(); + e.stopPropagation(); + }; + + playing = () => this.mediaState === "playing"; + playLink = (link: Doc) => { + const stack = this._stackedTimeline.current; + if (link.annotationOn === this.rootDoc) { + if (!this.layoutDoc.dontAutoPlayFollowedLinks) + this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link)); + else + this._ele!.currentTime = this.layoutDoc._currentTimecode = + stack?.anchorStart(link) || 0; + } else { + this.links + .filter((l) => l.anchor1 === link || l.anchor2 === link) + .forEach((l) => { + const { la1, la2 } = this.getLinkData(l); + const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2); + const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2); + if (startTime !== undefined) { + if (!this.layoutDoc.dontAutoPlayFollowedLinks) + endTime + ? this.playFrom(startTime, endTime) + : this.playFrom(startTime); + else + this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime; + } + }); } + }; - // pause the time during recording phase - @action - recordPause = (e: React.MouseEvent) => { - this._pauseStart = new Date().getTime(); - this._paused = true; - this._recorder.pause(); - e.stopPropagation(); + // shows trim controls + @action + startTrim = () => { + if (this.mediaState === "playing") { + this.Pause(); } - - // continue the recording - @action - recordPlay = (e: React.MouseEvent) => { - this._pauseEnd = new Date().getTime(); - this._paused = false; - this._recorder.resume(); - e.stopPropagation(); + this._trimming = true; + }; + + // hides trim controls and displays new clip + @action + finishTrim = () => { + if (this.mediaState === "playing") { + this.Pause(); } - - playing = () => this.mediaState === "playing"; - playLink = (link: Doc) => { - const stack = this._stackedTimeline.current; - if (link.annotationOn === this.rootDoc) { - if (!this.layoutDoc.dontAutoPlayFollowedLinks) this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link)); - else this._ele!.currentTime = this.layoutDoc._currentTimecode = (stack?.anchorStart(link) || 0); + this.dataDoc.clipStart = this._trimBounds.start; + this.dataDoc.clipEnd = this._trimBounds.end; + this._trimming = false; + }; + + isActiveChild = () => this._isAnyChildContentActive; + timelineWhenChildContentsActiveChanged = (isActive: boolean) => + this.props.whenChildContentsActiveChanged( + runInAction(() => (this._isAnyChildContentActive = isActive)) + ); + timelineScreenToLocal = () => + this.props + .ScreenToLocalTransform() + .translate( + -AudioBox.playheadWidth, + (-(100 - this.heightPercent) / 200) * this.props.PanelHeight() + ); + setAnchorTime = (time: number) => + (this._ele!.currentTime = this.layoutDoc._currentTimecode = time); + timelineHeight = () => + (((this.props.PanelHeight() * this.heightPercent) / 100) * + this.heightPercent) / + 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) + timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; + @computed get renderTimeline() { + return ( + l.anchor1 === link || l.anchor2 === link).forEach(l => { - const { la1, la2 } = this.getLinkData(l); - const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2); - const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2); - if (startTime !== undefined) { - if (!this.layoutDoc.dontAutoPlayFollowedLinks) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime); - else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime; - } - }); + removeDocument={this.removeDocument} + ScreenToLocalTransform={this.timelineScreenToLocal} + Play={this.Play} + Pause={this.Pause} + isContentActive={this.isContentActive} + playLink={this.playLink} + PanelWidth={this.timelineWidth} + PanelHeight={this.timelineHeight} + trimming={this._trimming} + trimBounds={this._trimBounds} + /> + ); + } + + render() { + const interactive = + SnappingManager.GetIsDragging() || this.isContentActive() + ? "-interactive" + : ""; + return ( +
this._isAnyChildContentActive; - timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged(runInAction(() => this._isAnyChildContentActive = isActive)); - timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-AudioBox.playheadWidth, -(100 - this.heightPercent) / 200 * this.props.PanelHeight()); - setAnchorTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time; - timelineHeight = () => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) - timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; - @computed get renderTimeline() { - return ; - } - - render() { - const interactive = SnappingManager.GetIsDragging() || this.isContentActive() ? "-interactive" : ""; - return
- {!this.path ? -
-
- -
- {this.mediaState === "recording" || this.mediaState === "paused" ? -
e.stopPropagation()}> -
- -
-
- -
-
{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
-
- : - } -
: -
-
-
-
-
- {this.renderTimeline} -
- {this.audio} -
- {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))} -
-
- {formatTime(Math.round(this.duration))} -
-
+ style={{ + pointerEvents: + this.props.layerProvider?.(this.layoutDoc) === false + ? "none" + : undefined, + }} + > + {!this.path ? ( +
+
+ +
+ {this.mediaState === "recording" || this.mediaState === "paused" ? ( +
e.stopPropagation()}> +
+
- } -
; - } -} \ No newline at end of file +
+ +
+
+ {formatTime( + Math.round(NumCast(this.layoutDoc._currentTimecode)) + )} +
+
+ ) : ( + + )} +
+ ) : ( +
+
+
+
+ {" "} + +
+
+ +
+
+ {this.renderTimeline} +
+ {this.audio} +
+ {formatTime( + Math.round(NumCast(this.layoutDoc._currentTimecode)) + )} +
+
+ {formatTime(Math.round(this.duration))} +
+
+
+ )} +
+ ); + } +} -- cgit v1.2.3-70-g09d2 From f5c126ba41bb15837c3527e588ba6fb3c79f3e89 Mon Sep 17 00:00:00 2001 From: geireann Date: Sat, 14 Aug 2021 13:29:46 -0400 Subject: code reorganising and updates --- package-lock.json | 5 +- src/client/documents/Documents.ts | 15 +- src/client/util/CurrentUserUtils.ts | 171 ++- src/client/util/tempCurrentUserUtils.ts | 1389 ++++++++++++++++++++ .../views/collections/CollectionLinearView.scss | 139 -- .../views/collections/CollectionLinearView.tsx | 193 --- .../views/collections/collectionFreeForm/index.ts | 7 + .../views/collections/collectionGrid/index.ts | 2 + .../collectionLinearView/CollectionLinearView.scss | 139 ++ .../collectionLinearView/CollectionLinearView.tsx | 192 +++ .../collections/collectionLinearView/index.ts | 1 + src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/FontIconBox.scss | 103 -- src/client/views/nodes/FontIconBox.tsx | 96 -- src/client/views/nodes/button/FontIconBadge.tsx | 37 + src/client/views/nodes/button/FontIconBox.scss | 103 ++ src/client/views/nodes/button/FontIconBox.tsx | 295 +++++ 17 files changed, 2328 insertions(+), 561 deletions(-) create mode 100644 src/client/util/tempCurrentUserUtils.ts delete mode 100644 src/client/views/collections/CollectionLinearView.scss delete mode 100644 src/client/views/collections/CollectionLinearView.tsx create mode 100644 src/client/views/collections/collectionFreeForm/index.ts create mode 100644 src/client/views/collections/collectionGrid/index.ts create mode 100644 src/client/views/collections/collectionLinearView/CollectionLinearView.scss create mode 100644 src/client/views/collections/collectionLinearView/CollectionLinearView.tsx create mode 100644 src/client/views/collections/collectionLinearView/index.ts delete mode 100644 src/client/views/nodes/FontIconBox.scss delete mode 100644 src/client/views/nodes/FontIconBox.tsx create mode 100644 src/client/views/nodes/button/FontIconBadge.tsx create mode 100644 src/client/views/nodes/button/FontIconBox.scss create mode 100644 src/client/views/nodes/button/FontIconBox.tsx (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 58cdf8805..59ae898bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7694,14 +7694,13 @@ "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { - "image-size": "image-size@github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", "tryit": "^1.0.1" }, "dependencies": { "image-size": { - "version": "git+ssh://git@github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1", - "from": "image-size@github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" + "version": "git+https://github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "from": "git+https://github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1" }, "isarray": { "version": "0.0.1", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 142c37ea4..a15b017bd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -37,7 +37,7 @@ import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; import { DocFocusOptions } from "../views/nodes/DocumentView"; import { FilterBox } from "../views/nodes/FilterBox"; -import { FontIconBox } from "../views/nodes/FontIconBox"; +import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; @@ -215,7 +215,19 @@ export class DocumentOptions { annotationOn?: Doc; isPushpin?: boolean; _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document + + //BUTTONS iconShape?: string; // shapes of the fonticon border + btnType?: string; + btnList?: List; + docColorBtn?: string; + userColorBtn?: string; + canClick?: string; + + //LINEAR VIEW + linearViewIsExpanded?: boolean; // is linear view expanded + linearViewExpandable?: boolean; // can linear view be expanded + layout_linkView?: Doc; // view template for a link document layout_keyValue?: string; // view tempalte for key value docs linkRelationship?: string; // type of relatinoship a link represents @@ -266,7 +278,6 @@ export class DocumentOptions { selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui clipboard?: Doc; searchQuery?: string; // for quersyBox - linearViewIsExpanded?: boolean; // is linear view expanded useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 66f9d060f..ecce573a1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -2,13 +2,14 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; +import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; @@ -19,6 +20,7 @@ import { Networking } from "../Network"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; +import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { LabelBox } from "../views/nodes/LabelBox"; @@ -31,13 +33,11 @@ import { LinkManager } from "./LinkManager"; import { Scripting } from "./Scripting"; import { SearchUtil } from "./SearchUtil"; import { SelectionManager } from "./SelectionManager"; -import { UndoManager } from "./UndoManager"; -import { SnappingManager } from "./SnappingManager"; -import { InkTool } from "../../fields/InkField"; -import { SharingManager } from "./SharingManager"; -import { computedFn } from "mobx-utils"; import { ColorScheme } from "./SettingsManager"; -import { Colors } from "../views/global/globalEnums"; +import { SharingManager } from "./SharingManager"; +import { SnappingManager } from "./SnappingManager"; +import { UndoManager } from "./UndoManager"; +import { ButtonType } from "../views/nodes/button/FontIconBox"; export let resolvedPorts: { server: number, socket: number }; @@ -68,13 +68,14 @@ export class CurrentUserUtils { [this.ficon({ ignoreClick: true, icon: "mobile", + btnType: ButtonType.ClickButton, backgroundColor: "transparent" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); doc["template-mobile-button"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile" + dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ClickButton, }); } @@ -89,7 +90,8 @@ export class CurrentUserUtils { slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); doc["template-button-slides"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card" + dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card", + btnType: ButtonType.ClickButton }); } @@ -135,7 +137,8 @@ export class CurrentUserUtils { doc["template-button-link"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true + dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true, + btnType: ButtonType.ClickButton }); } @@ -166,7 +169,8 @@ export class CurrentUserUtils { doc["template-button-switch"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true + dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true, + btnType: ButtonType.ClickButton }); } @@ -215,7 +219,11 @@ export class CurrentUserUtils { doc["template-button-detail"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(detailView) as any as Doc, title: "detailView", icon: "window-maximize", system: true + dragFactory: new PrefetchProxy(detailView) as any as Doc, + title: "detailView", + icon: "window-maximize", + system: true, + btnType: ButtonType.ClickButton, }); } @@ -501,6 +509,7 @@ export class CurrentUserUtils { icon, title, toolTip, + btnType: ButtonType.ClickButton, ignoreClick, _dropAction: "alias", onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, @@ -556,7 +565,7 @@ export class CurrentUserUtils { const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) => Docs.Create.FontIconDocument({ icon, - iconShape: "square", + btnType: ButtonType.MenuButton, _stayInCollection: true, _hideContextMenu: true, system: true, @@ -577,7 +586,6 @@ export class CurrentUserUtils { title: "menuItemPanel", childDropAction: "alias", _chromeHidden: true, - backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), ignoreClick: true, @@ -637,7 +645,7 @@ export class CurrentUserUtils { onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, backgroundColor: data.backgroundColor, system: true }, - [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) + [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) ); } @@ -747,8 +755,8 @@ export class CurrentUserUtils { if (doc.myTools === undefined) { const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, - system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, + title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, + system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", })) as any as Doc; doc.myTools = toolsStack; @@ -869,9 +877,9 @@ export class CurrentUserUtils { } static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { - ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", _forceActive: true, + ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _width: 100, boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - backgroundColor: "black", _lockedPosition: true, linearViewIsExpanded: true, system: true + _lockedPosition: true, linearViewIsExpanded: true, system: true })) as any as Doc static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ @@ -881,18 +889,133 @@ export class CurrentUserUtils { /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc) { if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "click to undo", title: "undo", icon: "undo-alt", system: true }); + doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true }); } if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "click to redo", title: "redo", icon: "redo-alt", system: true }); + doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true }); } if (doc.dockedBtns === undefined) { - doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); + doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); } (doc["dockedBtn-undo"] as Doc).dontUndo = true; (doc["dockedBtn-redo"] as Doc).dontUndo = true; } + static textTools(doc: Doc) { + return [ + { + title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", + "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], + scriptDoc: Doc.UserDoc(), toggle: 'userDoc._fontFamily' + }, + { + title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', + scriptDoc: Doc.UserDoc(), + toggle: 'userDoc._boldActive' + }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._italicsActive' }, + { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._underlineActive' }, + // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()', toggle: 'userDoc._underlineActive' }, + // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()', toggle: 'userDoc._underlineActive' }, + // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()', toggle: 'userDoc._underlineActive' }, + { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._highlightColor' }, + { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "fill-drip", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._textColor' }, + // { title: "Link", tooltip: "Link", btnType: ButtonType.DropdownButton, icon: "link", click: '', ignoreClick: true }, + ]; + } + + static async contextMenuBtnDescriptions(doc: Doc) { + return [ + // { title: "Perspective", tooltip: "Change document's perspective", type: "btn", btnType: ButtonType.DropdownButton, ignoreClick: true, icon: "desktop", click: '' }, + { + title: "Perspective", toolTip: "Perspective", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, + CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, + CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, + CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, + CollectionViewType.Grid], + scriptDoc: 'selectedDoc', + toggle: 'selectedDoc._viewType' + }, + { + title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, scriptDoc: 'selectedDoc', + docColorBtn: 'selectedDoc.backgroundColor', width: 60, ignoreClick: true, icon: "fill-drip", + canClick: 'numSelected > 0' + }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', toggle: 'selectedDoc.z', canClick: 'numSelected > 0' }, + { title: "Text Tools", type: "TextMenu", icon: "font" }, + // { title: "Ink Tools", type: "LinearMenu", icon: "pen-nib" }, + // { title: "GFX Tools", type: "LinearMenu", icon: "shapes" }, + // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, + ]; + } + + // Default context menu buttons + static async setupContextMenuButtons(doc: Doc) { + const docList: Doc[] = []; + + const contextMenuBtns = (await CurrentUserUtils.contextMenuBtnDescriptions(doc)).map(({ title, width, toolTip, ignoreClick, icon, type, btnType, click, toggle, scriptDoc, canClick, docColorBtn }) => { + const textDocList: Doc[] = []; + if (type === "TextMenu") { + const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, toggle, scriptDoc, userColorBtn }) => { + textDocList.push(Docs.Create.FontIconDocument({ + _nativeWidth: width ? width : 25, + _nativeHeight: 25, + _width: width ? width : 25, + _height: 25, + icon, + toolTip, + userColorBtn, + + // testToggle: toggle ? ScriptField.MakeScript(toggle, { this: scriptDoc, scriptContext: "any" }) : undefined, + // toggle: toggle, + btnType: btnType, + btnList: new List(list), + ignoreClick: ignoreClick, + _stayInCollection: true, + _hideContextMenu: true, + system: true, + dontUndo: true, + title, + backgroundColor: "black", + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + })); + }); + docList.push(CurrentUserUtils.blist({ ignoreClick: true, linearViewExpandable: true, _height: 30, backgroundColor: "transparent" }, textDocList)); + } else { + docList.push(Docs.Create.FontIconDocument({ + _nativeWidth: width ? width : 30, + _nativeHeight: 30, + _width: width ? width : 30, + _height: 30, + icon, + toolTip, + // testToggle: toggle ? ScriptField.MakeScript(toggle, { scriptContext: "any" }) : undefined, + // toggle: toggle, + docColorBtn, + canClick: canClick, + btnType: btnType, + ignoreClick: ignoreClick, + _stayInCollection: true, + _hideContextMenu: true, + system: true, + dontUndo: true, + title, + backgroundColor: "black", + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + })); + } + }); + + if (doc.contextMenuBtns === undefined) { + doc.contextMenuBtns = CurrentUserUtils.blist({ title: "menu buttons", ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList); + } + } // sets up the default set of documents to be shown in the Overlay layer static setupOverlays(doc: Doc) { if (doc.myOverlayDocs === undefined) { @@ -1052,8 +1175,8 @@ export class CurrentUserUtils { setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); + // doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); + // doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); // uncomment this to setup a default note style that uses the custom header layout // PromiseValue(doc.emptyHeader).then(factory => { diff --git a/src/client/util/tempCurrentUserUtils.ts b/src/client/util/tempCurrentUserUtils.ts new file mode 100644 index 000000000..3fba672e6 --- /dev/null +++ b/src/client/util/tempCurrentUserUtils.ts @@ -0,0 +1,1389 @@ +import { computed, observable, reaction, action } from "mobx"; +import * as rp from 'request-promise'; +import { DataSym, Doc, DocListCast, DocListCastAsync, AclReadonly } from "../../fields/Doc"; +import { Id } from "../../fields/FieldSymbols"; +import { List } from "../../fields/List"; +import { PrefetchProxy } from "../../fields/Proxy"; +import { RichTextField } from "../../fields/RichTextField"; +import { listSpec } from "../../fields/Schema"; +import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; +import { ComputedField, ScriptField } from "../../fields/ScriptField"; +import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types"; +import { nullAudio } from "../../fields/URLField"; +import { SharingPermissions } from "../../fields/util"; +import { Utils } from "../../Utils"; +import { DocServer } from "../DocServer"; +import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; +import { DocumentType } from "../documents/DocumentTypes"; +import { Networking } from "../Network"; +import { CollectionDockingView } from "../views/collections/collectionDocking/CollectionDockingView"; +import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; +import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; +import { MainView } from "../views/MainView"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; +import { LabelBox } from "../views/nodes/LabelBox"; +import { OverlayView } from "../views/OverlayView"; +import { DocumentManager } from "./DocumentManager"; +import { DragManager } from "./DragManager"; +import { makeTemplate } from "./DropConverter"; +import { HistoryUtil } from "./History"; +import { LinkManager } from "./LinkManager"; +import { Scripting } from "./Scripting"; +import { SearchUtil } from "./SearchUtil"; +import { SelectionManager } from "./SelectionManager"; +import { SnappingManager } from "./SnappingManager"; +import { InkTool } from "../../fields/InkField"; +import { ButtonType } from "../views/nodes/FontIconBox"; + + +export let resolvedPorts: { server: number, socket: number }; +const headerViewVersion = "0.1"; +export class CurrentUserUtils { + private static curr_id: string; + //TODO tfs: these should be temporary... + private static mainDocId: string | undefined; + + public static get id() { return this.curr_id; } + public static get MainDocId() { return this.mainDocId; } + public static set MainDocId(id: string | undefined) { this.mainDocId = id; } + @computed public static get UserDocument() { return Doc.UserDoc(); } + + @observable public static GuestTarget: Doc | undefined; + @observable public static GuestDashboard: Doc | undefined; + @observable public static GuestMobile: Doc | undefined; + @observable public static propertiesWidth: number = 0; + + // sets up the default User Templates - slideView, headerView + static setupUserTemplateButtons(doc: Doc) { + // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location) + if (doc["template-mobile-button"] === undefined) { + const queryTemplate = this.mobileButton({ + title: "NEW MOBILE BUTTON", + onClick: undefined, + }, + [this.ficon({ + ignoreClick: true, + icon: "mobile", + btnType: ButtonType.ClickButton, + backgroundColor: "transparent" + }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); + doc["template-mobile-button"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", + btnType: ButtonType.ClickButton, + icon: "mobile" + }); + } + + if (doc["template-button-slides"] === undefined) { + const slideTemplate = Docs.Create.MultirowDocument( + [ + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true }) + ], + { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } + ); + slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); + doc["template-button-slides"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", + icon: "address-card", + btnType: ButtonType.ClickButton + }); + } + + if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to. + const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created + linkTemplate.system = true; + Doc.GetProto(linkTemplate).layout = + "
" + + " " + + " " + + "
"; + (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView"); + + const rtf2 = { + doc: { + type: "doc", content: [ + { + type: "paragraph", + content: [{ + type: "dashField", + attrs: { + fieldKey: "src", + hideKey: false + } + }] + }, + { type: "paragraph" }, + { + type: "paragraph", + content: [{ + type: "dashField", + attrs: { + fieldKey: "dst", + hideKey: false + } + }] + }] + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + linkTemplate.header = new RichTextField(JSON.stringify(rtf2), ""); + + doc["template-button-link"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", + btnType: ButtonType.ClickButton, + icon: "window-maximize", system: true + }); + } + + if (doc["template-button-switch"] === undefined) { + const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; + + const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true }); + const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true }); + const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true }); + const labelTemplate = { + doc: { + type: "doc", content: [{ + type: "paragraph", + content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }] + }] + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS"); + Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'"); + // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'"); + // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); + Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); + // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); + const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true }); + box.isTemplateDoc = makeTemplate(box, true, "switch"); + + doc["template-button-switch"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", + btnType: ButtonType.ClickButton, + icon: "toggle-on", system: true + }); + } + + if (doc["template-button-detail"] === undefined) { + const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create; + + const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)"); + const carousel = CarouselDocument([], { + title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10, + onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F", system: true + }); + + const details = TextDocument("", { title: "details", _height: 200, _autoHeight: true, system: true }); + const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 75, _autoHeight: true, system: true }); + const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 150, _autoHeight: true, system: true }); + + const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"]; + const detailedTemplate = { + doc: { + type: "doc", content: buxtonFieldKeys.map(fieldKey => ({ + type: "paragraph", + content: [{ type: "dashField", attrs: { fieldKey } }] + })) + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); + + const shared = { _autoHeight: true, _xMargin: 0 }; + const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" }; + const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true }; + + const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); + descriptionWrapper._columnHeaders = new List([ + new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false), + new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true), + new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true), + ]); + const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeHidden: true, system: true }); + detailView.isTemplateDoc = makeTemplate(detailView); + + details.title = "Details"; + short.title = "A Short Description"; + long.title = "Long Description"; + + doc["template-button-detail"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(detailView) as any as Doc, title: "detailView", + btnType: ButtonType.ClickButton, + icon: "window-maximize", system: true + }); + } + + const requiredTypes = [ + doc["template-button-slides"] as Doc, + doc["template-mobile-button"] as Doc, + doc["template-button-detail"] as Doc, + doc["template-button-link"] as Doc, + //doc["template-button-switch"] as Doc] + ]; + if (doc["template-buttons"] === undefined) { + doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { + title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true, + hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, + _stayInCollection: true, _hideContextMenu: true, + _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true + })); + } else { + const curButnTypes = Cast(doc["template-buttons"], Doc, null); + DocListCastAsync(curButnTypes.data).then(async curBtns => { + curBtns && await Promise.all(curBtns); + requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype)); + }); + } + return doc["template-buttons"] as Doc; + } + + // setup the different note type skins + static setupNoteTemplates(doc: Doc) { + if (doc["template-note-Note"] === undefined) { + const noteView = Docs.Create.TextDocument("", { title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true }); + noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); + doc["template-note-Note"] = new PrefetchProxy(noteView); + } + if (doc["template-note-Idea"] === undefined) { + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "pink", system: true }); + noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); + doc["template-note-Idea"] = new PrefetchProxy(noteView); + } + if (doc["template-note-Topic"] === undefined) { + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "lightblue", system: true }); + noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); + doc["template-note-Topic"] = new PrefetchProxy(noteView); + } + if (doc["template-note-Todo"] === undefined) { + const noteView = Docs.Create.TextDocument("", { + title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", + layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true + }); + noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); + doc["template-note-Todo"] = new PrefetchProxy(noteView); + } + const taskStatusValues = [ + { title: "todo", _backgroundColor: "blue", color: "white", system: true }, + { title: "in progress", _backgroundColor: "yellow", color: "black", system: true }, + { title: "completed", _backgroundColor: "green", color: "white", system: true } + ]; + if (doc.fieldTypes === undefined) { + doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true }); + DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues); + } + + if (doc["template-notes"] === undefined) { + doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc], + { title: "Note Layouts", _height: 75, system: true })); + } else { + const curNoteTypes = Cast(doc["template-notes"], Doc, null); + const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc]; + DocListCastAsync(curNoteTypes.data).then(async curNotes => { + curNotes && await Promise.all(curNotes); + requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype)); + }); + } + + return doc["template-notes"] as Doc; + } + + // creates Note templates, and initial "user" templates + static setupDocTemplates(doc: Doc) { + const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc); + const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); + const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); + if (doc.templateDocs === undefined) { + doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { + title: "template layouts", _xPadding: 0, system: true, + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) + })); + } + } + + // setup templates for different document types when they are iconified from Document Decorations + static setupDefaultIconTemplates(doc: Doc) { + if (doc["template-icon-view"] === undefined) { + const iconView = Docs.Create.LabelDocument({ + title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + }); + // Docs.Create.TextDocument("", { + // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + // }); + // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); + iconView.isTemplateDoc = makeTemplate(iconView); + doc["template-icon-view"] = new PrefetchProxy(iconView); + } + if (doc["template-icon-view-rtf"] === undefined) { + const iconRtfView = Docs.Create.LabelDocument({ + title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + }); + iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); + doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); + } + if (doc["template-icon-view-button"] === undefined) { + const iconBtnView = Docs.Create.FontIconDocument({ + title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30, + _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + }); + iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON); + doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView); + } + if (doc["template-icon-view-img"] === undefined) { + const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { + title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + }); + iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); + doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); + } + if (doc["template-icon-view-col"] === undefined) { + const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); + iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); + doc["template-icon-view-col"] = new PrefetchProxy(iconColView); + } + if (doc["template-icons"] === undefined) { + doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, + doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); + } else { + const templateIconsDoc = Cast(doc["template-icons"], Doc, null); + const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, + doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; + DocListCastAsync(templateIconsDoc.data).then(async curIcons => { + curIcons && await Promise.all(curIcons); + requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype)); + }); + } + return doc["template-icons"] as Doc; + } + + static creatorBtnDescriptors(doc: Doc): { + title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, + click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc + }[] { + if (doc.emptyPresentation === undefined) { + doc.emptyPresentation = Docs.Create.PresDocument(new List(), + { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyCollection === undefined) { + doc.emptyCollection = Docs.Create.FreeformDocument([], + { _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyPane === undefined) { + doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptySlide === undefined) { + const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) }); + Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); + FormattedTextBox.SelectOnLoad = textDoc[Id]; + doc.emptySlide = textDoc; + } + if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) { + const json = { + doc: { + type: "doc", + content: [ + { + type: "paragraph", attrs: {}, content: [{ + type: "dashField", + attrs: { fieldKey: "author", docid: "", hideKey: false }, + marks: [{ type: "strong" }] + }, { + type: "dashField", + attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, + marks: [{ type: "strong" }] + }] + }] + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { + title: "text", version: headerViewVersion, target: doc, _height: 70, _headerPointerEvents: "all", + _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true, + cloneFieldFilter: new List(["system"]) + }, "header"); + const headerBtnHgt = 10; + headerTemplate[DataSym].layout = + "" + + ` ` + + " " + + ` Metadata` + + ""; + + // "
" + + // " " + + // " " + + // "
"; + (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); + doc.emptyHeader = headerTemplate; + ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyComparison === undefined) { + doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true, cloneFieldFilter: new List(["system"]) }); + } + if (doc.emptyScript === undefined) { + doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyScreenshot === undefined) { + doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) }); + } + if (doc.emptyWall === undefined) { + doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) }); + (doc.emptyWall as Doc).videoWall = true; + } + if (doc.emptyAudio === undefined) { + doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyNote === undefined) { + doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyImage === undefined) { + doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true }); + } + if (doc.emptyButton === undefined) { + doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List(["system"]) }); + ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0; + } + if (doc.emptyWebpage === undefined) { + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, isTemplateDoc: true, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List(["system"]) }); + } + if (doc.activeMobileMenu === undefined) { + this.setupActiveMobileMenu(doc); + } + return [ + { toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, }, + { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, + { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc, noviceMode: true }, + { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc }, + { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, + { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc, noviceMode: true }, + { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, + { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, + { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc }, + // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, + { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc }, + { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, + { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc }, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, + ]; + + } + + // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools + static async setupCreatorButtons(doc: Doc) { + let alreadyCreatedButtons: string[] = []; + const dragCreatorSet = await Cast(doc.myItemCreators, Doc, null); + if (dragCreatorSet) { + const dragCreators = await Cast(dragCreatorSet.data, listSpec(Doc)); + if (dragCreators) { + const dragDocs = await Promise.all(dragCreators); + alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); + } + } + const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); + const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ + _nativeWidth: 50, _nativeHeight: 50, _width: 30, _height: 25, + icon, + title, + toolTip, + btnType: ButtonType.ClickButton, + ignoreClick, + _dropAction: "alias", + onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, + onClick: click ? ScriptField.MakeScript(click) : undefined, + backgroundColor, + _hideContextMenu: true, + _removeDropProperties: new List(["_stayInCollection"]), + _stayInCollection: true, + dragFactory, + clickFactory, + hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, + system: true, + })); + + if (dragCreatorSet === undefined) { + doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { + title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, + _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true + })); + } else { + creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); + } + return doc.myItemCreators as Doc; + } + + static async menuBtnDescriptions(doc: Doc) { + return [ + { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, + { title: "My Files", target: Cast(doc.myFilesystem, Doc, null), icon: "file", click: 'selectMainMenu(self)' }, + { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' }, + { title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, + { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, + { title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + // { title: "Filter", target: Cast(doc.currentFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' }, + { title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, + // { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' }, + // { title: "Settings", target: undefined as any, icon: "cog", click: 'selectMainMenu(self)' }, + { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)' }, + ]; + } + + static setupSearchPanel(doc: Doc) { + if (doc.mySearchPanelDoc === undefined) { + doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({ + _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true, + childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "sidebar search stack", system: true + })) as any as Doc; + } + } + static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { + if (doc.menuStack === undefined) { + await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing + const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) => + Docs.Create.FontIconDocument({ + icon, + btnType: ButtonType.MenuButton, + _stayInCollection: true, + _hideContextMenu: true, + system: true, + dontUndo: true, + title, + target, + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + _width: 60, + _height: 60, + watchedDocuments, + onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) + })); + // hack -- last button is assumed to be the userDoc + menuBtns[menuBtns.length - 1].hidden = ComputedField.MakeFunction("IsNoviceMode()"); + + doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { + title: "menuItemPanel", + childDropAction: "alias", + _chromeHidden: true, + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + ignoreClick: true, + _gridGap: 0, + _yMargin: 0, + _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true + })); + } + // this resets all sidebar buttons to being deactivated + PromiseValue(Cast(doc.menuStack, Doc)).then(stack => { + stack && PromiseValue(stack.data).then(btns => { + DocListCastAsync(btns).then(bts => bts?.forEach(btn => { + btn.dontUndo = true; + btn.system = true; + if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files + btn.target = Doc.UserDoc().myFilesystem; + btn.title = "My Files"; + } + })); + }); + }); + return doc.menuStack as Doc; + } + + + // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu + static setupActiveMobileMenu(doc: Doc) { + if (doc.activeMobileMenu === undefined) { + doc.activeMobileMenu = this.setupMobileMenu(); + } + return doc.activeMobileMenu as Doc; + } + + // Sets up mobileMenu stacking document + static setupMobileMenu() { + const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { + _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true, + })); + return menu; + } + + // SEts up mobile buttons for inside mobile menu + static setupMobileButtons(doc?: Doc, buttons?: string[]) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ + { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " }, + { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, + { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." }, + { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." }, + { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." }, + { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." } + ]; + // returns a list of mobile buttons + return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => + this.mobileButton({ + title: data.title, + _lockedPosition: true, + onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, + backgroundColor: data.backgroundColor, system: true + }, + [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", btnType: ButtonType.ClickButton, system: true }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) + ); + } + + // sets up the main document for the mobile button + static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { + ...opts, + _removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, + borderRounding: "5px", boxShadow: "0 0", system: true + }) as any as Doc + + // sets up the text container for the information contained within the mobile button + static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { + ...opts, + _removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, + backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true + }) as any as Doc + + // Sets up the title of the button + static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { + ...opts, + title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", system: true + }) as any as Doc + + // Sets up the description of the button + static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { + ...opts, + title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true + }) as any as Doc + + + static setupThumbButtons(doc: Doc) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" }, + { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" }, + { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange" }, + { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" }, + { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" }, + ]; + return docProtoData.map(data => Docs.Create.FontIconDocument({ + _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, + _dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, + clipboard: data.clipboard, + onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, + backgroundColor: data.backgroundColor, + _removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, system: true + })); + } + + static setupThumbDoc(userDoc: Doc) { + if (!userDoc.thumbDoc) { + const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { + _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons", + _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true + }); + thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { + _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true + }); + userDoc.thumbDoc = thumbDoc; + } + return Cast(userDoc.thumbDoc, Doc); + } + + static setupMobileInkingDoc(userDoc: Doc) { + return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true }); + } + + static setupMobileUploadDoc(userDoc: Doc) { + // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" }) + const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", { + title: "Upload Images From the Web", _lockedPosition: true, system: true + }); + const uploadDoc = Docs.Create.StackingDocument([], { + title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeHidden: true, + }); + return Docs.Create.StackingDocument([webDoc, uploadDoc], { + _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeHidden: true, + }); + } + + static setupLibrary(userDoc: Doc) { + return CurrentUserUtils.setupDashboards(userDoc); + } + + // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. + // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) + static async setupToolsBtnPanel(doc: Doc) { + // setup a masonry view of all he creators + const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); + const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); + + doc["tabs-button-tools"] = undefined; + + if (doc.myCreators === undefined) { + doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { + title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, + _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, + })); + } + // setup a color picker + if (doc.myColorPicker === undefined) { + const color = Docs.Create.ColorDocument({ + title: "color picker", ignoreClick: true, _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true + }); + doc.myColorPicker = new PrefetchProxy(color); + } + + if (doc.myTools === undefined) { + const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { + title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, + system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", + })) as any as Doc; + + doc.myTools = toolsStack; + } + } + + static async setupDashboards(doc: Doc) { + // setup dashboards library item + await doc.myDashboards; + if (doc.myDashboards === undefined) { + doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true + })); + const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); + (doc.myDashboards as any as Doc).contextMenuScripts = new List([newDashboard!]); + (doc.myDashboards as any as Doc).contextMenuLabels = new List(["Create New Dashboard"]); + } + return doc.myDashboards as any as Doc; + } + + static async setupPresentations(doc: Doc) { + await doc.myPresentations; + if (doc.myPresentations === undefined) { + doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "My Trails", _showTitle: "title", _height: 100, + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true + })); + const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); + (doc.myPresentations as any as Doc).contextMenuScripts = new List([newPresentations!]); + (doc.myPresentations as any as Doc).contextMenuLabels = new List(["Create New Presentation"]); + const presentations = doc.myPresentations as any as Doc; + } + return doc.myPresentations as any as Doc; + } + + static async setupFilesystem(doc: Doc) { + await doc.myFilesystem; + if (doc.myFilesystem === undefined) { + doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); + doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true }); + doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileRoot as Doc, doc.myFileOrphans as Doc], { + title: "My Documents", _showTitle: "title", _height: 100, + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true + })); + } + return doc.myFilesystem as any as Doc; + } + + static setupRecentlyClosedDocs(doc: Doc) { + // setup Recently Closed library item + if (doc.myRecentlyClosedDocs === undefined) { + doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "Recently Closed", _showTitle: "title", treeViewShowClearButton: true, childHideLinkButton: true, + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true + })); + const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); + (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List([clearAll!]); + (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List(["Clear All"]); + } + } + static setupFilterDocs(doc: Doc) { + // setup Filter item + if (doc.currentFilter === undefined) { + doc.currentFilter = Docs.Create.FilterDocument({ + title: "unnamed filter", _height: 150, + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true + }); + const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); + (doc.currentFilter as Doc).contextMenuScripts = new List([clearAll!]); + (doc.currentFilter as Doc).contextMenuLabels = new List(["Clear All"]); + (doc.currentFilter as Doc).filterBoolean = "AND"; + } + } + + static setupUserDoc(doc: Doc) { + if (doc.myUserDoc === undefined) { + doc.treeViewOpen = true; + doc.treeViewExpandedView = "fields"; + doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true + })) as any as Doc; + } + } + + static setupSidebarContainer(doc: Doc) { + if (doc.sidebar === undefined) { + const sidebarContainer = new Doc(); + sidebarContainer.system = true; + doc.sidebar = new PrefetchProxy(sidebarContainer); + } + return doc.sidebar as Doc; + } + + // setup the list of sidebar mode buttons which determine what is displayed in the sidebar + static async setupSidebarButtons(doc: Doc) { + CurrentUserUtils.setupSidebarContainer(doc); + await CurrentUserUtils.setupToolsBtnPanel(doc); + CurrentUserUtils.setupImportSidebar(doc); + CurrentUserUtils.setupDashboards(doc); + CurrentUserUtils.setupPresentations(doc); + CurrentUserUtils.setupFilesystem(doc); + CurrentUserUtils.setupRecentlyClosedDocs(doc); + // CurrentUserUtils.setupFilterDocs(doc); + CurrentUserUtils.setupUserDoc(doc); + } + + static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { + ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _width: 100, boxShadow: "0 0", _forceActive: true, + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), + _lockedPosition: true, linearViewIsExpanded: true, system: true + })) as any as Doc + + static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ + ...opts, _dropAction: "alias", _removeDropProperties: new List(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true + })) as any as Doc + + /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window + static setupDockedButtons(doc: Doc) { + if (doc["dockedBtn-undo"] === undefined) { + doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), btnType: ButtonType.ClickButton, dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "Undo", icon: "undo-alt", system: true, canClick: 'canUndo' }); + } + if (doc["dockedBtn-redo"] === undefined) { + doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), btnType: ButtonType.ClickButton, dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "Redo", icon: "redo-alt", system: true, canClick: 'canRedo' }); + } + if (doc.dockedBtns === undefined) { + doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true, linearViewExpandable: true, _height: 42 }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); + } + (doc["dockedBtn-undo"] as Doc).dontUndo = true; + (doc["dockedBtn-redo"] as Doc).dontUndo = true; + } + + static textTools(doc: Doc) { + return [ + { + title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", + "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], + scriptDoc: Doc.UserDoc(), toggle: 'userDoc._fontFamily' + }, + { + title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', + scriptDoc: Doc.UserDoc(), + toggle: 'userDoc._boldActive' + }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._italicsActive' }, + { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._underlineActive' }, + // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()', toggle: 'userDoc._underlineActive' }, + // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()', toggle: 'userDoc._underlineActive' }, + // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()', toggle: 'userDoc._underlineActive' }, + { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._highlightColor' }, + { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "fill-drip", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._textColor' }, + // { title: "Link", tooltip: "Link", btnType: ButtonType.DropdownButton, icon: "link", click: '', ignoreClick: true }, + ]; + } + + static async contextMenuBtnDescriptions(doc: Doc) { + return [ + // { title: "Perspective", tooltip: "Change document's perspective", type: "btn", btnType: ButtonType.DropdownButton, ignoreClick: true, icon: "desktop", click: '' }, + { + title: "Perspective", toolTip: "Perspective", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, + CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, + CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, + CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, + CollectionViewType.Grid], + scriptDoc: 'selectedDoc', + toggle: 'selectedDoc._viewType' + }, + { + title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, scriptDoc: 'selectedDoc', + docColorBtn: 'selectedDoc.backgroundColor', width: 60, ignoreClick: true, icon: "fill-drip", + canClick: 'numSelected > 0' + }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', toggle: 'selectedDoc.z', canClick: 'numSelected > 0' }, + { title: "Text Tools", type: "TextMenu", icon: "font" }, + // { title: "Ink Tools", type: "LinearMenu", icon: "pen-nib" }, + // { title: "GFX Tools", type: "LinearMenu", icon: "shapes" }, + // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, + ]; + } + + // Default context menu buttons + static async setupContextMenuButtons(doc: Doc) { + const docList: Doc[] = []; + + const contextMenuBtns = (await CurrentUserUtils.contextMenuBtnDescriptions(doc)).map(({ title, width, toolTip, ignoreClick, icon, type, btnType, click, toggle, scriptDoc, canClick, docColorBtn }) => { + const textDocList: Doc[] = []; + if (type === "TextMenu") { + const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, toggle, scriptDoc, userColorBtn }) => { + textDocList.push(Docs.Create.FontIconDocument({ + _nativeWidth: width ? width : 25, + _nativeHeight: 25, + _width: width ? width : 25, + _height: 25, + icon, + toolTip, + userColorBtn, + // testToggle: toggle ? ScriptField.MakeScript(toggle, { this: scriptDoc, scriptContext: "any" }) : undefined, + // toggle: toggle, + btnType: btnType, + btnList: new List(list), + ignoreClick: ignoreClick, + _stayInCollection: true, + _hideContextMenu: true, + system: true, + dontUndo: true, + title, + backgroundColor: "black", + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + })); + }); + docList.push(CurrentUserUtils.blist({ ignoreClick: true, linearViewExpandable: true, _height: 30, backgroundColor: "transparent" }, textDocList)); + } else { + docList.push(Docs.Create.FontIconDocument({ + _nativeWidth: width ? width : 30, + _nativeHeight: 30, + _width: width ? width : 30, + _height: 30, + icon, + toolTip, + // testToggle: toggle ? ScriptField.MakeScript(toggle, { scriptContext: "any" }) : undefined, + // toggle: toggle, + docColorBtn, + canClick: canClick, + btnType: btnType, + ignoreClick: ignoreClick, + _stayInCollection: true, + _hideContextMenu: true, + system: true, + dontUndo: true, + title, + backgroundColor: "black", + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + })); + } + }); + + if (doc.contextMenuBtns === undefined) { + doc.contextMenuBtns = CurrentUserUtils.blist({ title: "menu buttons", ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList); + } + } + + // sets up the default set of documents to be shown in the Overlay layer + static setupOverlays(doc: Doc) { + if (doc.myOverlayDocs === undefined) { + doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); + } + } + + // the initial presentation Doc to use + static setupDefaultPresentation(doc: Doc) { + if (doc["template-presentation"] === undefined) { + doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ + title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _fitWidth: true, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true + })); + } + } + + // Sharing sidebar is where shared documents are contained + static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { + if (doc.myLinkDatabase === undefined) { + let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); + if (!linkDocs) { + linkDocs = new Doc(linkDatabaseId, true); + (linkDocs as Doc).author = Doc.CurrentUserEmail; + (linkDocs as Doc).data = new List([]); + (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add; + } + doc.myLinkDatabase = new PrefetchProxy(linkDocs); + } + if (doc.mySharedDocs === undefined) { + let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); + if (!sharedDocs) { + sharedDocs = Docs.Create.StackingDocument([], { + title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add, + _chromeHidden: true, boxShadow: "0 0", + }, sharingDocumentId + "outer", sharingDocumentId); + (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Add; + } + if (sharedDocs instanceof Doc) { + Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; + } + doc.mySharedDocs = new PrefetchProxy(sharedDocs); + } + } + + // Import sidebar is where shared documents are contained + static setupImportSidebar(doc: Doc) { + if (doc.myImportDocs === undefined) { + doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { + title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, + childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true, _chromeHidden: true, + })); + } + if (doc.myImportPanel === undefined) { + const uploads = Cast(doc.myImportDocs, Doc, null); + const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), btnType: ButtonType.ClickButton, toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true }); + doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, _showTitle: "title", ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true, boxShadow: "0 0" })); + } + } + + static setupClickEditorTemplates(doc: Doc) { + if (doc["clickFuncs-child"] === undefined) { + // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target + const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( + "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ", + { thisContainer: Doc.name }), { + title: "Click to open in target", _width: 300, _height: 200, + targetScriptKey: "onChildClick", system: true + }); + + const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( + "openOnRight(self.doubleClickView)", + {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); + + doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); + } + // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. + PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); + + if (doc.clickFuncs === undefined) { + const onClick = Docs.Create.ScriptingDocument(undefined, { + title: "onClick", "onClick-rawScript": "console.log('click')", + isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true + }, "onClick"); + const onChildClick = Docs.Create.ScriptingDocument(undefined, { + title: "onChildClick", "onChildClick-rawScript": "console.log('child click')", + isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true + }, "onChildClick"); + const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { + title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", + isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true + }, "onDoubleClick"); + const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, { + title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')", + isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true + }, "onChildDoubleClick"); + const onCheckedClick = Docs.Create.ScriptingDocument(undefined, { + title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", + "onCheckedClick-params": new List(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, + isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true + }, "onCheckedClick"); + doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true }); + } + PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); + + return doc.clickFuncs as Doc; + } + + static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { + if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); + const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]), + async () => { + const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; + SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); + }, { fireImmediately: true }); + // Document properties on load + doc.system = true; + doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; + doc.title = Doc.CurrentUserEmail; + doc._raiseWhenDragged = true; + doc._showLabel = false; + doc._showMenuLabel = true; + doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); + doc.activeInkWidth = StrCast(doc.activeInkWidth, "1"); + doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); + doc.activeFillColor = StrCast(doc.activeFillColor, ""); + doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); + doc.activeArrowEnd = StrCast(doc.activeArrowEnd, ""); + doc.activeDash = StrCast(doc.activeDash, "0"); + doc.fontSize = StrCast(doc.fontSize, "12px"); + doc.fontFamily = StrCast(doc.fontFamily, "Arial"); + doc.fontColor = StrCast(doc.fontColor, "black"); + doc.fontHighlight = StrCast(doc.fontHighlight, ""); + doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, true); + doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white"); + doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); + doc.noviceMode = BoolCast(doc.noviceMode, true); + doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // + doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // + Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); + doc.savedFilters = new List(); + doc.filterDocCount = 0; + this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon + this.setupDocTemplates(doc); // sets up the template menu of templates + this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile + this.setupSearchPanel(doc); + this.setupOverlays(doc); // documents in overlay layer + this.setupDockedButtons(doc); // the bottom bar of font icons + this.setupContextMenuButtons(doc); //buttons for context menu + await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels + await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId); + if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); + + setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered + + // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet + // doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); + // doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); + + // uncomment this to setup a default note style that uses the custom header layout + // PromiseValue(doc.emptyHeader).then(factory => { + // if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) { + // const deleg = Doc.delegateDragFactory(factory as Doc); + // deleg.title = "header"; + // doc.defaultTextLayout = new PrefetchProxy(deleg); + // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg); + // } + // }); + setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); + doc.fieldInfos = await Docs.setupFieldInfos(); + return doc; + } + + public static async loadCurrentUser() { + return rp.get(Utils.prepend("/getCurrentUser")).then(async response => { + if (response) { + const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response); + Doc.CurrentUserEmail = result.email; + resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts")); + DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email); + result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";"))); + return result; + } else { + throw new Error("There should be a user! Why does Dash think there isn't one?"); + } + }); + } + + public static async loadUserDocument(id: string) { + this.curr_id = id; + await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { + const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); + if (userDocumentId !== "guest") { + return DocServer.GetRefField(userDocumentId).then(async field => { + Docs.newAccount = !(field instanceof Doc); + await Docs.Prototypes.initialize(); + const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; + const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); + (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager + const a1 = await Cast(link?.anchor1, Doc, null); + const a2 = await Cast(link?.anchor2, Doc, null); + }); + return updated; + }); + } else { + throw new Error("There should be a user id! Why does Dash think there isn't one?"); + } + }); + } + + public static _urlState: HistoryUtil.DocUrl; + + public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => { + CurrentUserUtils.MainDocId = doc[Id]; + + if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard + !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); + userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); + } + const state = CurrentUserUtils._urlState; + if (state.sharing === true && !userDoc) { + DocServer.Control.makeReadOnly(); + } else { + fromHistory || HistoryUtil.pushState({ + type: "doc", + docId: doc[Id], + readonly: state.readonly, + nro: state.nro, + sharing: false, + }); + if (state.readonly === true || state.readonly === null) { + DocServer.Control.makeReadOnly(); + } else if (state.safe) { + if (!state.nro) { + DocServer.Control.makeReadOnly(); + } + CollectionView.SetSafeMode(true); + } else if (state.nro || state.nro === null || state.readonly === false) { + } else if (doc.readOnly) { + DocServer.Control.makeReadOnly(); + } else { + DocServer.Control.makeEditable(); + } + } + + return true; + } + + public static importDocument = () => { + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.accept = ".zip, application/pdf, video/*, image/*, audio/*"; + input.onchange = async _e => { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + const file = input.files && input.files[0]; + if (file && file.type === 'application/zip') { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = Docs.newAccount ? undefined : await DocServer.GetRefField(json); + if (doc instanceof Doc) { + setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => + docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. + } + } + } else if (input.files && input.files.length !== 0) { + const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null); + const disposer = OverlayView.ShowSpinner(); + DocListCastAsync(importDocs.data).then(async list => { + const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); + if (results.length !== input.files?.length) { + alert("Error uploading files - possibly due to unsupported file types"); + } + list?.splice(0, 0, ...results); + disposer(); + }); + } else { + console.log("No file selected"); + } + }; + input.click(); + } + + public static async snapshotDashboard(userDoc: Doc) { + const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); + Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); + CurrentUserUtils.openDashboard(userDoc, copy); + } + + public static createNewDashboard = async (userDoc: Doc, id?: string) => { + const myPresentations = await userDoc.myPresentations as Doc; + const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); + const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; + const dashboardCount = DocListCast(dashboards.data).length + 1; + const emptyPane = Cast(userDoc.emptyPane, Doc, null); + emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; + const freeformOptions: DocumentOptions = { + x: 0, + y: 400, + _width: 1500, + _height: 1000, + _fitWidth: true, + title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, + }; + const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); + Doc.AddDocToList(myPresentations, "data", presentation); + userDoc.activePresentation = presentation; + const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`); + const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); + const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); + const createDashboard = ScriptField.MakeScript(`createNewDashboard()`); + dashboardDoc.contextMenuScripts = new List([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!]); + dashboardDoc.contextMenuLabels = new List(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]); + + Doc.AddDocToList(dashboards, "data", dashboardDoc); + CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + } + + public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number) { + const tbox = Docs.Create.TextDocument("", { + _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, + _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), + _fontFamily: StrCast(Doc.UserDoc().fontFamily), title + }); + const template = Doc.UserDoc().defaultTextLayout; + if (template instanceof Doc) { + tbox._width = NumCast(template._width); + tbox.layoutKey = "layout_" + StrCast(template.title); + Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; + } + return tbox; + } + + public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); } + public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); } + public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } + public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } + public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } + public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); } + public static get OverlayDocs() { return DocListCast((Doc.UserDoc().myOverlayDocs as Doc)?.data); } + public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } + @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } +} + +Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { + const copy = Doc.copyDragFactory(dragFactory); + if (copy) { + CollectionDockingView.AddSplit(copy, "right"); + const view = DocumentManager.Instance.getFirstDocumentView(copy); + view && SelectionManager.SelectView(view, false); + } +}); +Scripting.addGlobal(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, + "is Dash in novice mode"); +Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, + "creates a snapshot copy of a dashboard"); +Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, + "creates a new dashboard when called"); +Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, + "creates a new presentation when called"); +Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, + "returns all the links to the document or its annotations", "(doc: any)"); +Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); }, + "imports files from device directly into the import sidebar"); \ No newline at end of file diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss deleted file mode 100644 index 46e40489b..000000000 --- a/src/client/views/collections/CollectionLinearView.scss +++ /dev/null @@ -1,139 +0,0 @@ -@import "../global/globalCssVariables"; -@import "../_nodeModuleOverrides"; - -.collectionLinearView-outer { - overflow: visible; - height: 100%; - pointer-events: none; - - .collectionLinearView { - display: flex; - height: 100%; - align-items: center; - - >span { - background: $dark-gray; - color: $white; - border-radius: 18px; - margin-right: 6px; - cursor: pointer; - } - - .bottomPopup-background { - background: $medium-blue; - display: flex; - border-radius: 10px; - height: 35; - transform: translate3d(6px, 0px, 0px); - align-content: center; - justify-content: center; - align-items: center; - } - - .bottomPopup-text { - color: $white; - display: inline; - white-space: nowrap; - padding-left: 8px; - padding-right: 20px; - vertical-align: middle; - font-size: 12.5px; - } - - .bottomPopup-descriptions { - cursor:pointer; - display: inline; - white-space: nowrap; - padding-left: 8px; - padding-right: 8px; - vertical-align: middle; - background-color: $light-gray; - border-radius: 3px; - color: black; - margin-right: 5px; - } - - .bottomPopup-exit { - cursor:pointer; - display: inline; - white-space: nowrap; - margin-right: 10px; - padding-left: 8px; - padding-right: 8px; - vertical-align: middle; - background-color: $close-red; - border-radius: 3px; - color: black; - } - - >label { - margin-top: "auto"; - margin-bottom: "auto"; - background: $dark-gray; - color: $white; - display: inline-block; - border-radius: 18px; - font-size: 12.5px; - width: 18px; - height: 18px; - margin-top: auto; - margin-bottom: auto; - margin-right: 3px; - cursor: pointer; - transition: transform 0.2s; - } - - label p { - padding-left: 5px; - } - - label:hover { - background: $medium-gray; - transform: scale(1.15); - } - - >input { - display: none; - } - - >input:not(:checked)~.collectionLinearView-content { - display: none; - } - - >input:checked~label { - transform: rotate(45deg); - transition: transform 0.5s; - cursor: pointer; - } - - .collectionLinearView-content { - display: flex; - opacity: 1; - position: relative; - margin-top: auto; - - .collectionLinearView-docBtn, - .collectionLinearView-docBtn-scalable { - position: relative; - margin: auto; - margin-left: 3px; - transform-origin: center 80%; - } - - .collectionLinearView-docBtn-scalable:hover { - transform: scale(1.15); - } - - .collectionLinearView-round-button { - width: 18px; - height: 18px; - border-radius: 18px; - font-size: 15px; - } - - .collectionLinearView-round-button:hover { - transform: scale(1.15); - } - } - } -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx deleted file mode 100644 index 52c836556..000000000 --- a/src/client/views/collections/CollectionLinearView.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { Tooltip } from '@material-ui/core'; -import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; -import { documentSchema } from '../../../fields/documentSchemas'; -import { Id } from '../../../fields/FieldSymbols'; -import { makeInterface } from '../../../fields/Schema'; -import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, Utils } from '../../../Utils'; -import { DragManager } from '../../util/DragManager'; -import { Transform } from '../../util/Transform'; -import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; -import { DocumentView } from '../nodes/DocumentView'; -import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup'; -import { StyleProp } from '../StyleProvider'; -import "./CollectionLinearView.scss"; -import { CollectionSubView } from './CollectionSubView'; -import { CollectionViewType } from './CollectionView'; - - -type LinearDocument = makeInterface<[typeof documentSchema,]>; -const LinearDocument = makeInterface(documentSchema); - -@observer -export class CollectionLinearView extends CollectionSubView(LinearDocument) { - @observable public addMenuToggle = React.createRef(); - @observable private _selectedIndex = -1; - private _dropDisposer?: DragManager.DragDropDisposer; - private _widthDisposer?: IReactionDisposer; - private _selectedDisposer?: IReactionDisposer; - - componentWillUnmount() { - this._dropDisposer?.(); - this._widthDisposer?.(); - this._selectedDisposer?.(); - this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log)); - } - - componentDidMount() { - this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10), - width => this.childDocs.length && (this.layoutDoc._width = width), - { fireImmediately: true } - ); - - this._selectedDisposer = reaction( - () => NumCast(this.layoutDoc.selectedIndex), - (i) => runInAction(() => { - this._selectedIndex = i; - let selected: any = undefined; - this.childLayoutPairs.map(async (pair, ind) => { - const isSelected = this._selectedIndex === ind; - if (isSelected) { - selected = pair; - } - else { - ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); - } - }); - if (selected && selected.layout) { - ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); - } - }), - { fireImmediately: true } - ); - } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view - this._dropDisposer && this._dropDisposer(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); - } - } - - dimension = () => NumCast(this.rootDoc._height); // 2 * the padding - getTransform = (ele: React.RefObject) => () => { - if (!ele.current) return Transform.Identity(); - const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current); - return new Transform(-translateX, -translateY, 1); - } - - @action - exitLongLinks = () => { - if (DocumentLinksButton.StartLink) { - if (DocumentLinksButton.StartLink.Document) { - action((e: React.PointerEvent) => { - Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc); - }); - } - } - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - } - - @action - changeDescriptionSetting = () => { - if (LinkDescriptionPopup.showDescriptions) { - if (LinkDescriptionPopup.showDescriptions === "ON") { - LinkDescriptionPopup.showDescriptions = "OFF"; - LinkDescriptionPopup.descriptionPopup = false; - } else { - LinkDescriptionPopup.showDescriptions = "ON"; - } - } else { - LinkDescriptionPopup.showDescriptions = "OFF"; - LinkDescriptionPopup.descriptionPopup = false; - } - } - - render() { - const guid = Utils.GenerateGuid(); - const flexDir: any = StrCast(this.Document.flexDirection); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - - const menuOpener = ; - - return
-
-
{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "Close menu" : "Open menu"}
} placement="top"> - {menuOpener} -
- this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> - -
- {this.childLayoutPairs.map((pair, ind) => { - const nested = pair.layout._viewType === CollectionViewType.Linear; - const dref = React.createRef(); - const scalable = pair.layout.onClick || pair.layout.onDragStart; - return
- -
; - })} -
- {DocumentLinksButton.StartLink ? e.stopPropagation()} > - - Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'} - - -
{"Toggle description pop-up"}
} placement="top"> - - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"} - -
- -
Exit linking mode
} placement="top"> - - Stop - -
- -
: null} -
-
; - } -} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/index.ts b/src/client/views/collections/collectionFreeForm/index.ts new file mode 100644 index 000000000..702dc8d42 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/index.ts @@ -0,0 +1,7 @@ +export * from "./CollectionFreeFormLayoutEngines"; +export * from "./CollectionFreeFormLinkView"; +export * from "./CollectionFreeFormLinksView"; +export * from "./CollectionFreeFormRemoteCursors"; +export * from "./CollectionFreeFormView"; +export * from "./MarqueeOptionsMenu"; +export * from "./MarqueeView"; \ No newline at end of file diff --git a/src/client/views/collections/collectionGrid/index.ts b/src/client/views/collections/collectionGrid/index.ts new file mode 100644 index 000000000..be5d5667a --- /dev/null +++ b/src/client/views/collections/collectionGrid/index.ts @@ -0,0 +1,2 @@ +export * from "./Grid"; +export * from "./CollectionGridView"; \ No newline at end of file diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss new file mode 100644 index 000000000..a10d43917 --- /dev/null +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -0,0 +1,139 @@ +@import "../../global/globalCssVariables"; +@import "../../_nodeModuleOverrides"; + +.collectionLinearView-outer { + overflow: visible; + height: 100%; + pointer-events: none; + + .collectionLinearView { + display: flex; + height: 100%; + align-items: center; + + >span { + background: $dark-gray; + color: $white; + border-radius: 18px; + margin-right: 6px; + cursor: pointer; + } + + .bottomPopup-background { + background: $medium-blue; + display: flex; + border-radius: 10px; + height: 35; + transform: translate3d(6px, 0px, 0px); + align-content: center; + justify-content: center; + align-items: center; + } + + .bottomPopup-text { + color: $white; + display: inline; + white-space: nowrap; + padding-left: 8px; + padding-right: 20px; + vertical-align: middle; + font-size: 12.5px; + } + + .bottomPopup-descriptions { + cursor:pointer; + display: inline; + white-space: nowrap; + padding-left: 8px; + padding-right: 8px; + vertical-align: middle; + background-color: $light-gray; + border-radius: 3px; + color: black; + margin-right: 5px; + } + + .bottomPopup-exit { + cursor:pointer; + display: inline; + white-space: nowrap; + margin-right: 10px; + padding-left: 8px; + padding-right: 8px; + vertical-align: middle; + background-color: $close-red; + border-radius: 3px; + color: black; + } + + >label { + margin-top: "auto"; + margin-bottom: "auto"; + background: $dark-gray; + color: $white; + display: inline-block; + border-radius: 18px; + font-size: 12.5px; + width: 18px; + height: 18px; + margin-top: auto; + margin-bottom: auto; + margin-right: 3px; + cursor: pointer; + transition: transform 0.2s; + } + + label p { + padding-left: 5px; + } + + label:hover { + background: $medium-gray; + transform: scale(1.15); + } + + >input { + display: none; + } + + >input:not(:checked)~.collectionLinearView-content { + display: none; + } + + >input:checked~label { + transform: rotate(45deg); + transition: transform 0.5s; + cursor: pointer; + } + + .collectionLinearView-content { + display: flex; + opacity: 1; + position: relative; + margin-top: auto; + + .collectionLinearView-docBtn, + .collectionLinearView-docBtn-scalable { + position: relative; + margin: auto; + margin-left: 3px; + transform-origin: center 80%; + } + + .collectionLinearView-docBtn-scalable:hover { + transform: scale(1.15); + } + + .collectionLinearView-round-button { + width: 18px; + height: 18px; + border-radius: 18px; + font-size: 15px; + } + + .collectionLinearView-round-button:hover { + transform: scale(1.15); + } + } + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx new file mode 100644 index 000000000..8e2ba2275 --- /dev/null +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx @@ -0,0 +1,192 @@ +import { Tooltip } from '@material-ui/core'; +import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { makeInterface } from '../../../../fields/Schema'; +import { documentSchema } from '../../../../fields/documentSchemas'; +import { CollectionSubView } from '../CollectionSubView'; +import { DragManager } from '../../../util/DragManager'; +import { ScriptCast, NumCast, StrCast, BoolCast } from '../../../../fields/Types'; +import { HeightSym, Doc, WidthSym } from '../../../../fields/Doc'; +import { Utils, returnFalse, returnTrue, emptyFunction, returnEmptyDoclist } from '../../../../Utils'; +import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; +import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; +import { StyleProp } from '../../StyleProvider'; +import { CollectionViewType } from '../CollectionView'; +import { Id } from '../../../../fields/FieldSymbols'; +import { DocumentView } from '../../nodes/DocumentView'; +import { Transform } from '../../../util/Transform'; + + +type LinearDocument = makeInterface<[typeof documentSchema,]>; +const LinearDocument = makeInterface(documentSchema); + +@observer +export class CollectionLinearView extends CollectionSubView(LinearDocument) { + @observable public addMenuToggle = React.createRef(); + @observable private _selectedIndex = -1; + private _dropDisposer?: DragManager.DragDropDisposer; + private _widthDisposer?: IReactionDisposer; + private _selectedDisposer?: IReactionDisposer; + + componentWillUnmount() { + this._dropDisposer?.(); + this._widthDisposer?.(); + this._selectedDisposer?.(); + this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log)); + } + + componentDidMount() { + this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10), + width => this.childDocs.length && (this.layoutDoc._width = width), + { fireImmediately: true } + ); + + this._selectedDisposer = reaction( + () => NumCast(this.layoutDoc.selectedIndex), + (i) => runInAction(() => { + this._selectedIndex = i; + let selected: any = undefined; + this.childLayoutPairs.map(async (pair, ind) => { + const isSelected = this._selectedIndex === ind; + if (isSelected) { + selected = pair; + } + else { + ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); + } + }); + if (selected && selected.layout) { + ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); + } + }), + { fireImmediately: true } + ); + } + protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + this._dropDisposer && this._dropDisposer(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } + } + + dimension = () => NumCast(this.rootDoc._height); // 2 * the padding + getTransform = (ele: React.RefObject) => () => { + if (!ele.current) return Transform.Identity(); + const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current); + return new Transform(-translateX, -translateY, 1); + } + + @action + exitLongLinks = () => { + if (DocumentLinksButton.StartLink) { + if (DocumentLinksButton.StartLink.Document) { + action((e: React.PointerEvent) => { + Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc); + }); + } + } + DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; + } + + @action + changeDescriptionSetting = () => { + if (LinkDescriptionPopup.showDescriptions) { + if (LinkDescriptionPopup.showDescriptions === "ON") { + LinkDescriptionPopup.showDescriptions = "OFF"; + LinkDescriptionPopup.descriptionPopup = false; + } else { + LinkDescriptionPopup.showDescriptions = "ON"; + } + } else { + LinkDescriptionPopup.showDescriptions = "OFF"; + LinkDescriptionPopup.descriptionPopup = false; + } + } + + render() { + const guid = Utils.GenerateGuid(); + const flexDir: any = StrCast(this.Document.flexDirection); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + + const menuOpener = ; + + return
+
+
{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "Close menu" : "Open menu"}
} placement="top"> + {menuOpener} +
+ this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> + +
+ {this.childLayoutPairs.map((pair, ind) => { + const nested = pair.layout._viewType === CollectionViewType.Linear; + const dref = React.createRef(); + const scalable = pair.layout.onClick || pair.layout.onDragStart; + return
+ +
; + })} +
+ {DocumentLinksButton.StartLink ? e.stopPropagation()} > + + Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'} + + +
{"Toggle description pop-up"}
} placement="top"> + + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"} + +
+ +
Exit linking mode
} placement="top"> + + Stop + +
+ +
: null} +
+
; + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionLinearView/index.ts b/src/client/views/collections/collectionLinearView/index.ts new file mode 100644 index 000000000..ff73e14ae --- /dev/null +++ b/src/client/views/collections/collectionLinearView/index.ts @@ -0,0 +1 @@ +export * from "./CollectionLinearView"; \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 3d2cdf5a4..544125ede 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -23,7 +23,7 @@ import "./DocumentView.scss"; import { EquationBox } from "./EquationBox"; import { FieldView, FieldViewProps } from "./FieldView"; import { FilterBox } from "./FilterBox"; -import { FontIconBox } from "./FontIconBox"; +import { FontIconBox } from "./button/FontIconBox"; import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox"; import { FunctionPlotBox } from "./FunctionPlotBox"; import { ImageBox } from "./ImageBox"; diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss deleted file mode 100644 index 718af2c16..000000000 --- a/src/client/views/nodes/FontIconBox.scss +++ /dev/null @@ -1,103 +0,0 @@ -@import "../global/globalCssVariables"; - -.fontIconBox-label { - color: $white; - margin-right: 4px; - margin-top: 1px; - position: relative; - text-align: center; - font-size: 7px; - letter-spacing: normal; - background-color: inherit; - border-radius: 8px; - margin-top: -8px; - padding: 0; - width: 100%; -} - -.fontIconBadge-container { - position:absolute; - z-index: 1000; - top: 12px; - - .fontIconBadge { - position: absolute; - top: -10px; - right: -10px; - color: $white; - background: $pink; - font-weight: 300; - border-radius: 100%; - width: 25px; - height: 25px; - text-align: center; - padding-top: 4px; - font-size: 12px; - } -} - -.menuButton-circle, -.menuButton-round { - border-radius: 100%; - background-color: $dark-gray; - padding: 0; - - .fontIconBox-label { - //margin-left: -10px; // button padding is 10px; - bottom: 0; - position: absolute; - } - - &:hover { - background-color: $light-gray; - } -} - -.menuButton-square { - padding-top: 3px; - padding-bottom: 3px; - background-color: $dark-gray; - - .fontIconBox-label { - border-radius: 0px; - margin-top: 0px; - border-radius: "inherit"; - } -} - -.menuButton, -.menuButton-circle, -.menuButton-round, -.menuButton-square { - margin-left: -5%; - width: 110%; - height: 100%; - pointer-events: all; - touch-action: none; - - .menuButton-wrap { - touch-action: none; - border-radius: 8px; - width: 100%; - } - - .menuButton-icon-square { - width: auto; - height: 29px; - padding: 4px; - } - - svg { - width: 95% !important; - height: 95%; - } -} -.menuButton-round { - width: 100%; - svg { - width: 50% !important; - height: 50%; - position: relative; - bottom: 2px; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx deleted file mode 100644 index 0d415e238..000000000 --- a/src/client/views/nodes/FontIconBox.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { AclPrivate, Doc, DocListCast } from '../../../fields/Doc'; -import { createSchema, makeInterface } from '../../../fields/Schema'; -import { ScriptField } from '../../../fields/ScriptField'; -import { Cast, StrCast } from '../../../fields/Types'; -import { GetEffectiveAcl } from '../../../fields/util'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; -import { DragManager } from '../../util/DragManager'; -import { ContextMenu } from '../ContextMenu'; -import { DocComponent } from '../DocComponent'; -import { StyleProp } from '../StyleProvider'; -import { FieldView, FieldViewProps } from './FieldView'; -import './FontIconBox.scss'; -import { Colors } from '../global/globalEnums'; -const FontIconSchema = createSchema({ - icon: "string", -}); - -type FontIconDocument = makeInterface<[typeof FontIconSchema]>; -const FontIconDocument = makeInterface(FontIconSchema); -@observer -export class FontIconBox extends DocComponent(FontIconDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } - showTemplate = (): void => { - const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this.props.addDocTab(dragFactory, "add:right"); - } - dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); }; - useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); }; - - specificContextMenu = (): void => { - if (!Doc.UserDoc().noviceMode) { - 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" }); - } - } - - render() { - const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle"); - const icon = StrCast(this.dataDoc.icon, "user") as any; - const presSize = shape === 'round' ? 25 : 30; - const presTrailsIcon = ; - const button = ; - return !this.layoutDoc.toolTip ? button : - {StrCast(this.layoutDoc.toolTip)}
}> - {button} - ; - } -} - -interface FontIconBadgeProps { - collection: Doc | undefined; -} - -@observer -export class FontIconBadge extends React.Component { - _notifsRef = React.createRef(); - - onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const dragData = new DragManager.DocumentDragData([this.props.collection!]); - DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - return true; - }, - returnFalse, emptyFunction, false); - } - - render() { - if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read - return
-
0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} -
-
; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx new file mode 100644 index 000000000..3be723052 --- /dev/null +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -0,0 +1,37 @@ + +import { AclPrivate, Doc, DocListCast } from '../../../../fields/Doc'; +import { GetEffectiveAcl } from '../../../../fields/util'; +import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils"; +import { DragManager } from '../../../util/DragManager'; +import './FontIconBox.scss'; +import { observer } from 'mobx-react'; +import React from 'react'; +interface FontIconBadgeProps { + collection: Doc | undefined; +} + +@observer +export class FontIconBadge extends React.Component { + _notifsRef = React.createRef(); + + onPointerDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, + (e: PointerEvent) => { + const dragData = new DragManager.DocumentDragData([this.props.collection!]); + DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + return true; + }, + returnFalse, emptyFunction, false); + } + + render() { + if (!(this.props.collection instanceof Doc)) return (null); + const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read + return
+
0 ? { "display": "initial" } : { "display": "none" }} + onPointerDown={this.onPointerDown} > + {length} +
+
; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss new file mode 100644 index 000000000..d4f1b9379 --- /dev/null +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -0,0 +1,103 @@ +@import "../../global/globalCssVariables"; + +.fontIconBox-label { + color: $white; + margin-right: 4px; + margin-top: 1px; + position: relative; + text-align: center; + font-size: 7px; + letter-spacing: normal; + background-color: inherit; + border-radius: 8px; + margin-top: -8px; + padding: 0; + width: 100%; +} + +.fontIconBadge-container { + position:absolute; + z-index: 1000; + top: 12px; + + .fontIconBadge { + position: absolute; + top: -10px; + right: -10px; + color: $white; + background: $pink; + font-weight: 300; + border-radius: 100%; + width: 25px; + height: 25px; + text-align: center; + padding-top: 4px; + font-size: 12px; + } +} + +.menuButton-circle, +.menuButton-round { + border-radius: 100%; + background-color: $dark-gray; + padding: 0; + + .fontIconBox-label { + //margin-left: -10px; // button padding is 10px; + bottom: 0; + position: absolute; + } + + &:hover { + background-color: $light-gray; + } +} + +.menuButton-square { + padding-top: 3px; + padding-bottom: 3px; + background-color: $dark-gray; + + .fontIconBox-label { + border-radius: 0px; + margin-top: 0px; + border-radius: "inherit"; + } +} + +.menuButton, +.menuButton-circle, +.menuButton-round, +.menuButton-square { + margin-left: -5%; + width: 110%; + height: 100%; + pointer-events: all; + touch-action: none; + + .menuButton-wrap { + touch-action: none; + border-radius: 8px; + width: 100%; + } + + .menuButton-icon-square { + width: auto; + height: 29px; + padding: 4px; + } + + svg { + width: 95% !important; + height: 95%; + } +} +.menuButton-round { + width: 100%; + svg { + width: 50% !important; + height: 50%; + position: relative; + bottom: 2px; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx new file mode 100644 index 000000000..f98d01dfc --- /dev/null +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -0,0 +1,295 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import React from 'react'; +import { ColorState, SketchPicker } from 'react-color'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { createSchema, makeInterface } from '../../../../fields/Schema'; +import { ScriptField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { ColorScheme } from '../../../util/SettingsManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { ContextMenu } from '../../ContextMenu'; +import { DocComponent } from '../../DocComponent'; +import { StyleProp } from '../../StyleProvider'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FontIconBadge } from './FontIconBadge'; +import './FontIconBox.scss'; +const FontIconSchema = createSchema({ + icon: "string", +}); + +export enum ButtonType { + MenuButton = "menuBtn", + DropdownList = "drpdownList", + DropdownButton = "drpdownBtn", + ClickButton = "clickBtn", + DoubleButton = "dblBtn", + ToggleButton = "tglBtn", + ColorButton = "colorBtn" +} + +type FontIconDocument = makeInterface<[typeof FontIconSchema]>; +const FontIconDocument = makeInterface(FontIconSchema); +@observer +export class FontIconBox extends DocComponent(FontIconDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } + showTemplate = (): void => { + const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); + dragFactory && this.props.addDocTab(dragFactory, "add:right"); + } + dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); }; + useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); }; + + specificContextMenu = (): void => { + if (!Doc.UserDoc().noviceMode) { + 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" }); + } + } + + + + // Determining UI Specs + @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); + @observable private color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + @observable private backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + @observable private icon = StrCast(this.dataDoc.icon, "user") as any; + @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen); + @observable private dropdownDirection: string = StrCast(this.rootDoc.dropDownDirection); + @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList); + @observable private activeFont: string = StrCast(Doc.UserDoc()._fontFamily); + @observable private type = StrCast(this.rootDoc.btnType); + + /** + * Types of buttons in dash: + * - Main menu button (LHS) + * - Tool button + * - Expandable button (CollectionLinearView) + * - Button inside of CollectionLinearView vs. outside of CollectionLinearView + * - Action button + * - Dropdown button + * - Color button + * - Dropdown list + **/ + + /** + * Dropdown button + */ + @computed get dropdownButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + return ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}> + + {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} +
+ +
+ {this.rootDoc.dropDownOpen ? +
+ {/* DROPDOWN BOX CONTENTS */} +
: null} +
+ ); + } + + @undoBatch setColor = action((color: ColorState, docColor?: string, userColor?: string) => { + console.log(docColor, userColor); + if (docColor) { + const numSelected = SelectionManager.Views().length; + const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; + eval(docColor); + } + else if (userColor) { + const userDoc = Doc.UserDoc(); + eval(userColor); + } + }); + + + + /** + * Default + */ + @computed get defaultButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + return ( +
+
+ + {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} + +
+
+ ) + } + + + render() { + // Variables called through eval (from button) + const canUndo: boolean = UndoManager.CanUndo(); + const canRedo: boolean = UndoManager.CanRedo(); + const numSelected = SelectionManager.Views().length; + const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; + const userDoc = Doc.UserDoc(); + + // Toggle and canClick properties as determined from the variable passed into the button doc + // const toggle = this.rootDoc.toggle ? ScriptCast(this.rootDoc.toggle) : undefined; + const canClick: boolean = this.rootDoc.canClick ? eval(StrCast(this.rootDoc.canClick)) : false; + // if (toggle) { + // console.log(StrCast(this.rootDoc.title), toggle); + // toggle.script.run(); + // } + const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark; + + const active: string = StrCast(this.rootDoc.dropDownOpen); + const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : +
+ {this.label} +
; + const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : +
+ {this.label} +
; + const dropdownCaret =
+ +
; + const colorBox = (func: (color: ColorState) => void) => ; + const items = this.buttonList.map((value) => { + return
Doc.UserDoc()._fontFamily = value}> + {value} +
; + }); + + /** + * Menu Panel Button: menuBtn + * Dropdown Button: dropDownBtn + * doubleBtn + **/ + + // CODEDUMP: glr + // const presSize = type === ButtonType.MenuButton ? 30 : 25; + // const presTrailsIcon = ; + + // TODO:glr Add label of button type + let button = ( + <> + {this.defaultButton} + + ); + + switch (this.type) { + case ButtonType.DropdownButton: + button = ( + <> + {this.dropdownButton} + + ); + break; + case ButtonType.DropdownList: + button = ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}> + {/* {toggle} */} + {label} + {dropdownCaret} + {this.rootDoc.dropDownOpen ? + <> +
+ {items} +
+
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> + + : null} +
+ ); + break; + case ButtonType.ColorButton: + button = ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} + onPointerDown={e => e.stopPropagation()}> + + {label} + {dropdownCaret} + {this.rootDoc.dropDownOpen ? + <> +
e.stopPropagation()} + onClick={e => e.stopPropagation()} + style={{ left: 0 }}> + {colorBox((color) => this.setColor(color, StrCast(this.rootDoc.docColor), StrCast(this.rootDoc.userColor)))} +
+
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> + + : null} +
+ ); + break; + // case ButtonType.ToggleButton: + // button = ( + //
+ // + // {label} + //
+ // ); + // break; + case ButtonType.ClickButton: + button = ( +
+ + {label} +
+ ); + break; + case ButtonType.DoubleButton: + button = ( +
+ + {label} +
+ ); + break; + case ButtonType.MenuButton: + button = ( +
+ + {menuLabel} +
+ //
+ // + // {label} + //
+ ); + break; + default: + break; + } + + return !this.layoutDoc.toolTip ? button : + {StrCast(this.layoutDoc.toolTip)}
}> + {button} + ; + } +} -- cgit v1.2.3-70-g09d2 From c4b3d5943b83a001fff0b967f9b155ed07328a42 Mon Sep 17 00:00:00 2001 From: geireann Date: Tue, 17 Aug 2021 21:52:43 -0400 Subject: import cleanup --- src/client/views/linking/LinkPopup.tsx | 35 +++++++++++----------------------- src/client/views/search/SearchBox.tsx | 12 +++++++----- 2 files changed, 18 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index df469c53b..b5a29415f 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -1,30 +1,16 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, observable, runInAction } from 'mobx'; +import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, DocListCast } from '../../../fields/Doc'; -import { Cast, StrCast } from '../../../fields/Types'; -import { WebField } from '../../../fields/URLField'; -import { emptyFunction, setupMoveUpEvents, returnFalse, returnTrue, returnEmptyDoclist, returnEmptyFilter } from '../../../Utils'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager } from '../../util/DragManager'; -import { Hypothesis } from '../../util/HypothesisUtils'; -import { LinkManager } from '../../util/LinkManager'; -import { undoBatch } from '../../util/UndoManager'; -import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView'; -import { LinkDocPreview } from '../nodes/LinkDocPreview'; -import './LinkPopup.scss'; -import React = require("react"); +import { EditorView } from 'prosemirror-view'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { DefaultStyleProvider } from '../StyleProvider'; import { Transform } from '../../util/Transform'; -import { DocUtils } from '../../documents/Documents'; -import { SearchBox } from '../search/SearchBox'; -import { EditorView } from 'prosemirror-view'; +import { undoBatch } from '../../util/UndoManager'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { SearchBox } from '../search/SearchBox'; +import { DefaultStyleProvider } from '../StyleProvider'; +import './LinkPopup.scss'; +import React = require("react"); interface LinkPopupProps { showPopup: boolean; @@ -78,7 +64,8 @@ export class LinkPopup extends React.Component { */} - ; const SearchBoxDocument = makeInterface(documentSchema, searchSchema); -- cgit v1.2.3-70-g09d2 From 2e0174e16abab1c603bc45bf98903a89f0cbf84b Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Tue, 17 Aug 2021 23:17:36 -0400 Subject: in-progress link relationship color association created list fields for link relationships and their associated colors. Need to implement colors in front end --- src/client/documents/Documents.ts | 2 ++ src/client/util/LinkManager.ts | 13 +++++++++++++ src/client/views/linking/LinkEditor.tsx | 32 ++++++++++++++++++++++---------- src/client/views/linking/LinkMenu.tsx | 2 +- 4 files changed, 38 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 48886aa3b..7c7943501 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -270,6 +270,8 @@ export class DocumentOptions { useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered + linkRelationshipList?: List; // for storing different link relationships (when set by user in the link editor) + linkColorList?: List; // colors of links corresponding to specific link relationships } export namespace Docs { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 933b98a8c..c15e91df6 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -3,6 +3,7 @@ import { computedFn } from "mobx-utils"; import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { ProxyField } from "../../fields/Proxy"; +import { listSpec } from "../../fields/Schema"; import { BoolCast, Cast, PromiseValue, StrCast } from "../../fields/Types"; import { LightboxView } from "../views/LightboxView"; import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView"; @@ -33,6 +34,7 @@ export class LinkManager { static links: Doc[] = []; constructor() { LinkManager._instance = this; + this.createLinkrelationshipList(); setTimeout(() => { LinkManager.userLinkDBs = []; const addLinkToDoc = (link: Doc) => { @@ -95,6 +97,17 @@ export class LinkManager { }); } + + public createLinkrelationshipList = () => { + const linkRelationshipList = new List(); + const linkColorList = new List(); + Doc.UserDoc().linkRelationshipList = linkRelationshipList; + Doc.UserDoc().linkColorList = linkColorList; + console.log(Doc.UserDoc().linkRelationshipList); + console.log(Doc.UserDoc().linkColorList); + // + } + public addLink(linkDoc: Doc, checkExists = false) { if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) { Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc); diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index f74b422d3..22c3c5f56 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -3,11 +3,13 @@ import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../fields/Doc"; -import { DateCast, StrCast } from "../../../fields/Types"; +import { Cast, DateCast, StrCast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkEditor.scss'; import React = require("react"); +import { listSpec } from "../../../fields/Schema"; +import { List } from "../../../fields/List"; interface LinkEditorProps { @@ -39,6 +41,16 @@ export class LinkEditor extends React.Component { setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { LinkManager.currentLink.linkRelationship = value; + const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List; + const linkColorList = Doc.UserDoc().linkColorList as List; + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color + if (linkRelationshipList && !linkRelationshipList.includes(value)) { + linkRelationshipList.push(value); + const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; + linkColorList.push(randColor) + console.log(randColor) + console.log("linkRelList size: " + linkRelationshipList.length); + } this.relationshipButtonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.relationshipButtonColor = ""), 750); return true; @@ -70,7 +82,7 @@ export class LinkEditor extends React.Component { } onDown = () => this.setDescripValue(this.description); - onRelationshipDown = () => this.setRelationshipValue(this.description); + onRelationshipDown = () => this.setRelationshipValue(this.relationship); @action handleChange = (e: React.ChangeEvent) => { this.description = e.target.value; } @@ -149,35 +161,35 @@ export class LinkEditor extends React.Component {
this.changeFollowBehavior("default")}> Default -
+
this.changeFollowBehavior("add:left")}> Always open in new left pane -
+
this.changeFollowBehavior("add:right")}> Always open in new right pane -
+
this.changeFollowBehavior("replace:right")}> Always replace right tab -
+
this.changeFollowBehavior("replace:left")}> Always replace left tab -
+
this.changeFollowBehavior("fullScreen")}> Always open full screen -
+
this.changeFollowBehavior("add")}> Always open in a new tab -
+
this.changeFollowBehavior("replace")}> Replace Tab -
+
{this.props.linkDoc.linksToAnnotation ?
this.changeFollowBehavior("openExternal")}> diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 6fc860447..53fe3f682 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -41,7 +41,7 @@ export class LinkMenu extends React.Component { /** * maps each link to a JSX element to be rendered - * @param groups LinkManager containing info of all of the links + * @param groups containing info of all of the links * @returns list of link JSX elements if there at least one linked element */ renderAllGroups = (groups: Map>): Array => { -- cgit v1.2.3-70-g09d2 From 9aa0e5e812dfbc67b0542c6740d43209313a37d6 Mon Sep 17 00:00:00 2001 From: geireann Date: Tue, 17 Aug 2021 23:50:00 -0400 Subject: more updates --- src/client/documents/Documents.ts | 19 +++++++++---------- src/client/views/linking/LinkPopup.scss | 2 +- src/client/views/linking/LinkPopup.tsx | 6 ++++-- src/client/views/nodes/FieldView.tsx | 5 ++--- src/client/views/pdf/AnchorMenu.tsx | 25 ++++++++++++++++--------- src/client/views/search/SearchBox.tsx | 28 ++++++++++++++++++++-------- 6 files changed, 52 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 48886aa3b..b14a6f800 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,7 @@ import { action, runInAction } from "mobx"; -import { basename, extname } from "path"; +import { basename } from "path"; import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing, updateCachedAcls } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { HtmlField } from "../../fields/HtmlField"; import { InkField } from "../../fields/InkField"; @@ -13,20 +13,20 @@ import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; import { AudioField, ImageField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; -import { MessageStore } from "../../server/Message"; import { Upload } from "../../server/SharedMediaTypes"; import { OmitKeys, Utils } from "../../Utils"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { DocServer } from "../DocServer"; import { Networking } from "../Network"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { DocumentManager } from "../util/DocumentManager"; import { dropActionType } from "../util/DragManager"; import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; import { LinkManager } from "../util/LinkManager"; import { Scripting } from "../util/Scripting"; import { undoBatch, UndoManager } from "../util/UndoManager"; -import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; +import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { ContextMenu } from "../views/ContextMenu"; import { ContextMenuProps } from "../views/ContextMenuItem"; @@ -36,30 +36,29 @@ import { AudioBox } from "../views/nodes/AudioBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; import { DocFocusOptions } from "../views/nodes/DocumentView"; +import { EquationBox } from "../views/nodes/EquationBox"; +import { FieldViewProps } from "../views/nodes/FieldView"; import { FilterBox } from "../views/nodes/FilterBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; +import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { LabelBox } from "../views/nodes/LabelBox"; import { LinkBox } from "../views/nodes/LinkBox"; import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup"; import { PDFBox } from "../views/nodes/PDFBox"; -import { PresBox } from "../views/nodes/trails/PresBox"; import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; import { ScriptingBox } from "../views/nodes/ScriptingBox"; import { SliderBox } from "../views/nodes/SliderBox"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; +import { PresBox } from "../views/nodes/trails/PresBox"; +import { PresElementBox } from "../views/nodes/trails/PresElementBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; -import { PresElementBox } from "../views/nodes/trails/PresElementBox"; import { SearchBox } from "../views/search/SearchBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; -import { EquationBox } from "../views/nodes/EquationBox"; -import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { FieldViewProps } from "../views/nodes/FieldView"; const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss index 8ae65158d..60c9ebfcd 100644 --- a/src/client/views/linking/LinkPopup.scss +++ b/src/client/views/linking/LinkPopup.scss @@ -5,7 +5,7 @@ height: 200px; width: 200px; position: absolute; - padding: 15px; + // padding: 15px; border-radius: 3px; input { diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index b5a29415f..85fd91d9f 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -50,7 +50,7 @@ export class LinkPopup extends React.Component { const popupVisibility = this.props.showPopup ? "block" : "none"; return (
-
+ {/*
@@ -58,7 +58,7 @@ export class LinkPopup extends React.Component {

or

-
+
*/}
{/* { { @observable public Status: "marquee" | "annotation" | "" = ""; public OnClick: (e: PointerEvent) => void = unimplementedFunction; + public OnLink: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string, isPushpin: boolean) => Opt = (color: string, isPushpin: boolean) => undefined; public GetAnchor: (savedAnnotations?: ObservableMap) => Opt = () => undefined; @@ -65,7 +66,14 @@ export class AnchorMenu extends AntimodeMenu { componentDidMount() { this._disposer = reaction(() => SelectionManager.Views(), - selected => AnchorMenu.Instance.fadeOut(true)); + selected => { + AnchorMenu.Instance.fadeOut(true) + this._showLinkPopup = false; + }); + } + + componentWillUnmount() { + console.log("unmount"); } pointerDown = (e: React.PointerEvent) => { @@ -145,14 +153,13 @@ export class AnchorMenu extends AntimodeMenu { , - - //NOTE: link popup is currently incomplete - // {"Link selected text to document or URL"}
}> - // - // , - // + //NOTE: link popup is currently in progress + {"Link selected text to document or URL"}
}> + + , + ] : [ {"Remove Link Anchor"}
}> , - + ] : [ {"Remove Link Anchor"}
}>
} key="pin menu" placement="bottom"> - - ; const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; const prop = {propTitle}
} key="properties" placement="bottom"> - +
; - return this.getElement(!this.SelectedCollection ? [/*button*/] : - [, - prop, - /*button*/]); + // NEW BUTTONS + //dash col linear view buttons + const contMenuButtons = +
+ {this.contMenuButtons} + {prop} +
; + + return contMenuButtons; + + // const button = Pin Menu} key="pin menu" placement="bottom"> + // + // ; + + // OLD BUTTONS + // return this.getElement(!this.SelectedCollection ? [/*button*/] : + // [, + // prop, + // /*button*/]); } } diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss index a10d43917..db39e304b 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -5,12 +5,22 @@ overflow: visible; height: 100%; pointer-events: none; + // background-color: rgba(0, 0, 0, 0.2); + border-radius: 5px; + padding-left: 5px; + padding-right: 5px; + border-left: $standard-border; + border-right: $standard-border; .collectionLinearView { display: flex; height: 100%; align-items: center; + .collectionView{ + overflow: visible !important; + } + >span { background: $dark-gray; color: $white; @@ -67,29 +77,25 @@ } >label { - margin-top: "auto"; - margin-bottom: "auto"; background: $dark-gray; color: $white; - display: inline-block; + display: flex; border-radius: 18px; font-size: 12.5px; - width: 18px; - height: 18px; + font-weight:100; + width: fit-content; + height: 100%; margin-top: auto; margin-bottom: auto; margin-right: 3px; cursor: pointer; transition: transform 0.2s; - } - - label p { - padding-left: 5px; - } + align-items: center; + justify-content: center; - label:hover { - background: $medium-gray; - transform: scale(1.15); + &:hover { + background: $medium-gray; + } } >input { @@ -110,7 +116,6 @@ display: flex; opacity: 1; position: relative; - margin-top: auto; .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable { diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx index 8e2ba2275..e7970758a 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx @@ -2,20 +2,21 @@ import { Tooltip } from '@material-ui/core'; import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { makeInterface } from '../../../../fields/Schema'; +import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; import { documentSchema } from '../../../../fields/documentSchemas'; -import { CollectionSubView } from '../CollectionSubView'; +import { Id } from '../../../../fields/FieldSymbols'; +import { makeInterface } from '../../../../fields/Schema'; +import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, Utils } from '../../../../Utils'; import { DragManager } from '../../../util/DragManager'; -import { ScriptCast, NumCast, StrCast, BoolCast } from '../../../../fields/Types'; -import { HeightSym, Doc, WidthSym } from '../../../../fields/Doc'; -import { Utils, returnFalse, returnTrue, emptyFunction, returnEmptyDoclist } from '../../../../Utils'; +import { Transform } from '../../../util/Transform'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; +import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; import { StyleProp } from '../../StyleProvider'; -import { CollectionViewType } from '../CollectionView'; -import { Id } from '../../../../fields/FieldSymbols'; -import { DocumentView } from '../../nodes/DocumentView'; -import { Transform } from '../../../util/Transform'; +import "./CollectionLinearView.scss"; +import { CollectionSubView } from '.././CollectionSubView'; +import { CollectionViewType } from '.././CollectionView'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -108,33 +109,37 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { render() { const guid = Utils.GenerateGuid(); const flexDir: any = StrCast(this.Document.flexDirection); + const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const icon: string = StrCast(this.props.Document.icon); const menuOpener = ; return
-
{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "Close menu" : "Open menu"}
} placement="top"> + {!expandable ? (null) :
{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}
} placement="top"> {menuOpener} -
- this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> +
} + this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
{this.childLayoutPairs.map((pair, ind) => { const nested = pair.layout._viewType === CollectionViewType.Linear; const dref = React.createRef(); - const scalable = pair.layout.onClick || pair.layout.onDragStart; - return
{ LightboxView.SetCookie(StrCast(anchor["cookies-set"])); - // copying over VIEW fields immediately allows the view type to switch to create the right _componentView - Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => { - this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]); - }); + // copying over VIEW fields immediately allows the view type to switch to create the right _componentView + Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => { + this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]); + }); // after a timeout, the right _componentView should have been created, so call it to update its view spec values setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => @@ -764,7 +765,7 @@ export class DocumentViewInternal extends DocComponent console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); } - + if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. @@ -949,12 +950,13 @@ export class DocumentViewInternal extends DocComponent { TraceMobx(); const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); + const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return (
{!this.props.Document || !this.props.PanelWidth() ? (null) : (
; const FontIconDocument = makeInterface(FontIconSchema); @observer -export class FontIconBox extends DocComponent(FontIconDocument) { +export class FontIconBox extends DocComponent(FontIconDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); @@ -56,8 +59,6 @@ export class FontIconBox extends DocComponent( // Determining UI Specs @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); - @observable private color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - @observable private backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); @observable private icon = StrCast(this.dataDoc.icon, "user") as any; @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen); @observable private dropdownDirection: string = StrCast(this.rootDoc.dropDownDirection); @@ -82,16 +83,18 @@ export class FontIconBox extends DocComponent( */ @computed get dropdownButton() { const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); return (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}> - - {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} + + {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
}
- +
{this.rootDoc.dropDownOpen ?
( * Default */ @computed get defaultButton() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const active: string = StrCast(this.rootDoc.dropDownOpen); return (
- {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} - + {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} + {/* */}
) @@ -136,6 +141,8 @@ export class FontIconBox extends DocComponent( render() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); // Variables called through eval (from button) const canUndo: boolean = UndoManager.CanUndo(); const canRedo: boolean = UndoManager.CanRedo(); @@ -144,7 +151,7 @@ export class FontIconBox extends DocComponent( const userDoc = Doc.UserDoc(); // Toggle and canClick properties as determined from the variable passed into the button doc - // const toggle = this.rootDoc.toggle ? ScriptCast(this.rootDoc.toggle) : undefined; + const toggle = this.rootDoc.toggle ? ScriptCast(this.rootDoc.toggle) : undefined; const canClick: boolean = this.rootDoc.canClick ? eval(StrCast(this.rootDoc.canClick)) : false; // if (toggle) { // console.log(StrCast(this.rootDoc.title), toggle); @@ -154,11 +161,11 @@ export class FontIconBox extends DocComponent( const active: string = StrCast(this.rootDoc.dropDownOpen); const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : -
+
{this.label}
; const dropdownCaret =
( style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
; - const colorBox = (func: (color: ColorState) => void) => void) => ; @@ -207,9 +214,9 @@ export class FontIconBox extends DocComponent( case ButtonType.DropdownList: button = (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}> - {/* {toggle} */} + {toggle} {label} {dropdownCaret} {this.rootDoc.dropDownOpen ? @@ -227,10 +234,10 @@ export class FontIconBox extends DocComponent( case ButtonType.ColorButton: button = (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} onPointerDown={e => e.stopPropagation()}> - + {label} {dropdownCaret} {this.rootDoc.dropDownOpen ? @@ -247,40 +254,36 @@ export class FontIconBox extends DocComponent(
); break; - // case ButtonType.ToggleButton: - // button = ( - //
- // - // {label} - //
- // ); - // break; + case ButtonType.ToggleButton: + button = ( +
+ + {label} +
+ ); + break; case ButtonType.ClickButton: button = ( -
- +
+ {label}
); break; case ButtonType.DoubleButton: button = ( -
- +
+ {label}
); break; case ButtonType.MenuButton: button = ( -
- +
+ {menuLabel}
- //
- // - // {label} - //
); break; default: -- cgit v1.2.3-70-g09d2 From 94cfa66db4d667cd0dae9c6ddbe152cbff27819f Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Thu, 19 Aug 2021 11:24:54 -0400 Subject: updates --- package-lock.json | 5 +++-- src/client/util/CurrentUserUtils.ts | 17 +++++++++-------- src/client/views/nodes/button/FontIconBox.scss | 17 ++++++++++++++--- src/client/views/nodes/button/FontIconBox.tsx | 11 ++++++++++- 4 files changed, 36 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 59ae898bf..7810e3120 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7694,13 +7694,14 @@ "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { + "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", "tryit": "^1.0.1" }, "dependencies": { "image-size": { - "version": "git+https://github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1", - "from": "git+https://github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1" + "version": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "from": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" }, "isarray": { "version": "0.0.1", diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index cd4c217b5..d03ef4aca 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -70,14 +70,14 @@ export class CurrentUserUtils { [this.ficon({ ignoreClick: true, icon: "mobile", - btnType: ButtonType.ClickButton, + btnType: ButtonType.ToolButton, backgroundColor: "transparent" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); doc["template-mobile-button"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ClickButton, + dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton, }); } @@ -93,7 +93,7 @@ export class CurrentUserUtils { doc["template-button-slides"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card", - btnType: ButtonType.ClickButton + btnType: ButtonType.ToolButton }); } @@ -140,7 +140,7 @@ export class CurrentUserUtils { doc["template-button-link"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true, - btnType: ButtonType.ClickButton + btnType: ButtonType.ToolButton }); } @@ -172,7 +172,7 @@ export class CurrentUserUtils { doc["template-button-switch"] = CurrentUserUtils.ficon({ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true, - btnType: ButtonType.ClickButton + btnType: ButtonType.ToolButton }); } @@ -225,7 +225,7 @@ export class CurrentUserUtils { title: "detailView", icon: "window-maximize", system: true, - btnType: ButtonType.ClickButton, + btnType: ButtonType.ToolButton, }); } @@ -511,12 +511,13 @@ export class CurrentUserUtils { icon, title, toolTip, - btnType: ButtonType.ClickButton, + btnType: ButtonType.ToolButton, ignoreClick, _dropAction: "alias", onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, onClick: click ? ScriptField.MakeScript(click) : undefined, - backgroundColor, + backgroundColor: backgroundColor ? backgroundColor : Colors.DARK_GRAY, + color: Colors.WHITE, _hideContextMenu: true, _removeDropProperties: new List(["_stayInCollection"]), _stayInCollection: true, diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 46a499466..0c866988d 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -38,12 +38,24 @@ &.clickBtn { cursor: pointer; + width: 40px; } &.tglBtn { cursor: pointer; } + &.toolBtn { + cursor: pointer; + width: 40px; + border-radius: 100%; + + svg { + width: 60% !important; + height: 60%; + } + } + &.menuBtn { cursor: pointer; border-radius: 0px; @@ -109,7 +121,7 @@ } .list-item:hover { - background-color:lightgrey; + background-color: lightgrey; } } } @@ -201,5 +213,4 @@ // background:transparent; // position: fixed; // } -// } - +// } \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 2c6369e9f..9e7608dc3 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -27,7 +27,8 @@ export enum ButtonType { ClickButton = "clickBtn", DoubleButton = "dblBtn", ToggleButton = "tglBtn", - ColorButton = "colorBtn" + ColorButton = "colorBtn", + ToolButton = "toolBtn" } export interface ButtonProps extends FieldViewProps { @@ -254,6 +255,14 @@ export class FontIconBox extends DocComponent(Fon
); break; + case ButtonType.ToolButton: + button = ( +
+ + {label} +
+ ); + break; case ButtonType.ToggleButton: button = (
-- cgit v1.2.3-70-g09d2 From d5841cda5aa838cf02b26a7ffbcc2b1713a66f36 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 19 Aug 2021 17:43:33 -0400 Subject: menu nearing final updates --- src/client/documents/Documents.ts | 2 + src/client/util/CurrentUserUtils.ts | 115 +- src/client/util/tempCurrentUserUtils.ts | 1389 -------------------- src/client/views/DocumentButtonBar.tsx | 5 +- src/client/views/collections/CollectionMenu.tsx | 1 + .../collectionLinearView/CollectionLinearView.scss | 11 +- .../collectionLinearView/CollectionLinearView.tsx | 2 +- src/client/views/nodes/button/ButtonScripts.ts | 14 + src/client/views/nodes/button/FontIconBox.scss | 64 +- src/client/views/nodes/button/FontIconBox.tsx | 375 ++++-- 10 files changed, 414 insertions(+), 1564 deletions(-) delete mode 100644 src/client/util/tempCurrentUserUtils.ts create mode 100644 src/client/views/nodes/button/ButtonScripts.ts (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 817fbb9d6..f6b2e0736 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -223,11 +223,13 @@ export class DocumentOptions { docColorBtn?: string; userColorBtn?: string; canClick?: string; + script?: string; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded linearViewExpandable?: boolean; // can linear view be expanded linearViewToggleButton?: string; // button to open close linear view group + linearViewSubMenu?: boolean; layout_linkView?: Doc; // view template for a link document layout_keyValue?: string; // view tempalte for key value docs diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d03ef4aca..4ff0446ad 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -923,44 +923,58 @@ export class CurrentUserUtils { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - scriptDoc: Doc.UserDoc(), toggle: 'userDoc._fontFamily' + script: 'changeFont' }, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' }, + { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' }, + // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'}, + // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'}, + // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'}, + { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._highlightColor' }, + { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "fill-drip", ignoreClick: true, scriptDoc: Doc.UserDoc(), script:'console.log("test")', userColorBtn: 'userDoc._textColor' }, + ]; + } + + static inkTools(doc: Doc) { + return [ + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'togglePen()', script: 'togglePen' }, + { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'toggleHighlighter()', script: 'toggleHighlighter' }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'toggleCircle()', script: 'toggleCircle' }, + { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'toggleSquare()', script: 'toggleSquare' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'toggleLine()', script: 'toggleLine' }, + ]; + } + + static webTools(doc: Doc) { + return [ { - title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', - scriptDoc: Doc.UserDoc(), - toggle: 'userDoc._boldActive' + title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", + "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], + script: 'changeFont' }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._italicsActive' }, - { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._underlineActive' }, - // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()', toggle: 'userDoc._underlineActive' }, - // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()', toggle: 'userDoc._underlineActive' }, - // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()', toggle: 'userDoc._underlineActive' }, - { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._highlightColor' }, - { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "fill-drip", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._textColor' }, - // { title: "Link", tooltip: "Link", btnType: ButtonType.DropdownButton, icon: "link", click: '', ignoreClick: true }, ]; } static async contextMenuBtnDescriptions(doc: Doc) { return [ - // { title: "Perspective", tooltip: "Change document's perspective", type: "btn", btnType: ButtonType.DropdownButton, ignoreClick: true, icon: "desktop", click: '' }, { - title: "Perspective", toolTip: "Perspective", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, - CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, - CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, - CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid], - scriptDoc: 'selectedDoc', - toggle: 'selectedDoc._viewType' + CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, + CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, + CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, + CollectionViewType.Grid], + script: 'changeView', }, { - title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, scriptDoc: 'selectedDoc', - docColorBtn: 'selectedDoc.backgroundColor', width: 60, ignoreClick: true, icon: "fill-drip", - canClick: 'numSelected > 0' + title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, width: 60, ignoreClick: true, icon: "fill-drip", + script: "changeBackgroundColor" }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', toggle: 'selectedDoc.z', canClick: 'numSelected > 0' }, - { title: "Text Tools", type: "TextMenu", icon: "font" }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()' }, + { title: "Text", type: "TextMenu" }, + { title: "Ink & GFX", type: "InkMenu" }, // { title: "Ink Tools", type: "LinearMenu", icon: "pen-nib" }, // { title: "GFX Tools", type: "LinearMenu", icon: "shapes" }, // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, @@ -971,21 +985,44 @@ export class CurrentUserUtils { static async setupContextMenuButtons(doc: Doc) { const docList: Doc[] = []; - const contextMenuBtns = (await CurrentUserUtils.contextMenuBtnDescriptions(doc)).map(({ title, width, toolTip, ignoreClick, icon, type, btnType, click, toggle, scriptDoc, canClick, docColorBtn }) => { + const contextMenuBtns = (await CurrentUserUtils.contextMenuBtnDescriptions(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script }) => { const textDocList: Doc[] = []; if (type === "TextMenu") { - const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, toggle, scriptDoc, userColorBtn }) => { + const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, script, userColorBtn }) => { textDocList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 25, - _nativeHeight: 25, - _width: width ? width : 25, - _height: 25, + _nativeWidth: width ? width : 30, + _nativeHeight: 30, + _width: width ? width : 30, + _height: 30, icon, toolTip, userColorBtn, - - // testToggle: toggle ? ScriptField.MakeScript(toggle, { this: scriptDoc, scriptContext: "any" }) : undefined, - // toggle: toggle, + script, + btnType: btnType, + btnList: new List(list), + ignoreClick: ignoreClick, + _stayInCollection: true, + _hideContextMenu: true, + system: true, + dontUndo: true, + title, + backgroundColor: "black", + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined + })); + }); + docList.push(CurrentUserUtils.blist({ linearViewSubMenu: true, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, textDocList)); + } else if (type === "InkMenu") { + const inkBtns = (CurrentUserUtils.inkTools(doc)).map(({ title, toolTip, icon, btnType, click }) => { + textDocList.push(Docs.Create.FontIconDocument({ + _nativeWidth: width ? width : 30, + _nativeHeight: 30, + _width: width ? width : 30, + _height: 30, + icon, + toolTip, + script, btnType: btnType, btnList: new List(list), ignoreClick: ignoreClick, @@ -997,10 +1034,10 @@ export class CurrentUserUtils { backgroundColor: "black", _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined })); }); - docList.push(CurrentUserUtils.blist({ ignoreClick: true, linearViewExpandable: true, icon:"Text", _height: 30, backgroundColor: "transparent" }, textDocList)); + docList.push(CurrentUserUtils.blist({ linearViewSubMenu: true, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, textDocList)); } else { docList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 30, @@ -1011,9 +1048,9 @@ export class CurrentUserUtils { toolTip, // testToggle: toggle ? ScriptField.MakeScript(toggle, { scriptContext: "any" }) : undefined, // toggle: toggle, - docColorBtn, - canClick: canClick, + script, btnType: btnType, + btnList: new List(list), ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, @@ -1093,7 +1130,7 @@ export class CurrentUserUtils { } if (doc.myImportPanel === undefined) { const uploads = Cast(doc.myImportDocs, Doc, null); - const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true }); + const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", type: ButtonType.ToolButton, icon: "upload", system: true }); doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, _showTitle: "title", ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true, boxShadow: "0 0" })); } } diff --git a/src/client/util/tempCurrentUserUtils.ts b/src/client/util/tempCurrentUserUtils.ts deleted file mode 100644 index 3fba672e6..000000000 --- a/src/client/util/tempCurrentUserUtils.ts +++ /dev/null @@ -1,1389 +0,0 @@ -import { computed, observable, reaction, action } from "mobx"; -import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync, AclReadonly } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { List } from "../../fields/List"; -import { PrefetchProxy } from "../../fields/Proxy"; -import { RichTextField } from "../../fields/RichTextField"; -import { listSpec } from "../../fields/Schema"; -import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; -import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; -import { SharingPermissions } from "../../fields/util"; -import { Utils } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; -import { DocumentType } from "../documents/DocumentTypes"; -import { Networking } from "../Network"; -import { CollectionDockingView } from "../views/collections/collectionDocking/CollectionDockingView"; -import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; -import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; -import { MainView } from "../views/MainView"; -import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; -import { LabelBox } from "../views/nodes/LabelBox"; -import { OverlayView } from "../views/OverlayView"; -import { DocumentManager } from "./DocumentManager"; -import { DragManager } from "./DragManager"; -import { makeTemplate } from "./DropConverter"; -import { HistoryUtil } from "./History"; -import { LinkManager } from "./LinkManager"; -import { Scripting } from "./Scripting"; -import { SearchUtil } from "./SearchUtil"; -import { SelectionManager } from "./SelectionManager"; -import { SnappingManager } from "./SnappingManager"; -import { InkTool } from "../../fields/InkField"; -import { ButtonType } from "../views/nodes/FontIconBox"; - - -export let resolvedPorts: { server: number, socket: number }; -const headerViewVersion = "0.1"; -export class CurrentUserUtils { - private static curr_id: string; - //TODO tfs: these should be temporary... - private static mainDocId: string | undefined; - - public static get id() { return this.curr_id; } - public static get MainDocId() { return this.mainDocId; } - public static set MainDocId(id: string | undefined) { this.mainDocId = id; } - @computed public static get UserDocument() { return Doc.UserDoc(); } - - @observable public static GuestTarget: Doc | undefined; - @observable public static GuestDashboard: Doc | undefined; - @observable public static GuestMobile: Doc | undefined; - @observable public static propertiesWidth: number = 0; - - // sets up the default User Templates - slideView, headerView - static setupUserTemplateButtons(doc: Doc) { - // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location) - if (doc["template-mobile-button"] === undefined) { - const queryTemplate = this.mobileButton({ - title: "NEW MOBILE BUTTON", - onClick: undefined, - }, - [this.ficon({ - ignoreClick: true, - icon: "mobile", - btnType: ButtonType.ClickButton, - backgroundColor: "transparent" - }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); - doc["template-mobile-button"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", - btnType: ButtonType.ClickButton, - icon: "mobile" - }); - } - - if (doc["template-button-slides"] === undefined) { - const slideTemplate = Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ); - slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); - doc["template-button-slides"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", - icon: "address-card", - btnType: ButtonType.ClickButton - }); - } - - if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to. - const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created - linkTemplate.system = true; - Doc.GetProto(linkTemplate).layout = - "
" + - " " + - " " + - "
"; - (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView"); - - const rtf2 = { - doc: { - type: "doc", content: [ - { - type: "paragraph", - content: [{ - type: "dashField", - attrs: { - fieldKey: "src", - hideKey: false - } - }] - }, - { type: "paragraph" }, - { - type: "paragraph", - content: [{ - type: "dashField", - attrs: { - fieldKey: "dst", - hideKey: false - } - }] - }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - linkTemplate.header = new RichTextField(JSON.stringify(rtf2), ""); - - doc["template-button-link"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", - btnType: ButtonType.ClickButton, - icon: "window-maximize", system: true - }); - } - - if (doc["template-button-switch"] === undefined) { - const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; - - const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true }); - const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true }); - const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true }); - const labelTemplate = { - doc: { - type: "doc", content: [{ - type: "paragraph", - content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }] - }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS"); - Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'"); - // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'"); - // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); - Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); - // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); - const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true }); - box.isTemplateDoc = makeTemplate(box, true, "switch"); - - doc["template-button-switch"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", - btnType: ButtonType.ClickButton, - icon: "toggle-on", system: true - }); - } - - if (doc["template-button-detail"] === undefined) { - const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create; - - const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)"); - const carousel = CarouselDocument([], { - title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10, - onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F", system: true - }); - - const details = TextDocument("", { title: "details", _height: 200, _autoHeight: true, system: true }); - const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 75, _autoHeight: true, system: true }); - const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 150, _autoHeight: true, system: true }); - - const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"]; - const detailedTemplate = { - doc: { - type: "doc", content: buxtonFieldKeys.map(fieldKey => ({ - type: "paragraph", - content: [{ type: "dashField", attrs: { fieldKey } }] - })) - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); - - const shared = { _autoHeight: true, _xMargin: 0 }; - const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" }; - const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true }; - - const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); - descriptionWrapper._columnHeaders = new List([ - new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false), - new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true), - new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true), - ]); - const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeHidden: true, system: true }); - detailView.isTemplateDoc = makeTemplate(detailView); - - details.title = "Details"; - short.title = "A Short Description"; - long.title = "Long Description"; - - doc["template-button-detail"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(detailView) as any as Doc, title: "detailView", - btnType: ButtonType.ClickButton, - icon: "window-maximize", system: true - }); - } - - const requiredTypes = [ - doc["template-button-slides"] as Doc, - doc["template-mobile-button"] as Doc, - doc["template-button-detail"] as Doc, - doc["template-button-link"] as Doc, - //doc["template-button-switch"] as Doc] - ]; - if (doc["template-buttons"] === undefined) { - doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { - title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, - _stayInCollection: true, _hideContextMenu: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true - })); - } else { - const curButnTypes = Cast(doc["template-buttons"], Doc, null); - DocListCastAsync(curButnTypes.data).then(async curBtns => { - curBtns && await Promise.all(curBtns); - requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype)); - }); - } - return doc["template-buttons"] as Doc; - } - - // setup the different note type skins - static setupNoteTemplates(doc: Doc) { - if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); - doc["template-note-Note"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "pink", system: true }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); - doc["template-note-Idea"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "lightblue", system: true }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); - doc["template-note-Topic"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Todo"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", - layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); - doc["template-note-Todo"] = new PrefetchProxy(noteView); - } - const taskStatusValues = [ - { title: "todo", _backgroundColor: "blue", color: "white", system: true }, - { title: "in progress", _backgroundColor: "yellow", color: "black", system: true }, - { title: "completed", _backgroundColor: "green", color: "white", system: true } - ]; - if (doc.fieldTypes === undefined) { - doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true }); - DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues); - } - - if (doc["template-notes"] === undefined) { - doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc], - { title: "Note Layouts", _height: 75, system: true })); - } else { - const curNoteTypes = Cast(doc["template-notes"], Doc, null); - const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc]; - DocListCastAsync(curNoteTypes.data).then(async curNotes => { - curNotes && await Promise.all(curNotes); - requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype)); - }); - } - - return doc["template-notes"] as Doc; - } - - // creates Note templates, and initial "user" templates - static setupDocTemplates(doc: Doc) { - const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc); - const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); - if (doc.templateDocs === undefined) { - doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { - title: "template layouts", _xPadding: 0, system: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) - })); - } - } - - // setup templates for different document types when they are iconified from Document Decorations - static setupDefaultIconTemplates(doc: Doc) { - if (doc["template-icon-view"] === undefined) { - const iconView = Docs.Create.LabelDocument({ - title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true - }); - // Docs.Create.TextDocument("", { - // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") - // }); - // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); - iconView.isTemplateDoc = makeTemplate(iconView); - doc["template-icon-view"] = new PrefetchProxy(iconView); - } - if (doc["template-icon-view-rtf"] === undefined) { - const iconRtfView = Docs.Create.LabelDocument({ - title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true - }); - iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); - doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); - } - if (doc["template-icon-view-button"] === undefined) { - const iconBtnView = Docs.Create.FontIconDocument({ - title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30, - _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true - }); - iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON); - doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView); - } - if (doc["template-icon-view-img"] === undefined) { - const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true - }); - iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); - doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); - } - if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); - iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); - doc["template-icon-view-col"] = new PrefetchProxy(iconColView); - } - if (doc["template-icons"] === undefined) { - doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); - } else { - const templateIconsDoc = Cast(doc["template-icons"], Doc, null); - const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; - DocListCastAsync(templateIconsDoc.data).then(async curIcons => { - curIcons && await Promise.all(curIcons); - requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype)); - }); - } - return doc["template-icons"] as Doc; - } - - static creatorBtnDescriptors(doc: Doc): { - title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc - }[] { - if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument(new List(), - { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyPane === undefined) { - doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptySlide === undefined) { - const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) }); - Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); - FormattedTextBox.SelectOnLoad = textDoc[Id]; - doc.emptySlide = textDoc; - } - if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) { - const json = { - doc: { - type: "doc", - content: [ - { - type: "paragraph", attrs: {}, content: [{ - type: "dashField", - attrs: { fieldKey: "author", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }, { - type: "dashField", - attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }] - }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - title: "text", version: headerViewVersion, target: doc, _height: 70, _headerPointerEvents: "all", - _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true, - cloneFieldFilter: new List(["system"]) - }, "header"); - const headerBtnHgt = 10; - headerTemplate[DataSym].layout = - "" + - ` ` + - " " + - ` Metadata` + - ""; - - // "
" + - // " " + - // " " + - // "
"; - (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); - doc.emptyHeader = headerTemplate; - ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true, cloneFieldFilter: new List(["system"]) }); - } - if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) }); - } - if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) }); - (doc.emptyWall as Doc).videoWall = true; - } - if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyImage === undefined) { - doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true }); - } - if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, isTemplateDoc: true, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List(["system"]) }); - } - if (doc.activeMobileMenu === undefined) { - this.setupActiveMobileMenu(doc); - } - return [ - { toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, }, - { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, - { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, - { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc, noviceMode: true }, - { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc }, - { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, - { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc, noviceMode: true }, - { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, - { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, - { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc }, - // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, - { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc }, - { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, - { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - ]; - - } - - // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static async setupCreatorButtons(doc: Doc) { - let alreadyCreatedButtons: string[] = []; - const dragCreatorSet = await Cast(doc.myItemCreators, Doc, null); - if (dragCreatorSet) { - const dragCreators = await Cast(dragCreatorSet.data, listSpec(Doc)); - if (dragCreators) { - const dragDocs = await Promise.all(dragCreators); - alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); - } - } - const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); - const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ - _nativeWidth: 50, _nativeHeight: 50, _width: 30, _height: 25, - icon, - title, - toolTip, - btnType: ButtonType.ClickButton, - ignoreClick, - _dropAction: "alias", - onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, - onClick: click ? ScriptField.MakeScript(click) : undefined, - backgroundColor, - _hideContextMenu: true, - _removeDropProperties: new List(["_stayInCollection"]), - _stayInCollection: true, - dragFactory, - clickFactory, - hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, - system: true, - })); - - if (dragCreatorSet === undefined) { - doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { - title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true - })); - } else { - creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); - } - return doc.myItemCreators as Doc; - } - - static async menuBtnDescriptions(doc: Doc) { - return [ - { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, - { title: "My Files", target: Cast(doc.myFilesystem, Doc, null), icon: "file", click: 'selectMainMenu(self)' }, - { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' }, - { title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, - { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, - // { title: "Filter", target: Cast(doc.currentFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' }, - { title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, - // { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' }, - // { title: "Settings", target: undefined as any, icon: "cog", click: 'selectMainMenu(self)' }, - { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)' }, - ]; - } - - static setupSearchPanel(doc: Doc) { - if (doc.mySearchPanelDoc === undefined) { - doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({ - _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true, - childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "sidebar search stack", system: true - })) as any as Doc; - } - } - static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.menuStack === undefined) { - await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) => - Docs.Create.FontIconDocument({ - icon, - btnType: ButtonType.MenuButton, - _stayInCollection: true, - _hideContextMenu: true, - system: true, - dontUndo: true, - title, - target, - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - _width: 60, - _height: 60, - watchedDocuments, - onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) - })); - // hack -- last button is assumed to be the userDoc - menuBtns[menuBtns.length - 1].hidden = ComputedField.MakeFunction("IsNoviceMode()"); - - doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { - title: "menuItemPanel", - childDropAction: "alias", - _chromeHidden: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - ignoreClick: true, - _gridGap: 0, - _yMargin: 0, - _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true - })); - } - // this resets all sidebar buttons to being deactivated - PromiseValue(Cast(doc.menuStack, Doc)).then(stack => { - stack && PromiseValue(stack.data).then(btns => { - DocListCastAsync(btns).then(bts => bts?.forEach(btn => { - btn.dontUndo = true; - btn.system = true; - if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files - btn.target = Doc.UserDoc().myFilesystem; - btn.title = "My Files"; - } - })); - }); - }); - return doc.menuStack as Doc; - } - - - // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu - static setupActiveMobileMenu(doc: Doc) { - if (doc.activeMobileMenu === undefined) { - doc.activeMobileMenu = this.setupMobileMenu(); - } - return doc.activeMobileMenu as Doc; - } - - // Sets up mobileMenu stacking document - static setupMobileMenu() { - const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { - _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true, - })); - return menu; - } - - // SEts up mobile buttons for inside mobile menu - static setupMobileButtons(doc?: Doc, buttons?: string[]) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ - { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " }, - { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, - { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." }, - { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." }, - { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." }, - { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." } - ]; - // returns a list of mobile buttons - return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => - this.mobileButton({ - title: data.title, - _lockedPosition: true, - onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - backgroundColor: data.backgroundColor, system: true - }, - [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", btnType: ButtonType.ClickButton, system: true }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) - ); - } - - // sets up the main document for the mobile button - static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { - ...opts, - _removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, - borderRounding: "5px", boxShadow: "0 0", system: true - }) as any as Doc - - // sets up the text container for the information contained within the mobile button - static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { - ...opts, - _removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, - backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true - }) as any as Doc - - // Sets up the title of the button - static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { - ...opts, - title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", system: true - }) as any as Doc - - // Sets up the description of the button - static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { - ...opts, - title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true - }) as any as Doc - - - static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" }, - { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" }, - { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange" }, - { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" }, - { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" }, - ]; - return docProtoData.map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - _dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, - clipboard: data.clipboard, - onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, - backgroundColor: data.backgroundColor, - _removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, system: true - })); - } - - static setupThumbDoc(userDoc: Doc) { - if (!userDoc.thumbDoc) { - const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons", - _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true - }); - thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { - _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true - }); - userDoc.thumbDoc = thumbDoc; - } - return Cast(userDoc.thumbDoc, Doc); - } - - static setupMobileInkingDoc(userDoc: Doc) { - return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true }); - } - - static setupMobileUploadDoc(userDoc: Doc) { - // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" }) - const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", { - title: "Upload Images From the Web", _lockedPosition: true, system: true - }); - const uploadDoc = Docs.Create.StackingDocument([], { - title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeHidden: true, - }); - return Docs.Create.StackingDocument([webDoc, uploadDoc], { - _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeHidden: true, - }); - } - - static setupLibrary(userDoc: Doc) { - return CurrentUserUtils.setupDashboards(userDoc); - } - - // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. - // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static async setupToolsBtnPanel(doc: Doc) { - // setup a masonry view of all he creators - const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - - doc["tabs-button-tools"] = undefined; - - if (doc.myCreators === undefined) { - doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { - title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, - _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, - })); - } - // setup a color picker - if (doc.myColorPicker === undefined) { - const color = Docs.Create.ColorDocument({ - title: "color picker", ignoreClick: true, _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true - }); - doc.myColorPicker = new PrefetchProxy(color); - } - - if (doc.myTools === undefined) { - const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, - system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", - })) as any as Doc; - - doc.myTools = toolsStack; - } - } - - static async setupDashboards(doc: Doc) { - // setup dashboards library item - await doc.myDashboards; - if (doc.myDashboards === undefined) { - doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true - })); - const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); - (doc.myDashboards as any as Doc).contextMenuScripts = new List([newDashboard!]); - (doc.myDashboards as any as Doc).contextMenuLabels = new List(["Create New Dashboard"]); - } - return doc.myDashboards as any as Doc; - } - - static async setupPresentations(doc: Doc) { - await doc.myPresentations; - if (doc.myPresentations === undefined) { - doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Trails", _showTitle: "title", _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true - })); - const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); - (doc.myPresentations as any as Doc).contextMenuScripts = new List([newPresentations!]); - (doc.myPresentations as any as Doc).contextMenuLabels = new List(["Create New Presentation"]); - const presentations = doc.myPresentations as any as Doc; - } - return doc.myPresentations as any as Doc; - } - - static async setupFilesystem(doc: Doc) { - await doc.myFilesystem; - if (doc.myFilesystem === undefined) { - doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); - doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true }); - doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileRoot as Doc, doc.myFileOrphans as Doc], { - title: "My Documents", _showTitle: "title", _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true - })); - } - return doc.myFilesystem as any as Doc; - } - - static setupRecentlyClosedDocs(doc: Doc) { - // setup Recently Closed library item - if (doc.myRecentlyClosedDocs === undefined) { - doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "Recently Closed", _showTitle: "title", treeViewShowClearButton: true, childHideLinkButton: true, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true - })); - const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List([clearAll!]); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List(["Clear All"]); - } - } - static setupFilterDocs(doc: Doc) { - // setup Filter item - if (doc.currentFilter === undefined) { - doc.currentFilter = Docs.Create.FilterDocument({ - title: "unnamed filter", _height: 150, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true - }); - const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); - (doc.currentFilter as Doc).contextMenuScripts = new List([clearAll!]); - (doc.currentFilter as Doc).contextMenuLabels = new List(["Clear All"]); - (doc.currentFilter as Doc).filterBoolean = "AND"; - } - } - - static setupUserDoc(doc: Doc) { - if (doc.myUserDoc === undefined) { - doc.treeViewOpen = true; - doc.treeViewExpandedView = "fields"; - doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true - })) as any as Doc; - } - } - - static setupSidebarContainer(doc: Doc) { - if (doc.sidebar === undefined) { - const sidebarContainer = new Doc(); - sidebarContainer.system = true; - doc.sidebar = new PrefetchProxy(sidebarContainer); - } - return doc.sidebar as Doc; - } - - // setup the list of sidebar mode buttons which determine what is displayed in the sidebar - static async setupSidebarButtons(doc: Doc) { - CurrentUserUtils.setupSidebarContainer(doc); - await CurrentUserUtils.setupToolsBtnPanel(doc); - CurrentUserUtils.setupImportSidebar(doc); - CurrentUserUtils.setupDashboards(doc); - CurrentUserUtils.setupPresentations(doc); - CurrentUserUtils.setupFilesystem(doc); - CurrentUserUtils.setupRecentlyClosedDocs(doc); - // CurrentUserUtils.setupFilterDocs(doc); - CurrentUserUtils.setupUserDoc(doc); - } - - static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { - ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _width: 100, boxShadow: "0 0", _forceActive: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - _lockedPosition: true, linearViewIsExpanded: true, system: true - })) as any as Doc - - static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, _dropAction: "alias", _removeDropProperties: new List(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true - })) as any as Doc - - /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window - static setupDockedButtons(doc: Doc) { - if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), btnType: ButtonType.ClickButton, dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "Undo", icon: "undo-alt", system: true, canClick: 'canUndo' }); - } - if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), btnType: ButtonType.ClickButton, dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "Redo", icon: "redo-alt", system: true, canClick: 'canRedo' }); - } - if (doc.dockedBtns === undefined) { - doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true, linearViewExpandable: true, _height: 42 }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); - } - (doc["dockedBtn-undo"] as Doc).dontUndo = true; - (doc["dockedBtn-redo"] as Doc).dontUndo = true; - } - - static textTools(doc: Doc) { - return [ - { - title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", - "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - scriptDoc: Doc.UserDoc(), toggle: 'userDoc._fontFamily' - }, - { - title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', - scriptDoc: Doc.UserDoc(), - toggle: 'userDoc._boldActive' - }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._italicsActive' }, - { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', scriptDoc: Doc.UserDoc(), toggle: 'userDoc._underlineActive' }, - // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()', toggle: 'userDoc._underlineActive' }, - // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()', toggle: 'userDoc._underlineActive' }, - // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()', toggle: 'userDoc._underlineActive' }, - { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._highlightColor' }, - { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "fill-drip", ignoreClick: true, scriptDoc: Doc.UserDoc(), userColorBtn: 'userDoc._textColor' }, - // { title: "Link", tooltip: "Link", btnType: ButtonType.DropdownButton, icon: "link", click: '', ignoreClick: true }, - ]; - } - - static async contextMenuBtnDescriptions(doc: Doc) { - return [ - // { title: "Perspective", tooltip: "Change document's perspective", type: "btn", btnType: ButtonType.DropdownButton, ignoreClick: true, icon: "desktop", click: '' }, - { - title: "Perspective", toolTip: "Perspective", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, - CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, - CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, - CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid], - scriptDoc: 'selectedDoc', - toggle: 'selectedDoc._viewType' - }, - { - title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, scriptDoc: 'selectedDoc', - docColorBtn: 'selectedDoc.backgroundColor', width: 60, ignoreClick: true, icon: "fill-drip", - canClick: 'numSelected > 0' - }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', toggle: 'selectedDoc.z', canClick: 'numSelected > 0' }, - { title: "Text Tools", type: "TextMenu", icon: "font" }, - // { title: "Ink Tools", type: "LinearMenu", icon: "pen-nib" }, - // { title: "GFX Tools", type: "LinearMenu", icon: "shapes" }, - // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, - ]; - } - - // Default context menu buttons - static async setupContextMenuButtons(doc: Doc) { - const docList: Doc[] = []; - - const contextMenuBtns = (await CurrentUserUtils.contextMenuBtnDescriptions(doc)).map(({ title, width, toolTip, ignoreClick, icon, type, btnType, click, toggle, scriptDoc, canClick, docColorBtn }) => { - const textDocList: Doc[] = []; - if (type === "TextMenu") { - const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, toggle, scriptDoc, userColorBtn }) => { - textDocList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 25, - _nativeHeight: 25, - _width: width ? width : 25, - _height: 25, - icon, - toolTip, - userColorBtn, - // testToggle: toggle ? ScriptField.MakeScript(toggle, { this: scriptDoc, scriptContext: "any" }) : undefined, - // toggle: toggle, - btnType: btnType, - btnList: new List(list), - ignoreClick: ignoreClick, - _stayInCollection: true, - _hideContextMenu: true, - system: true, - dontUndo: true, - title, - backgroundColor: "black", - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined - })); - }); - docList.push(CurrentUserUtils.blist({ ignoreClick: true, linearViewExpandable: true, _height: 30, backgroundColor: "transparent" }, textDocList)); - } else { - docList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 30, - _nativeHeight: 30, - _width: width ? width : 30, - _height: 30, - icon, - toolTip, - // testToggle: toggle ? ScriptField.MakeScript(toggle, { scriptContext: "any" }) : undefined, - // toggle: toggle, - docColorBtn, - canClick: canClick, - btnType: btnType, - ignoreClick: ignoreClick, - _stayInCollection: true, - _hideContextMenu: true, - system: true, - dontUndo: true, - title, - backgroundColor: "black", - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined - })); - } - }); - - if (doc.contextMenuBtns === undefined) { - doc.contextMenuBtns = CurrentUserUtils.blist({ title: "menu buttons", ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList); - } - } - - // sets up the default set of documents to be shown in the Overlay layer - static setupOverlays(doc: Doc) { - if (doc.myOverlayDocs === undefined) { - doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); - } - } - - // the initial presentation Doc to use - static setupDefaultPresentation(doc: Doc) { - if (doc["template-presentation"] === undefined) { - doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _fitWidth: true, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true - })); - } - } - - // Sharing sidebar is where shared documents are contained - static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.myLinkDatabase === undefined) { - let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); - if (!linkDocs) { - linkDocs = new Doc(linkDatabaseId, true); - (linkDocs as Doc).author = Doc.CurrentUserEmail; - (linkDocs as Doc).data = new List([]); - (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add; - } - doc.myLinkDatabase = new PrefetchProxy(linkDocs); - } - if (doc.mySharedDocs === undefined) { - let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); - if (!sharedDocs) { - sharedDocs = Docs.Create.StackingDocument([], { - title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add, - _chromeHidden: true, boxShadow: "0 0", - }, sharingDocumentId + "outer", sharingDocumentId); - (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Add; - } - if (sharedDocs instanceof Doc) { - Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; - } - doc.mySharedDocs = new PrefetchProxy(sharedDocs); - } - } - - // Import sidebar is where shared documents are contained - static setupImportSidebar(doc: Doc) { - if (doc.myImportDocs === undefined) { - doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { - title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, - childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true, _chromeHidden: true, - })); - } - if (doc.myImportPanel === undefined) { - const uploads = Cast(doc.myImportDocs, Doc, null); - const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), btnType: ButtonType.ClickButton, toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true }); - doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, _showTitle: "title", ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true, boxShadow: "0 0" })); - } - } - - static setupClickEditorTemplates(doc: Doc) { - if (doc["clickFuncs-child"] === undefined) { - // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target - const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ", - { thisContainer: Doc.name }), { - title: "Click to open in target", _width: 300, _height: 200, - targetScriptKey: "onChildClick", system: true - }); - - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "openOnRight(self.doubleClickView)", - {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); - - doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); - } - // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. - PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); - - if (doc.clickFuncs === undefined) { - const onClick = Docs.Create.ScriptingDocument(undefined, { - title: "onClick", "onClick-rawScript": "console.log('click')", - isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true - }, "onClick"); - const onChildClick = Docs.Create.ScriptingDocument(undefined, { - title: "onChildClick", "onChildClick-rawScript": "console.log('child click')", - isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true - }, "onChildClick"); - const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", - isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true - }, "onDoubleClick"); - const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')", - isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true - }, "onChildDoubleClick"); - const onCheckedClick = Docs.Create.ScriptingDocument(undefined, { - title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", - "onCheckedClick-params": new List(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, - isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true - }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true }); - } - PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); - - return doc.clickFuncs as Doc; - } - - static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); - const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); - reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]), - async () => { - const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); - const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; - SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); - }, { fireImmediately: true }); - // Document properties on load - doc.system = true; - doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; - doc.title = Doc.CurrentUserEmail; - doc._raiseWhenDragged = true; - doc._showLabel = false; - doc._showMenuLabel = true; - doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); - doc.activeInkWidth = StrCast(doc.activeInkWidth, "1"); - doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); - doc.activeFillColor = StrCast(doc.activeFillColor, ""); - doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); - doc.activeArrowEnd = StrCast(doc.activeArrowEnd, ""); - doc.activeDash = StrCast(doc.activeDash, "0"); - doc.fontSize = StrCast(doc.fontSize, "12px"); - doc.fontFamily = StrCast(doc.fontFamily, "Arial"); - doc.fontColor = StrCast(doc.fontColor, "black"); - doc.fontHighlight = StrCast(doc.fontHighlight, ""); - doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, true); - doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white"); - doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); - doc.noviceMode = BoolCast(doc.noviceMode, true); - doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // - doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // - Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); - doc.savedFilters = new List(); - doc.filterDocCount = 0; - this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon - this.setupDocTemplates(doc); // sets up the template menu of templates - this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile - this.setupSearchPanel(doc); - this.setupOverlays(doc); // documents in overlay layer - this.setupDockedButtons(doc); // the bottom bar of font icons - this.setupContextMenuButtons(doc); //buttons for context menu - await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels - await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId); - if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); - - setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered - - // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - // doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - // doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - - // uncomment this to setup a default note style that uses the custom header layout - // PromiseValue(doc.emptyHeader).then(factory => { - // if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) { - // const deleg = Doc.delegateDragFactory(factory as Doc); - // deleg.title = "header"; - // doc.defaultTextLayout = new PrefetchProxy(deleg); - // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg); - // } - // }); - setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); - doc.fieldInfos = await Docs.setupFieldInfos(); - return doc; - } - - public static async loadCurrentUser() { - return rp.get(Utils.prepend("/getCurrentUser")).then(async response => { - if (response) { - const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response); - Doc.CurrentUserEmail = result.email; - resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts")); - DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email); - result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";"))); - return result; - } else { - throw new Error("There should be a user! Why does Dash think there isn't one?"); - } - }); - } - - public static async loadUserDocument(id: string) { - this.curr_id = id; - await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { - const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); - if (userDocumentId !== "guest") { - return DocServer.GetRefField(userDocumentId).then(async field => { - Docs.newAccount = !(field instanceof Doc); - await Docs.Prototypes.initialize(); - const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; - const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); - (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager - const a1 = await Cast(link?.anchor1, Doc, null); - const a2 = await Cast(link?.anchor2, Doc, null); - }); - return updated; - }); - } else { - throw new Error("There should be a user id! Why does Dash think there isn't one?"); - } - }); - } - - public static _urlState: HistoryUtil.DocUrl; - - public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => { - CurrentUserUtils.MainDocId = doc[Id]; - - if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); - userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); - } - const state = CurrentUserUtils._urlState; - if (state.sharing === true && !userDoc) { - DocServer.Control.makeReadOnly(); - } else { - fromHistory || HistoryUtil.pushState({ - type: "doc", - docId: doc[Id], - readonly: state.readonly, - nro: state.nro, - sharing: false, - }); - if (state.readonly === true || state.readonly === null) { - DocServer.Control.makeReadOnly(); - } else if (state.safe) { - if (!state.nro) { - DocServer.Control.makeReadOnly(); - } - CollectionView.SetSafeMode(true); - } else if (state.nro || state.nro === null || state.readonly === false) { - } else if (doc.readOnly) { - DocServer.Control.makeReadOnly(); - } else { - DocServer.Control.makeEditable(); - } - } - - return true; - } - - public static importDocument = () => { - const input = document.createElement("input"); - input.type = "file"; - input.multiple = true; - input.accept = ".zip, application/pdf, video/*, image/*, audio/*"; - input.onchange = async _e => { - const upload = Utils.prepend("/uploadDoc"); - const formData = new FormData(); - const file = input.files && input.files[0]; - if (file && file.type === 'application/zip') { - formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json !== "error") { - const doc = Docs.newAccount ? undefined : await DocServer.GetRefField(json); - if (doc instanceof Doc) { - setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => - docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. - } - } - } else if (input.files && input.files.length !== 0) { - const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null); - const disposer = OverlayView.ShowSpinner(); - DocListCastAsync(importDocs.data).then(async list => { - const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); - if (results.length !== input.files?.length) { - alert("Error uploading files - possibly due to unsupported file types"); - } - list?.splice(0, 0, ...results); - disposer(); - }); - } else { - console.log("No file selected"); - } - }; - input.click(); - } - - public static async snapshotDashboard(userDoc: Doc) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); - } - - public static createNewDashboard = async (userDoc: Doc, id?: string) => { - const myPresentations = await userDoc.myPresentations as Doc; - const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); - const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; - const dashboardCount = DocListCast(dashboards.data).length + 1; - const emptyPane = Cast(userDoc.emptyPane, Doc, null); - emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; - const freeformOptions: DocumentOptions = { - x: 0, - y: 400, - _width: 1500, - _height: 1000, - _fitWidth: true, - title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, - }; - const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); - Doc.AddDocToList(myPresentations, "data", presentation); - userDoc.activePresentation = presentation; - const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`); - const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); - const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); - const createDashboard = ScriptField.MakeScript(`createNewDashboard()`); - dashboardDoc.contextMenuScripts = new List([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!]); - dashboardDoc.contextMenuLabels = new List(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]); - - Doc.AddDocToList(dashboards, "data", dashboardDoc); - CurrentUserUtils.openDashboard(userDoc, dashboardDoc); - } - - public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number) { - const tbox = Docs.Create.TextDocument("", { - _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, - _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), - _fontFamily: StrCast(Doc.UserDoc().fontFamily), title - }); - const template = Doc.UserDoc().defaultTextLayout; - if (template instanceof Doc) { - tbox._width = NumCast(template._width); - tbox.layoutKey = "layout_" + StrCast(template.title); - Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; - } - return tbox; - } - - public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); } - public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); } - public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } - public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } - public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } - public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); } - public static get OverlayDocs() { return DocListCast((Doc.UserDoc().myOverlayDocs as Doc)?.data); } - public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } - @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } -} - -Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { - const copy = Doc.copyDragFactory(dragFactory); - if (copy) { - CollectionDockingView.AddSplit(copy, "right"); - const view = DocumentManager.Instance.getFirstDocumentView(copy); - view && SelectionManager.SelectView(view, false); - } -}); -Scripting.addGlobal(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, - "is Dash in novice mode"); -Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, - "creates a snapshot copy of a dashboard"); -Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, - "creates a new dashboard when called"); -Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, - "creates a new presentation when called"); -Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, - "returns all the links to the document or its annotations", "(doc: any)"); -Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); }, - "imports files from device directly into the import sidebar"); \ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 5f09a322c..5640e5132 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -27,6 +27,7 @@ import React = require("react"); import { PresBox } from './nodes/trails/PresBox'; import { undoBatch } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; +import { Colors } from './global/globalEnums'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -187,9 +188,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get followLinkButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) : {"follow primary link on click"}
}> +
{"Set onClick to follow primary link"}
}>
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 55af4650f..1f36e94cf 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1284,3 +1284,4 @@ Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) { CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); }); + diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss index db39e304b..9c766e03f 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -6,11 +6,12 @@ height: 100%; pointer-events: none; // background-color: rgba(0, 0, 0, 0.2); - border-radius: 5px; - padding-left: 5px; - padding-right: 5px; - border-left: $standard-border; - border-right: $standard-border; + + &.true { + padding-left: 5px; + padding-right: 5px; + border-left: $standard-border; + } .collectionLinearView { display: flex; diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx index e7970758a..a8846fd45 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx @@ -119,7 +119,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {

{BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : "–" : icon ? icon : "+"}

; - return
+ return
{!expandable ? (null) :
{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}
} placement="top"> {menuOpener} diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts new file mode 100644 index 000000000..bb4dd8bc9 --- /dev/null +++ b/src/client/views/nodes/button/ButtonScripts.ts @@ -0,0 +1,14 @@ +import { Scripting } from "../../../util/Scripting"; +import { SelectionManager } from "../../../util/SelectionManager"; + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeView(view: string) { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed"); +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function toggleOverlay() { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed"); +}); \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 0c866988d..72fab74d9 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -43,6 +43,11 @@ &.tglBtn { cursor: pointer; + + svg { + width: 50% !important; + height: 50%; + } } &.toolBtn { @@ -99,6 +104,8 @@ overflow: hidden; cursor: pointer; background: transparent; + align-content: center; + align-items: center; .menuButton-dropdownList { position: absolute; @@ -145,6 +152,39 @@ } } + .menuButton-dropdown { + display: flex; + justify-content: center; + align-items: center; + font-size: 15px; + /* background-color: #b9b9b9; */ + grid-column: 2; + border-radius: 0px 7px 7px 0px; + /* position: absolute; */ + width: 13px; + height: 100%; + right: 0; + } + + .menuButton-dropdown-header{ + width: 100%; + font-weight: 300; + overflow:hidden; + font-size: 12px; + white-space: nowrap; + text-overflow: ellipsis; + } + + .dropbox-background { + width: 100vw; + height: 100vh; + top: 0; + z-index: 20; + left: 0; + background:transparent; + position: fixed; + } + } @@ -186,31 +226,11 @@ // position: absolute; // } -// .menuButton-dropdown { -// display: flex; -// justify-content: center; -// align-items: center; -// font-size: 15px; -// /* background-color: #b9b9b9; */ -// grid-column: 2; -// border-radius: 0px 7px 7px 0px; -// /* position: absolute; */ -// width: 13px; -// height: 100%; -// right: 0; -// } + // &:hover { // background-color: $light-gray; // } -// .dropbox-background { -// width: 100vw; -// height: 100vh; -// top: 0; -// z-index: 20; -// left: 0; -// background:transparent; -// position: fixed; -// } + // } \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 9e7608dc3..9a54579dc 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -56,15 +56,12 @@ export class FontIconBox extends DocComponent(Fon } } - - // Determining UI Specs @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); @observable private icon = StrCast(this.dataDoc.icon, "user") as any; @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen); @observable private dropdownDirection: string = StrCast(this.rootDoc.dropDownDirection); @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList); - @observable private activeFont: string = StrCast(Doc.UserDoc()._fontFamily); @observable private type = StrCast(this.rootDoc.btnType); /** @@ -106,18 +103,185 @@ export class FontIconBox extends DocComponent(Fon ); } - @undoBatch setColor = action((color: ColorState, docColor?: string, userColor?: string) => { - console.log(docColor, userColor); - if (docColor) { - const numSelected = SelectionManager.Views().length; - const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; - eval(docColor); + /** + * Dropdown button + */ + @computed get dropdownListButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + const script: string = StrCast(this.rootDoc.script); + + let noviceList: string[] = []; + let text:string | undefined; + let dropdown = true; + let show = true; + let icon: IconProp = "caret-down"; + + + if (script == 'changeView'){ + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + if (selected && StrCast(selected.Document.type) == DocumentType.COL){ + text = StrCast(selected.Document._viewType); + } else if (selected) { + dropdown = false; + text = StrCast(selected.Document.type); + icon = Doc.toIcon(selected.Document); + } + noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; + } else if (script == 'changeFont') { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + if (selected && StrCast(selected.Document.type) == DocumentType.RTF){ + text = StrCast(selected.Document._fontFamily); + } else { + text = StrCast(Doc.UserDoc()._fontFamily); + } + noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", + "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; + } else { + show = false; } - else if (userColor) { - const userDoc = Doc.UserDoc(); - eval(userColor); + + const items = this.buttonList.map((value) => { + // console.log(value); + if (Doc.UserDoc().noviceMode && !noviceList.includes(value)){ + return; + } + const click = () => { + const s = ScriptField.MakeScript(script+'("'+value+'")'); + if (s){ + // console.log(s.script); + s.script.run().result; + } + } + return
+ {value[0].toUpperCase() + value.slice(1)} +
; + }); + + const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : +
+ {this.label} +
; + + return ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}> +
+ {text && text[0].toUpperCase() + text.slice(1)} +
+ {label} +
+ +
+ {this.rootDoc.dropDownOpen ? +
+
+ {items} +
+
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> +
+ : null} +
+ ); + } + + + @computed get rangeButton() { + return ( +
+ +
+ ) + } + + /** + * Colour button + */ + @computed get colorButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const numSelected = SelectionManager.Views().length; + const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; + const colorBox = (func: (color: ColorState) => void) => ; + const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : +
+ {this.label} +
; + const dropdownCaret =
+ +
; + const script: string = StrCast(this.rootDoc.script); + const click = (value: ColorState) => { + const hex: string = value.hex; + const s = ScriptField.MakeScript(script+'("'+hex+'")'); + if (s){ + // console.log(s.script); + s.script.run().result; + } } - }); + return ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} + onPointerDown={e => e.stopPropagation()}> + + {label} + {dropdownCaret} + {this.rootDoc.dropDownOpen ? +
+
e.stopPropagation()} + onClick={e => e.stopPropagation()} + style={{ left: 0 }}> + {colorBox((color) => click(color))} +
+
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> +
+ : null} +
+ ); + } + + @computed get toggleButton() { + const numSelected = SelectionManager.Views().length; + const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; + + const script: string = StrCast(this.rootDoc.script)+"(true)"; + let toggleTrue: boolean | undefined = false; + if (script == 'toggleOverlay'){ + toggleTrue = selectedDoc && BoolCast(selectedDoc.z); + console.log('toggleOverlay'); + } + const boolResult = ScriptField.MakeScript(script)?.script.run().result; + // console.log(this.rootDoc.title, script, boolResult); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + // const canClick: boolean = this.rootDoc.canClick ? eval(StrCast(this.rootDoc.canClick)) : false; + const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : +
+ {this.label} +
; + return ( +
+ + {label} +
+ ) + } @@ -151,13 +315,7 @@ export class FontIconBox extends DocComponent(Fon const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; const userDoc = Doc.UserDoc(); - // Toggle and canClick properties as determined from the variable passed into the button doc - const toggle = this.rootDoc.toggle ? ScriptCast(this.rootDoc.toggle) : undefined; - const canClick: boolean = this.rootDoc.canClick ? eval(StrCast(this.rootDoc.canClick)) : false; - // if (toggle) { - // console.log(StrCast(this.rootDoc.title), toggle); - // toggle.script.run(); - // } + const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark; const active: string = StrCast(this.rootDoc.dropDownOpen); @@ -169,111 +327,34 @@ export class FontIconBox extends DocComponent(Fon
{this.label}
; - const dropdownCaret =
- -
; - const colorBox = (func: (color: ColorState) => void) => ; - const items = this.buttonList.map((value) => { - return
Doc.UserDoc()._fontFamily = value}> - {value} -
; - }); - - /** - * Menu Panel Button: menuBtn - * Dropdown Button: dropDownBtn - * doubleBtn - **/ - - // CODEDUMP: glr - // const presSize = type === ButtonType.MenuButton ? 30 : 25; - // const presTrailsIcon = ; - + // TODO:glr Add label of button type - let button = ( - <> - {this.defaultButton} - - ); + let button = this.defaultButton; switch (this.type) { case ButtonType.DropdownButton: - button = ( - <> - {this.dropdownButton} - - ); + button = this.dropdownButton; break; case ButtonType.DropdownList: - button = ( -
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}> - {toggle} - {label} - {dropdownCaret} - {this.rootDoc.dropDownOpen ? - <> -
- {items} -
-
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> - - : null} -
- ); + button = this.dropdownListButton; break; case ButtonType.ColorButton: - button = ( -
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} - onPointerDown={e => e.stopPropagation()}> - - {label} - {dropdownCaret} - {this.rootDoc.dropDownOpen ? - <> -
e.stopPropagation()} - onClick={e => e.stopPropagation()} - style={{ left: 0 }}> - {colorBox((color) => this.setColor(color, StrCast(this.rootDoc.docColor), StrCast(this.rootDoc.userColor)))} -
-
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> - - : null} -
- ); + button = this.colorButton; break; case ButtonType.ToolButton: button = ( -
+
{label}
); break; case ButtonType.ToggleButton: - button = ( -
- - {label} -
- ); + button = this.toggleButton; break; case ButtonType.ClickButton: button = ( -
+
{label}
@@ -299,9 +380,91 @@ export class FontIconBox extends DocComponent(Fon break; } - return !this.layoutDoc.toolTip ? button : + return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton ? button : {StrCast(this.layoutDoc.toolTip)}
}> {button} ; } } + +// SCRIPTING BUTTONS + +import { Scripting } from "../../../util/Scripting"; +import { CollectionViewType } from '../../collections/CollectionView'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Colors } from '../../global/globalEnums'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeView(view: string) { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed"); +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeBackgroundColor(color?: string, checkResult?: boolean) { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + if (checkResult){ + return selected && selected.Document._backgroundColor; + } + selected ? selected.Document._backgroundColor = color : console.log("[FontIconBox.tsx] changeBackgroundColor failed"); +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function toggleOverlay(checkResult?:boolean) { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + if (checkResult){ + return selected && selected.Document.z == 1; + } + selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed"); +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeFont(font: string) { + // TODO: glr check if font selected and change selected font + SelectionManager.Views().map(dv => dv.props.Document._fontFamily = font); + console.log(font); + Doc.UserDoc()._fontFamily = font; + return Doc.UserDoc()._fontFamily; +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeFontColor(color: string) { + // TODO: glr check if font selected and change selected font + console.log(color); + Doc.UserDoc()._fontColor = color; +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeFontSize(size: string) { + // TODO: glr check if font selected and change selected font + console.log(size); + Doc.UserDoc()._fontSize = size; +}); + +Scripting.addGlobal(function toggleBold(checkResult?:boolean) { + if(checkResult) { + console.log("got here"); + return Doc.UserDoc().bold; + } + // TODO: glr check if font selected and change selected font + SelectionManager.Views().map(dv => dv.props.Document.bold = !dv.props.Document.bold); + Doc.UserDoc().bold = !Doc.UserDoc().bold; + return Doc.UserDoc().bold; +}); + +Scripting.addGlobal(function toggleUnderline(checkResult?:boolean) { + if(checkResult) return Doc.UserDoc().underline; + // TODO: glr check if font selected and change selected font + SelectionManager.Views().map(dv => dv.props.Document.underline = !dv.props.Document.underline); + Doc.UserDoc().bold = !Doc.UserDoc().underline; + return Doc.UserDoc().underline; +}); + +Scripting.addGlobal(function toggleItalic(checkResult?:boolean) { + if(checkResult) return Doc.UserDoc().italic; + // TODO: glr check if font selected and change selected font + SelectionManager.Views().map(dv => dv.props.Document.italic = !dv.props.Document.italic); + Doc.UserDoc().bold = !Doc.UserDoc().italic; + return Doc.UserDoc().italic; +}); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From a4340dc70a52f45af18435e28d1a3f2a163d3379 Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Thu, 19 Aug 2021 21:35:26 -0400 Subject: added randomly colored link relationships default color is black until link relationship is edited manually --- src/client/util/LinkManager.ts | 4 ++-- .../collectionFreeForm/CollectionFreeFormLinkView.tsx | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index c15e91df6..175de0fa5 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -34,7 +34,7 @@ export class LinkManager { static links: Doc[] = []; constructor() { LinkManager._instance = this; - this.createLinkrelationshipList(); + this.createLinkrelationshipLists(); setTimeout(() => { LinkManager.userLinkDBs = []; const addLinkToDoc = (link: Doc) => { @@ -98,7 +98,7 @@ export class LinkManager { } - public createLinkrelationshipList = () => { + public createLinkrelationshipLists = () => { const linkRelationshipList = new List(); const linkColorList = new List(); Doc.UserDoc().linkRelationshipList = linkRelationshipList; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index a8f5e6dd2..ba67bbcef 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -9,6 +9,9 @@ import { SnappingManager } from "../../../util/SnappingManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; import React = require("react"); +import { LinkManager } from "../../../util/LinkManager"; +import { List } from "../../../fields/List"; + export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -173,8 +176,14 @@ export class CollectionFreeFormLinkView extends React.Component; + const linkColorList = Doc.UserDoc().linkColorList as List; + const strokeColor = linkColorList[linkRelationshipList.indexOf(linkRelationship)]; //access stroke color using index of the relationship in the color list + console.log(strokeColor); return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - {textX === undefined ? (null) : {StrCast(this.props.LinkDocs[0].description)} -- cgit v1.2.3-70-g09d2 From 077e4ba816afd35bba4622e53d4dca62b74bf292 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 19 Aug 2021 21:56:57 -0400 Subject: menu UI updates --- src/Utils.ts | 48 ++++------------------ src/client/util/CurrentUserUtils.ts | 35 ++++++++-------- src/client/views/StyleProvider.tsx | 8 +--- src/client/views/collections/TabDocView.tsx | 2 + .../collectionLinearView/CollectionLinearView.scss | 28 +++++++------ .../collectionLinearView/CollectionLinearView.tsx | 4 +- src/client/views/nodes/button/FontIconBox.scss | 21 ++++++---- src/client/views/nodes/button/FontIconBox.tsx | 2 +- 8 files changed, 62 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 194c38a6f..0887759ee 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,6 +3,8 @@ import v5 = require("uuid/v5"); import { ColorState } from 'react-color'; import { Socket } from 'socket.io'; import { Message } from './server/Message'; +import { Colors } from './client/views/global/globalEnums'; +import Color = require('color'); export namespace Utils { export let DRAG_THRESHOLD = 4; @@ -556,46 +558,12 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe } export function lightOrDark(color: any) { - - // Variables for red, green, blue values - var r, g, b, hsp; - - // Check the format of the color, HEX or RGB? - if (color.match(/^rgb/)) { - - // If RGB --> store the red, green, blue values in separate variables - color = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/); - - r = color[1]; - g = color[2]; - b = color[3]; - } - else { - - // If hex --> Convert it to RGB: http://gist.github.com/983661 - color = +("0x" + color.slice(1).replace( - color.length < 5 && /./g, '$&$&')); - - r = color >> 16; - g = color >> 8 & 255; - b = color & 255; - } - - // HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html - hsp = Math.sqrt( - 0.299 * (r * r) + - 0.587 * (g * g) + - 0.114 * (b * b) - ); - - // Using the HSP value, determine whether the color is light or dark - if (hsp > 127.5) { - return 'light'; - } - else { - - return 'dark'; - } + const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) : + color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color; + const col = Color(nonAlphaColor).rgb(); + const colsum = (col.red() + col.green() + col.blue()); + if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY; + else return Colors.WHITE; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4ff0446ad..0440367de 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -990,10 +990,10 @@ export class CurrentUserUtils { if (type === "TextMenu") { const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, script, userColorBtn }) => { textDocList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 30, - _nativeHeight: 30, - _width: width ? width : 30, - _height: 30, + _nativeWidth: width ? width : 25, + _nativeHeight: 25, + _width: width ? width : 25, + _height: 25, icon, toolTip, userColorBtn, @@ -1006,7 +1006,8 @@ export class CurrentUserUtils { system: true, dontUndo: true, title, - backgroundColor: "black", + color: Colors.WHITE, + backgroundColor: "transparent", _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined @@ -1016,10 +1017,10 @@ export class CurrentUserUtils { } else if (type === "InkMenu") { const inkBtns = (CurrentUserUtils.inkTools(doc)).map(({ title, toolTip, icon, btnType, click }) => { textDocList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 30, - _nativeHeight: 30, - _width: width ? width : 30, - _height: 30, + _nativeWidth: width ? width : 25, + _nativeHeight: 25, + _width: width ? width : 25, + _height: 25, icon, toolTip, script, @@ -1031,7 +1032,8 @@ export class CurrentUserUtils { system: true, dontUndo: true, title, - backgroundColor: "black", + color: Colors.WHITE, + backgroundColor: "transparent", _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined @@ -1040,14 +1042,12 @@ export class CurrentUserUtils { docList.push(CurrentUserUtils.blist({ linearViewSubMenu: true, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, textDocList)); } else { docList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 30, - _nativeHeight: 30, - _width: width ? width : 30, - _height: 30, + _nativeWidth: width ? width : 25, + _nativeHeight: 25, + _width: width ? width : 25, + _height: 25, icon, toolTip, - // testToggle: toggle ? ScriptField.MakeScript(toggle, { scriptContext: "any" }) : undefined, - // toggle: toggle, script, btnType: btnType, btnList: new List(list), @@ -1057,7 +1057,8 @@ export class CurrentUserUtils { system: true, dontUndo: true, title, - backgroundColor: "black", + color: Colors.WHITE, + backgroundColor: "transparent", _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 85520f6b3..470ae7c77 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -21,6 +21,7 @@ import "./nodes/FilterBox.scss"; import "./StyleProvider.scss"; import React = require("react"); import Color = require('color'); +import { lightOrDark } from '../../Utils'; export enum StyleLayers { Background = "background" @@ -101,12 +102,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 400 || col.alpha() < 0.25) return Colors.DARK_GRAY; - return Colors.WHITE; + return lightOrDark(backColor) case StyleProp.Hidden: return BoolCast(doc?._hidden); case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"], doc?._viewType === CollectionViewType.Pile ? "50%" : ""); case StyleProp.TitleHeight: return 15; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 1969d728c..9ff1a0f61 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -124,6 +124,8 @@ export class TabDocView extends React.Component { tab.element[0].prepend(iconWrap); tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }), ({ layer, color }) => { + console.log("TabDocView: " + this.tabColor); + console.log("lightOrDark: " + lightOrDark(this.tabColor)); const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color titleEle.style.color = textColor; titleEle.style.backgroundColor = "transparent"; diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss index 9c766e03f..b37233892 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -5,12 +5,16 @@ overflow: visible; height: 100%; pointer-events: none; - // background-color: rgba(0, 0, 0, 0.2); &.true { padding-left: 5px; padding-right: 5px; border-left: $standard-border; + background-color: #4476f780; + } + + >input:not(:checked)~&.true { + background-color: transparent; } .collectionLinearView { @@ -78,24 +82,25 @@ } >label { - background: $dark-gray; + pointer-events: all; + cursor: pointer; + background-color: $medium-blue; + padding: 5; + border-radius: 2px; + height: 25; + + margin: 0; color: $white; display: flex; - border-radius: 18px; - font-size: 12.5px; - font-weight:100; + font-weight: 100; width: fit-content; - height: 100%; - margin-top: auto; - margin-bottom: auto; - margin-right: 3px; - cursor: pointer; transition: transform 0.2s; align-items: center; justify-content: center; + transition:0.1s; &:hover { - background: $medium-gray; + transform: scale(1.05); } } @@ -122,7 +127,6 @@ .collectionLinearView-docBtn-scalable { position: relative; margin: auto; - margin-left: 3px; transform-origin: center 80%; } diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx index a8846fd45..5d89d82b4 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx @@ -114,12 +114,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const icon: string = StrCast(this.props.Document.icon); - const menuOpener = ; - return
+ return
{!expandable ? (null) :
{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}
} placement="top"> {menuOpener} diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 72fab74d9..8dfb66e30 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -6,7 +6,11 @@ justify-content: center; align-items: center; font-size: 80%; - border-radius: 7px; + border-radius: 2px; + + &:hover { + background-color: rgba(0,0,0,0.3) !important; + } .menuButton-wrap { grid-column: 1; @@ -94,8 +98,7 @@ width: 100px; display: grid; grid-auto-columns: 80px 20px; - justify-items: flex-start; - padding-left: 10px; + justify-items: center; font-family: 'Roboto'; white-space: nowrap; text-overflow: ellipsis; @@ -113,14 +116,15 @@ height: fit-content; top: 100%; z-index: 21; - background-color: #e3e3e3; - box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); - border-radius: 3px; + background-color: $white; + box-shadow: 0px 3px 4px rgba(0,0,0,0.3); + padding: 1px; .list-item { - color: black; + color: $black; width: 100%; height: 25px; + font-weight: 400; display: flex; justify-content: left; align-items: center; @@ -169,7 +173,8 @@ .menuButton-dropdown-header{ width: 100%; font-weight: 300; - overflow:hidden; + padding: 5px; + overflow: hidden; font-size: 12px; white-space: nowrap; text-overflow: ellipsis; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 9a54579dc..84ad03fa2 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -169,7 +169,7 @@ export class FontIconBox extends DocComponent(Fon return (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
{text && text[0].toUpperCase() + text.slice(1)} -- cgit v1.2.3-70-g09d2 From b147d26c510d98357bd4ddf8525750f72bc9069a Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 19 Aug 2021 21:59:58 -0400 Subject: mini update --- .../views/collections/collectionLinearView/CollectionLinearView.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss index b37233892..2b3f8f2c9 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -10,7 +10,7 @@ padding-left: 5px; padding-right: 5px; border-left: $standard-border; - background-color: #4476f780; + background-color: #4476f73d; } >input:not(:checked)~&.true { -- cgit v1.2.3-70-g09d2 From 8f77323d6be4d4e3537d2bc2bbe815e9d578eccb Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Fri, 20 Aug 2021 00:12:42 -0400 Subject: fixed bug where link colors reset on reload need to fix new mystery bug where any new links disappear after refreshing once but re-appear after a second refrsh --- src/client/util/LinkManager.ts | 14 +++++++------- .../collectionFreeForm/CollectionFreeFormLinkView.tsx | 3 ++- .../collectionFreeForm/CollectionFreeFormLinksView.tsx | 1 + src/client/views/linking/LinkEditor.tsx | 11 ++++++----- src/client/views/nodes/LinkDescriptionPopup.tsx | 4 ++-- src/client/views/pdf/AnchorMenu.tsx | 4 ++-- 6 files changed, 20 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 175de0fa5..1fc0a7593 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -99,13 +99,13 @@ export class LinkManager { public createLinkrelationshipLists = () => { - const linkRelationshipList = new List(); - const linkColorList = new List(); - Doc.UserDoc().linkRelationshipList = linkRelationshipList; - Doc.UserDoc().linkColorList = linkColorList; - console.log(Doc.UserDoc().linkRelationshipList); - console.log(Doc.UserDoc().linkColorList); - // + //create new lists for link relations and their associated colors if the lists don't already exist + if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { + const linkRelationshipList = new List(); + const linkColorList = new List(); + Doc.UserDoc().linkRelationshipList = linkRelationshipList; + Doc.UserDoc().linkColorList = linkColorList; + } } public addLink(linkDoc: Doc, checkExists = false) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index ba67bbcef..0c44df64c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -180,7 +180,8 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; - const strokeColor = linkColorList[linkRelationshipList.indexOf(linkRelationship)]; //access stroke color using index of the relationship in the color list + //access stroke color using index of the relationship in the color list (default black) + const strokeColor = linkRelationshipList.indexOf(linkRelationship) == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; console.log(strokeColor); return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> { setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { LinkManager.currentLink.linkRelationship = value; - const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List; - const linkColorList = Doc.UserDoc().linkColorList as List; + const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); + const linkColorList = StrListCast(Doc.UserDoc().linkColorList); // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (linkRelationshipList && !linkRelationshipList.includes(value)) { linkRelationshipList.push(value); @@ -99,10 +99,11 @@ export class LinkEditor extends React.Component { style={{ width: "100%" }} id="input" value={this.relationship} - placeholder={"enter link label"} + placeholder={"Enter link relationship"} // color={"rgb(88, 88, 88)"} onKeyDown={this.onRelationshipKey} onChange={this.handleRelationshipChange} + onBlur={this.onRelationshipDown} >
{ style={{ width: "100%" }} id="input" value={this.description} - placeholder={"enter link label"} + placeholder={"Enter link description"} // color={"rgb(88, 88, 88)"} onKeyDown={this.onKey} onChange={this.handleChange} diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 30b272a9a..b62a4dd56 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -54,7 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> { }}> e.key === "Enter" && this.onDismiss(true)} - placeholder={"(optional) enter link label..."} + placeholder={"(Optional) Enter link description..."} onChange={(e) => this.descriptionChanged(e)}>
@@ -65,4 +65,4 @@ export class LinkDescriptionPopup extends React.Component<{}> {
; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 8d74b2ac4..75e3f81fb 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -152,12 +152,12 @@ export class AnchorMenu extends AntimodeMenu { , //NOTE: link popup is currently in progress - {"Link selected text to document or URL"}
}> + {"Link selected text to document"}
}> , - + ] : [ {"Remove Link Anchor"}
}> + )}
; } @@ -1050,7 +1053,7 @@ export class CollectionTreeViewChrome extends React.Component
Sort -
+
diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss index 2b3f8f2c9..44752e034 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -5,12 +5,12 @@ overflow: visible; height: 100%; pointer-events: none; - + &.true { padding-left: 5px; padding-right: 5px; border-left: $standard-border; - background-color: #4476f73d; + background-color: $medium-blue-alt; } >input:not(:checked)~&.true { @@ -21,8 +21,9 @@ display: flex; height: 100%; align-items: center; + gap: 5px; - .collectionView{ + .collectionView { overflow: visible !important; } @@ -56,7 +57,7 @@ } .bottomPopup-descriptions { - cursor:pointer; + cursor: pointer; display: inline; white-space: nowrap; padding-left: 8px; @@ -69,7 +70,7 @@ } .bottomPopup-exit { - cursor:pointer; + cursor: pointer; display: inline; white-space: nowrap; margin-right: 10px; @@ -97,7 +98,7 @@ transition: transform 0.2s; align-items: center; justify-content: center; - transition:0.1s; + transition: 0.1s; &:hover { transform: scale(1.05); diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx index 3327bef36..713d93f97 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx @@ -108,12 +108,13 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } render() { - const guid = Utils.GenerateGuid(); - const flexDir: any = StrCast(this.Document.flexDirection); - const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); + const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label + const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content + const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content + const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const icon: string = StrCast(this.props.Document.icon); + const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle const menuOpener =
; -- cgit v1.2.3-70-g09d2 From 9b0a043eeb272c69c1aac07c5a4df409f09400b7 Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:48:47 -0400 Subject: matched link group header background to color of relationship --- src/client/views/linking/LinkEditor.tsx | 1 - src/client/views/linking/LinkMenuGroup.tsx | 21 +++++++++++++++++++-- src/client/views/linking/LinkRelationshipSearch.tsx | 1 - 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 2d6a6942b..fba95cad2 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -115,7 +115,6 @@ export class LinkEditor extends React.Component { } toggleSearchIsActive = () => { this.searchIsActive = !this.searchIsActive; - console.log("toggle search to " + this.searchIsActive) } @action diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index c7586a467..cb6571f92 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; +import { Doc, StrListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { Cast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; @@ -20,6 +20,23 @@ interface LinkMenuGroupProps { export class LinkMenuGroup extends React.Component { private _menuRef = React.createRef(); + getBackgroundColor = (): string => { + const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); + const linkColorList = StrListCast(Doc.UserDoc().linkColorList); + let color = "white"; + // if this link's relationship property is not default "link", set its color + if (linkRelationshipList) { + const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType); + const RGBcolor: string = linkColorList[relationshipIndex]; + if (RGBcolor) { + //set opacity to 0.25 by modifiying the rgb string + color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)"; + console.log(color); + } + } + return color; + } + render() { const set = new Set(this.props.group); const groupItems = Array.from(set.keys()).map(linkDoc => { @@ -39,7 +56,7 @@ export class LinkMenuGroup extends React.Component { return (
-
+

{this.props.groupType}:

diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx index bb128f746..8f83f0e6e 100644 --- a/src/client/views/linking/LinkRelationshipSearch.tsx +++ b/src/client/views/linking/LinkRelationshipSearch.tsx @@ -13,7 +13,6 @@ interface LinkRelationshipSearchProps { export class LinkRelationshipSearch extends React.Component { handleResultClick = (e: React.MouseEvent) => { - console.log("click"); const relationship = (e.target as HTMLParagraphElement).textContent; if (relationship) { this.props.handleRelationshipSearchChange(relationship) -- cgit v1.2.3-70-g09d2 From 101f04d82e49335fb2a55301a4ef4e7b04f081e6 Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Mon, 23 Aug 2021 22:15:40 -0400 Subject: menu getting closer :cry: --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 134 ++++++++++++-------- src/client/views/collections/TabDocView.scss | 30 +++-- src/client/views/collections/TabDocView.tsx | 12 +- .../collectionLinearView/CollectionLinearView.scss | 2 +- .../collectionLinearView/CollectionLinearView.tsx | 10 +- src/client/views/global/globalCssVariables.scss | 5 + src/client/views/global/globalEnums.tsx | 4 + src/client/views/nodes/button/FontIconBox.scss | 120 +++++++++++++++++- src/client/views/nodes/button/FontIconBox.tsx | 137 +++++++++++++++++---- 10 files changed, 362 insertions(+), 93 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b7ba6f1fe..57015e241 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -230,6 +230,7 @@ export class DocumentOptions { linearViewExpandable?: boolean; // can linear view be expanded linearViewToggleButton?: string; // button to open close linear view group linearViewSubMenu?: boolean; + linearViewFloating?: boolean; flexGap?: number; // Linear view flex gap flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2903b84e3..c7aa57ff0 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -38,7 +38,20 @@ import { SharingManager } from "./SharingManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; import { ButtonType } from "../views/nodes/button/FontIconBox"; - +import { IconName } from "@fortawesome/fontawesome-svg-core"; + +interface Button { + title?: string; + toolTip?: string; + icon?: string; + btnType?: ButtonType; + click?: string; + switchToggle?: boolean; + script?: string; + width?: number; + list?: string[]; + ignoreClick?: boolean; +} export let resolvedPorts: { server: number, socket: number }; const headerViewVersion = "0.1"; @@ -918,46 +931,71 @@ export class CurrentUserUtils { } static textTools(doc: Doc) { - return [ + const tools:Button[] = + [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], script: 'changeFont' }, + { title: "Font size", toolTip: "Font size", btnType: ButtonType.NumberButton, script: 'changeFontSize'}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' }, { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' }, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'}, - { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, script:'changeFontColor' }, + { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", ignoreClick: true, script:'changeAlignment("left")' }, + { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", ignoreClick: true, script:'changeAlignment("center")' }, + { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", ignoreClick: true, script:'changeAlignment("right")' }, + + { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script:'changeFontColor' }, { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, script:'changeHighlightColor' }, ]; + return tools; } static inkTools(doc: Doc) { - return [ + const tools:Button[] = [ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'togglePen()', script: 'togglePen' }, - { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'toggleHighlighter()', script: 'toggleHighlighter' }, + { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setHighlighter()', script: 'toggleHighlighter' }, { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'toggleCircle()', script: 'toggleCircle' }, { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'toggleSquare()', script: 'toggleSquare' }, { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'toggleLine()', script: 'toggleLine' }, ]; + return tools; } - static webTools(doc: Doc) { - return [ - { - title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", - "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - script: 'changeFont' + static schemaTools(doc: Doc) { + const tools:Button[] = + [ + { + title: "Show preview", + toolTip: "Show preview of selected document", + btnType: ButtonType.ToggleButton, + switchToggle: true, + icon: "eye", + click: 'toggleSchemaShow()', + script: 'toggleSchemaShow' }, ]; + return tools; + } + + static webTools(doc: Doc) { + const tools:Button[] = + [ + { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack()' }, + { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward()' }, + { title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, + { title: "URL", toolTip: "URL", width: 150, btnType: ButtonType.EditableText, icon: "lock", script: 'webSetURL' }, + ]; + + return tools; } - static async contextMenuBtnDescriptions(doc: Doc) { + static async contextMenuTools(doc: Doc) { return [ { title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, @@ -973,50 +1011,41 @@ export class CurrentUserUtils { script: "changeBackgroundColor" }, { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', script:'toggleOverlay'}, - { title: "Text", type: "TextMenu" }, - { title: "Ink & GFX", type: "InkMenu" }, - // { title: "Ink Tools", type: "LinearMenu", icon: "pen-nib" }, - // { title: "GFX Tools", type: "LinearMenu", icon: "shapes" }, - // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, + { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, + { title: "Text", type: "textTools", subMenu: true }, + { title: "Ink & GFX", type: "inkTools", subMenu: true }, + { title: "Web", type: "webTools", subMenu: true }, + { title: "Schema", type: "schemaTools", subMenu: true } ]; } - // Default context menu buttons + // Sets up the default context menu buttons static async setupContextMenuButtons(doc: Doc) { const docList: Doc[] = []; - const contextMenuBtns = (await CurrentUserUtils.contextMenuBtnDescriptions(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script }) => { - const textDocList: Doc[] = []; - if (type === "TextMenu") { - const textBtns = (CurrentUserUtils.textTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, btnType, click, script, userColorBtn }) => { - textDocList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 25, - _nativeHeight: 25, - _width: width ? width : 25, - _height: 25, - icon, - toolTip, - userColorBtn, - script, - btnType: btnType, - btnList: new List(list), - ignoreClick: ignoreClick, - _stayInCollection: true, - _hideContextMenu: true, - system: true, - dontUndo: true, - title, - color: Colors.WHITE, - backgroundColor: "transparent", - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined - })); - }); - docList.push(CurrentUserUtils.linearButtonList({ linearViewSubMenu: true, flexGap: 5, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, textDocList)); - } else if (type === "InkMenu") { - const inkBtns = (CurrentUserUtils.inkTools(doc)).map(({ title, toolTip, icon, btnType, click }) => { - textDocList.push(Docs.Create.FontIconDocument({ + (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu }) => { + const menuDocList: Doc[] = []; + if (subMenu) { + // default is textTools + let tools: Button[]; + switch(type){ + case "inkTools": + tools = CurrentUserUtils.inkTools(doc); + break; + case "schemaTools": + tools = CurrentUserUtils.schemaTools(doc); + break; + case "webTools": + tools = CurrentUserUtils.webTools(doc); + break; + case "textTools": + tools = CurrentUserUtils.textTools(doc); + break; + default: + break; + } + tools.map(({ title, toolTip, icon, btnType, click, script, width, list, ignoreClick }) => { + menuDocList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, _nativeHeight: 25, _width: width ? width : 25, @@ -1039,7 +1068,7 @@ export class CurrentUserUtils { onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined })); }); - docList.push(CurrentUserUtils.linearButtonList({ linearViewSubMenu: true, flexGap: 5, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, textDocList)); + docList.push(CurrentUserUtils.linearButtonList({ linearViewSubMenu: true, flexGap: 5, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, menuDocList)); } else { docList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, @@ -1070,6 +1099,7 @@ export class CurrentUserUtils { doc.contextMenuBtns = CurrentUserUtils.linearButtonList({ title: "menu buttons", flexGap: 0, ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList); } } + // sets up the default set of documents to be shown in the Overlay layer static setupOverlays(doc: Doc) { if (doc.myOverlayDocs === undefined) { diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index a963f1cb9..7f62ecaa0 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -1,3 +1,6 @@ +@import "../global/globalCssVariables.scss"; + + input.lm_title:focus, input.lm_title { max-width: unset !important; @@ -57,12 +60,11 @@ input.lm_title { } } -.miniMap-hidden, .miniMap { position: absolute; overflow: hidden; - right: 10; - bottom: 10; + right: 15; + bottom: 15; border: solid 1px; box-shadow: black 0.4vw 0.4vw 0.8vw; width: 100%; @@ -82,17 +84,21 @@ input.lm_title { } .miniMap-hidden { + cursor: pointer; position: absolute; - bottom: 0; - right: 0; - width: 45px; - height: 45px; - transform: translate(20px, 20px) rotate(45deg); - border-radius: 30px; + bottom: 5; + display: flex; + right: 5; + width: 25px; + height: 25px; + border-radius: 3px; padding: 2px; + justify-content: center; + align-items: center; + align-content: center; + background-color: $light-gray; - >svg { - margin-top: 3px; - transform: translate(0px, 7px); + &:hover { + box-shadow: none; } } \ No newline at end of file diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 00de36400..a999512cc 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -34,6 +34,7 @@ import { CollectionView, CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; import React = require("react"); import Color = require('color'); +import { Colors, Shadows } from '../global/globalEnums'; const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { @@ -386,8 +387,15 @@ export class TabDocView extends React.Component { background={this.miniMapColor} document={this._document} tabView={this.tabView} /> - {"toggle minimap"}
}> -
e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} > + {this._document.hideMinimap ? "Open minimap" : "Close minimap"}
}> +
e.stopPropagation()} + onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss index 44752e034..24c9bc9cb 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.scss @@ -89,7 +89,7 @@ padding: 5; border-radius: 2px; height: 25; - + min-width: 25; margin: 0; color: $white; display: flex; diff --git a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx index 48f50cf2d..990c2e4b2 100644 --- a/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinearView/CollectionLinearView.tsx @@ -17,7 +17,7 @@ import { StyleProp } from '../../StyleProvider'; import "./CollectionLinearView.scss"; import { CollectionSubView } from '.././CollectionSubView'; import { CollectionViewType } from '.././CollectionView'; -import { Colors } from '../../global/globalEnums'; +import { Colors, Shadows } from '../../global/globalEnums'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -112,6 +112,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not + const floating: boolean = BoolCast(this.props.Document.linearViewFloating); // Specify whether it is expandable or not + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle @@ -121,7 +123,9 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { backgroundColor: backgroundColor === color ? "black" : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY }} onPointerDown={e => e.stopPropagation()} > -
+
{BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : "–" : icon ? icon : "+"}
; @@ -181,7 +185,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
; })}
- {DocumentLinksButton.StartLink ? e.stopPropagation()} > diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index a1baed7d9..2fd67a8dc 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -16,6 +16,7 @@ $close-red: #e48282; $drop-shadow: "#32323215"; + //padding $minimum-padding: 4px; $medium-padding: 16px; @@ -48,6 +49,10 @@ $standard-border: solid 1px #9f9f9f; // border radius $standard-border-radius: 3px; +// shadow +$standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); + + $searchpanel-height: 32px; $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc $docDecorations-zindex: 998; // then doc decorations appear over everything else diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx index 074a3aab3..56779c37c 100644 --- a/src/client/views/global/globalEnums.tsx +++ b/src/client/views/global/globalEnums.tsx @@ -36,4 +36,8 @@ export enum IconSizes { export enum Borders { STANDARD = "solid 1px #9F9F9F" +} + +export enum Shadows { + STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)" } \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index fef99ce83..c1f5282d0 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -38,11 +38,15 @@ &.clickBtn { cursor: pointer; - width: 40px; &:hover { background-color: rgba(0, 0, 0, 0.3) !important; } + + svg { + width: 50% !important; + height: 50%; + } } &.tglBtn { @@ -157,9 +161,58 @@ } } + &.numBtn { + cursor: pointer; + background: transparent; + + &.slider { + .numberBtn-slider {} + + .menuButton-dropdownBox { + position: absolute; + width: fit-content; + height: fit-content; + top: 100%; + z-index: 21; + background-color: #e3e3e3; + box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); + border-radius: $standard-border-radius; + } + } + + &.dropdown { + .menuButton-dropdownList { + position: absolute; + width: fit-content; + height: fit-content; + top: 100%; + z-index: 21; + background-color: $white; + box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); + padding: 1px; + + .list-item { + color: $black; + width: 100%; + height: 25px; + font-weight: 400; + display: flex; + justify-content: left; + align-items: center; + padding-left: 5px; + } + + .list-item:hover { + background-color: lightgrey; + } + } + } + } + &.drpDownBtn { cursor: pointer; background: transparent; + border: solid 0.5px grey; &.true { background: rgba(0, 0, 0, 0.3); @@ -172,7 +225,7 @@ top: 100%; background-color: #e3e3e3; box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); - border-radius: 3px; + border-radius: $standard-border-radius; } } @@ -208,4 +261,67 @@ position: fixed; } +} + + +//TOGGLE + +.switch { + position: relative; + display: inline-block; + width: 50px; + height: 25px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: lightgrey; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 22px; + width: 22px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked+.slider { + background-color: $medium-blue; +} + +input:focus+.slider { + box-shadow: 0 0 1px $medium-blue; +} + +input:checked+.slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: $standard-border-radius; +} + +.slider.round:before { + border-radius: $standard-border-radius; } \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index aa3a1ada4..46d92cd6e 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -3,9 +3,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; +import { StringifyOptions } from 'querystring'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; import { Doc, StrListCast } from '../../../../fields/Doc'; +import { InkTool } from '../../../../fields/InkField'; import { createSchema, makeInterface } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types'; @@ -17,9 +19,11 @@ import { UndoManager } from '../../../util/UndoManager'; import { CollectionViewType } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; import { DocComponent } from '../../DocComponent'; +import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '.././FieldView'; +import { RichTextMenu } from '../formattedText/RichTextMenu'; import './FontIconBox.scss'; const FontIconSchema = createSchema({ icon: "string", @@ -34,7 +38,8 @@ export enum ButtonType { ToggleButton = "tglBtn", ColorButton = "colorBtn", ToolButton = "toolBtn", - NumberButton = "numBtn" + NumberButton = "numBtn", + EditableText = "editableTxt" } export enum NumButtonType { @@ -94,10 +99,15 @@ export class FontIconBox extends DocComponent(Fon @computed get numberButton() { const numType: string = StrCast(this.rootDoc.numButtonType); - return ( -
+ const dropdown = numType ? +
+ {/* DROPDOWN BOX CONTENTS */} +
: null; + return ( +
+ {this.rootDoc.dropDownOpen ? dropdown : null}
); } @@ -130,7 +140,7 @@ export class FontIconBox extends DocComponent(Fon } /** - * Dropdown button + * Dropdown list */ @computed get dropdownListButton() { const active: string = StrCast(this.rootDoc.dropDownOpen); @@ -221,7 +231,7 @@ export class FontIconBox extends DocComponent(Fon } /** - * Colour button + * Color button */ @computed get colorButton() { const active: string = StrCast(this.rootDoc.dropDownOpen); @@ -229,10 +239,18 @@ export class FontIconBox extends DocComponent(Fon const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const numSelected = SelectionManager.Views().length; const selectedDoc = numSelected > 0 ? SelectionManager.Views()[0].Document : undefined; + + const script: string = StrCast(this.rootDoc.script); + const scriptCheck: string = script + "(undefined, true)"; + const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result; + console.log("[boolResul]: ", boolResult); + + const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', + '#FFFFFF', '#f1efeb', 'transparent']; + const colorBox = (func: (color: ColorState) => void) => ; + presetColors={colorOptions} />; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label} @@ -242,10 +260,9 @@ export class FontIconBox extends DocComponent(Fon style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
; - const script: string = StrCast(this.rootDoc.script); const click = (value: ColorState) => { const hex: string = value.hex; - const s = ScriptField.MakeScript(script + '("' + hex + '")'); + const s = ScriptField.MakeScript(script + '("' + hex + '", false)'); if (s) { s.script.run().result; } @@ -277,20 +294,47 @@ export class FontIconBox extends DocComponent(Fon } @computed get toggleButton() { - const script: string = StrCast(this.rootDoc.script) + "(true)"; - const boolResult = ScriptField.MakeScript(script)?.script.run().result; + // Determine the type of toggle button + const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle); + + // Script for running the toggle + const script: string = StrCast(this.rootDoc.script) + "()"; + // Script for checking the outcome of the toggle + const checkScript: string = StrCast(this.rootDoc.script) + "(true)"; + // Function to run the script + const runScript = () => ScriptField.MakeScript(script)?.script.run(); + const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result; + + // Colors const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + // Button label const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
; + return ( -
- - {label} -
+ switchToggle ? +
+ +
+ : +
+ + {label} +
); } @@ -314,6 +358,19 @@ export class FontIconBox extends DocComponent(Fon ); } + @computed get editableText() { + const setValue = (value: string, shiftDown?: boolean): boolean => { + console.log("setValue"); + return true; + }; + + return
+ TEST + ""} SetValue={setValue} contents="..."> + +
; + } + render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); @@ -342,6 +399,8 @@ export class FontIconBox extends DocComponent(Fon let button = this.defaultButton; switch (this.type) { + case ButtonType.EditableText: + button = this.editableText; case ButtonType.NumberButton: button = this.numberButton; break; @@ -420,7 +479,6 @@ Scripting.addGlobal(function changeBackgroundColor(color?: string, checkResult?: Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; if (checkResult && selected) { - console.log("z: ", NumCast(selected.Document.z) === 1); return NumCast(selected.Document.z) === 1; } selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed"); @@ -429,17 +487,43 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { // toggle: Set overlay status of selected document Scripting.addGlobal(function changeFont(font: string) { SelectionManager.Views().map(dv => dv.props.Document._fontFamily = font); - console.log(font); + console.log("[changeFont]: ", font); + console.log(RichTextMenu.Instance.getActiveMarksOnSelection()); Doc.UserDoc()._fontFamily = font; return Doc.UserDoc()._fontFamily; }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function changeFontColor(color: string) { - console.log(color); +Scripting.addGlobal(function changeFontColor(color?: string, checkResult?: boolean) { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + if (checkResult) { + if (selected) { + console.log("[Font color] (selected): " + StrCast(selected.Document._fontColor)); + return selected.Document._fontColor; + } else { + console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor)); + return Doc.UserDoc()._fontColor; + } + } + Doc.UserDoc()._fontColor = color; +}); + +// toggle: Set overlay status of selected document +Scripting.addGlobal(function changeFontHighlight(color?: string, checkResult?: boolean) { + const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; + if (checkResult) { + if (selected) { + console.log("[Font color] (selected): " + StrCast(selected.Document._fontColor)); + return selected.Document._fontColor; + } else { + console.log("[Font color] (global): " + StrCast(Doc.UserDoc()._fontColor)); + return Doc.UserDoc()._fontColor; + } + } Doc.UserDoc()._fontColor = color; }); + // toggle: Set overlay status of selected document Scripting.addGlobal(function changeFontSize(size: string) { console.log(size); @@ -471,4 +555,15 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { SelectionManager.Views().map(dv => dv.props.Document.italic = !dv.props.Document.italic); Doc.UserDoc().bold = !Doc.UserDoc().italic; return Doc.UserDoc().italic; +}); + +Scripting.addGlobal(function setActiveInkTool(checkResult?: boolean, tool?: InkTool) { + if (checkResult) { + return Doc.UserDoc().activeInkTool === tool; + } + if (tool) { + Doc.UserDoc().activeInkTool = tool; + } else { + Doc.UserDoc().activeInkTool = InkTool.None; + } }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 707038795f7129d30a1d4ed5f2311f18a282fecb Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Aug 2021 02:30:06 -0400 Subject: fixed following link from ink stroke. made interacting with annotatable documents consistent when selections are cleared by clicking. fixed undo for webboxes. fixed limiting size of links button. --- src/client/documents/Documents.ts | 1 + src/client/views/DocComponent.tsx | 10 +++--- src/client/views/MarqueeAnnotator.tsx | 16 ++++----- src/client/views/PreviewCursor.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 10 ++++-- .../collections/collectionFreeForm/MarqueeView.tsx | 15 +++++--- src/client/views/nodes/DocumentView.tsx | 6 ++-- src/client/views/nodes/ImageBox.tsx | 25 ++++++++------ src/client/views/nodes/ScreenshotBox.tsx | 3 +- src/client/views/nodes/VideoBox.tsx | 19 +++++----- src/client/views/nodes/WebBox.tsx | 40 +++++++++------------- src/client/views/pdf/PDFViewer.tsx | 14 ++++---- 12 files changed, 82 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 48886aa3b..f8e9e8702 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -703,6 +703,7 @@ export namespace Docs { I.data = new InkField(points); I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; + I.links = ComputedField.MakeFunction("links(self)") as any; I[Initializing] = false; return I; } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 14d32ef12..d9cc29bed 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -90,9 +90,9 @@ export interface ViewBoxAnnotatableProps { renderDepth: number; isAnnotationOverlay?: boolean; } -export function ViewBoxAnnotatableComponent

(schemaCtor: (doc: Doc) => T, _annotationKey: string = "annotations") { +export function ViewBoxAnnotatableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends Touchable

{ - @observable _annotationKey: string = _annotationKey; + @observable _annotationKeySuffix = () => "annotations"; @observable _isAnyChildContentActive = false; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @@ -125,7 +125,7 @@ export function ViewBoxAnnotatableComponent

{ if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); doc.context = this.props.Document; - if (annotationKey ?? this._annotationKey) Doc.GetProto(doc).annotationOn = this.props.Document; + if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; this.props.layerProvider?.(doc, true); Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); }); @@ -214,7 +214,7 @@ export function ViewBoxAnnotatableComponent

{ @observable private _width: number = 0; @observable private _height: number = 0; - constructor(props: any) { - super(props); - runInAction(() => { - AnchorMenu.Instance.Status = "marquee"; - AnchorMenu.Instance.fadeOut(true); - // clear out old marquees and initialize menu for new selection - Array.from(this.props.savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); - this.props.savedAnnotations.clear(); - }); + @action + static clearAnnotations(savedAnnotations: ObservableMap) { + AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.fadeOut(true); + // clear out old marquees and initialize menu for new selection + Array.from(savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); + savedAnnotations.clear(); } @action componentDidMount() { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 679a4b81e..2b82ef475 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -158,7 +158,7 @@ export class PreviewCursor extends React.Component<{}> { } render() { return (!PreviewCursor._clickPoint || !PreviewCursor.Visible) ? (null) : -

e && e.focus()} +
e?.focus()} style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}> I
; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a82128e47..a821aeeea 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -36,6 +36,7 @@ import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import './CollectionView.scss'; +import { returnEmptyString } from '../../../Utils'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); @@ -62,7 +63,7 @@ export enum CollectionViewType { export interface CollectionViewProps extends FieldViewProps { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) layoutEngine?: () => string; - setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; + setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; // property overrides for child documents children?: never | (() => JSX.Element[]) | React.ReactNode; @@ -84,7 +85,7 @@ export interface CollectionViewProps extends FieldViewProps { type CollectionDocument = makeInterface<[typeof documentSchema]>; const CollectionDocument = makeInterface(documentSchema); @observer -export class CollectionView extends ViewBoxAnnotatableComponent(CollectionDocument, "") { +export class CollectionView extends ViewBoxAnnotatableComponent(CollectionDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } @observable private static _safeMode = false; @@ -92,6 +93,11 @@ export class CollectionView extends ViewBoxAnnotatableComponent this._annotationKeySuffix = returnEmptyString); + } + get collectionViewType(): CollectionViewType | undefined { const viewField = StrCast(this.layoutDoc._viewType); if (CollectionView._safeMode) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 437a3cba8..81f6307d1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -41,7 +41,7 @@ interface MarqueeViewProps { trySelectCluster: (addToSel: boolean) => boolean; nudge?: (x: number, y: number, nudgeTime?: number) => boolean; ungroup?: () => void; - setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; + setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; } @observer export class MarqueeView extends React.Component @@ -211,7 +211,7 @@ export class MarqueeView extends React.Component { - if (drag) { + setPreviewCursor = action((x: number, y: number, drag: boolean, hide: boolean) => { + if (hide) { + this._downX = this._lastX = x; + this._downY = this._lastY = y; + this._commandExecuted = false; + PreviewCursor.Visible = false; + } else if (drag) { this._downX = this._lastX = x; this._downY = this._lastY = y; this._commandExecuted = false; @@ -313,7 +318,7 @@ export class MarqueeView extends React.Component this.onClickHandler; setHeight = (height: number) => this.layoutDoc._height = height; setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; - isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() ? true : false; + isContentActive = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.isContentActive() ? true : false; @computed get contents() { TraceMobx(); const audioView = !this.layoutDoc._showAudio ? (null) : @@ -792,7 +792,7 @@ export class DocumentViewInternal extends DocComponent; return
{this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} {this.hideLinkButton ? (null) : -
+
} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 2c0106960..c4e74ebd2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,19 +1,21 @@ -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, 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 { ObjectField } from '../../../fields/ObjectField'; import { createSchema, makeInterface } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, OmitKeys, returnOne, Utils, returnFalse } from '../../../Utils'; +import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Networking } from '../../Network'; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; @@ -21,15 +23,13 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; +import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { InkTool } from '../../../fields/InkField'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { AnchorMenu } from '../pdf/AnchorMenu'; -import { Docs } from '../../documents/Documents'; +import { SnappingManager } from '../../util/SnappingManager'; const path = require('path'); export const pageSchema = createSchema({ @@ -294,7 +294,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent {fadepath === srcpath ? (null) :
; } - @action marqueeDown = (e: React.PointerEvent) => { if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { - this._marqueeing = [e.clientX, e.clientY]; - e.stopPropagation(); + setupMoveUpEvents(this, e, action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations) + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); } } @action diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index f0db0b594..7ad96bf05 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -175,8 +175,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { this._videoRef = r; setTimeout(() => { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index fc97a6f4d..d4df30b48 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -9,7 +9,7 @@ import { InkTool } from "../../../fields/InkField"; import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { AudioField, nullAudio, VideoField } from "../../../fields/URLField"; -import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils, returnFalse } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -209,11 +209,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.props.isSelected(), - selected => !selected && setTimeout(() => { - Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - })); this._disposers.triggerVideo = reaction( () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined, time => time !== undefined && setTimeout(() => { @@ -550,9 +545,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent; } - marqueeDown = action((e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) this._marqueeing = [e.clientX, e.clientY]; - }); + marqueeDown = (e: React.PointerEvent) => { + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + setupMoveUpEvents(this, e, action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); + } + } finishMarquee = action(() => { this._marqueeing = undefined; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2ff41c73a..0c32a30db 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -44,7 +44,7 @@ const WebDocument = makeInterface(documentSchema); export class WebBox extends ViewBoxAnnotatableComponent(WebDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } public static openSidebarWidth = 250; - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); + private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); private _mainCont: React.RefObject = React.createRef(); private _outerRef: React.RefObject = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -52,16 +52,16 @@ export class WebBox extends ViewBoxAnnotatableComponent(); private _initialScroll: Opt; private _sidebarRef = React.createRef(); - @observable private _urlHash: string = ""; @observable private _scrollTimer: any; @observable private _overlayAnnoInfo: Opt; @observable private _marqueeing: number[] | undefined; - @observable private _url: string = "hello"; @observable private _isAnnotating = false; @observable private _iframeClick: HTMLIFrameElement | undefined = undefined; @observable private _iframe: HTMLIFrameElement | null = null; @observable private _savedAnnotations = new ObservableMap(); @observable private _scrollHeight = 1500; + @computed get _url() { return this.webField?.toString() || ""; } + @computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; } @computed get scrollHeight() { return this._scrollHeight; } @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } @@ -82,9 +82,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - this._url = this.webField?.toString() || ""; - this._urlHash = WebBox.urlHash(this._url) + ""; - this._annotationKey = this._urlHash + "-annotations"; + this._annotationKeySuffix = () => this._urlHash + "-annotations"; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`); this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`); @@ -214,6 +212,8 @@ export class WebBox extends ViewBoxAnnotatableComponent([...history, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!)); - this._urlHash = WebBox.urlHash(this._url) + ""; - this._annotationKey = this._urlHash + "-annotations"; + this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); return true; } return false; @@ -321,9 +319,7 @@ export class WebBox extends ViewBoxAnnotatableComponent([this._url]); else this.dataDoc[this.fieldKey + "-future"] = new List([...future, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!)); - this._urlHash = WebBox.urlHash(this._url) + ""; - this._annotationKey = this._urlHash + "-annotations"; + this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); return true; } return false; @@ -342,17 +338,10 @@ export class WebBox extends ViewBoxAnnotatableComponent([url]); - } else { - this.dataDoc[this.fieldKey + "-history"] = new List([...history, url]); - } + this.dataDoc[this.fieldKey + "-history"] = new List([...(history || []), url]); this.layoutDoc._scrollTop = 0; future && (future.length = 0); } - this._url = newUrl; - this._urlHash = WebBox.urlHash(this._url) + ""; - this._annotationKey = this._urlHash + "-annotations"; if (!preview) this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl)); } catch (e) { console.log("WebBox URL error:" + this._url); @@ -413,15 +402,18 @@ export class WebBox extends ViewBoxAnnotatableComponent { if (!e.altKey && e.button === 0 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { - this._marqueeing = [e.clientX, e.clientY]; - this.props.select(false); + setupMoveUpEvents(this, e, action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); } } @action finishMarquee = (x?: number, y?: number) => { this._marqueeing = undefined; this._isAnnotating = false; this._iframeClick = undefined; - x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false); + x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false); } @computed @@ -511,7 +503,7 @@ export class WebBox extends ViewBoxAnnotatableComponent) => this._overlayAnnoInfo = anno); - setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; + setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index ee553fd43..41a60bedf 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -69,7 +69,7 @@ export class PDFViewer extends React.Component { private _pdfViewer: any; private _styleRule: any; // stylesheet rule for making hyperlinks clickable private _retries = 0; // number of times tried to create the PDF viewer - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); + private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); private _annotationLayer: React.RefObject = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewer: React.RefObject = React.createRef(); @@ -371,10 +371,11 @@ export class PDFViewer extends React.Component { this._downY = e.clientY; if ((this.props.Document._viewScale || 1) !== 1) return; if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) { - this._setPreviewCursor?.(e.clientX, e.clientY, true); + this._setPreviewCursor?.(e.clientX, e.clientY, true, false); } if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { this.props.select(false); + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) { this._textSelecting = false; @@ -382,10 +383,7 @@ export class PDFViewer extends React.Component { } else { // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee. setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. - // clear out old marquees and initialize menu for new selection - AnchorMenu.Instance.Status = "marquee"; - Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); + this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "htmlAnnotation", { "pointer-events": "none" }); document.addEventListener("pointerup", this.onSelectEnd); document.addEventListener("pointermove", this.onSelectMove); @@ -454,12 +452,12 @@ export class PDFViewer extends React.Component { if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - this._setPreviewCursor(e.clientX, e.clientY, false); + this._setPreviewCursor(e.clientX, e.clientY, false, false); } // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks } - setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; + setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; getCoverImage = () => { if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) { -- cgit v1.2.3-70-g09d2 From 44a2517ca0f153e63a75bfdef5e624ac24acaa28 Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Tue, 24 Aug 2021 08:19:42 -0400 Subject: icon updates --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/nodes/button/FontIconBox.scss | 13 +++++++++++ src/client/views/nodes/button/FontIconBox.tsx | 30 ++++++++++++-------------- 3 files changed, 28 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c7aa57ff0..c305a495c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -989,7 +989,7 @@ export class CurrentUserUtils { { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack()' }, { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward()' }, { title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, - { title: "URL", toolTip: "URL", width: 150, btnType: ButtonType.EditableText, icon: "lock", script: 'webSetURL' }, + { title: "URL", toolTip: "URL", width: 150, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL' }, ]; return tools; diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index c1f5282d0..b6aa2ba60 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -209,6 +209,19 @@ } } + &.editableTxt { + cursor: pointer; + background: transparent; + width: 100%; + height: 100%; + + &:hover { + background-color: $close-red; + } + + + } + &.drpDownBtn { cursor: pointer; background: transparent; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 46d92cd6e..054e55c56 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -152,7 +152,6 @@ export class FontIconBox extends DocComponent(Fon let noviceList: string[] = []; let text: string | undefined; let dropdown = true; - let show = true; let icon: IconProp = "caret-down"; @@ -164,6 +163,8 @@ export class FontIconBox extends DocComponent(Fon dropdown = false; text = StrCast(selected.Document.type); icon = Doc.toIcon(selected.Document); + } else { + text = "None selected"; } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; } else if (script === 'changeFont') { @@ -175,8 +176,6 @@ export class FontIconBox extends DocComponent(Fon } noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; - } else { - show = false; } const items = this.buttonList.map((value) => { @@ -206,7 +205,7 @@ export class FontIconBox extends DocComponent(Fon return (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
{text && text[0].toUpperCase() + text.slice(1)} @@ -269,7 +268,7 @@ export class FontIconBox extends DocComponent(Fon }; return (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} onPointerDown={e => e.stopPropagation()}> @@ -315,9 +314,16 @@ export class FontIconBox extends DocComponent(Fon
{this.label}
; - + console.log("switchToggle"); return ( - switchToggle ? + !switchToggle ? +
+ + {label} +
:
- : -
- - {label} -
); } @@ -365,7 +363,7 @@ export class FontIconBox extends DocComponent(Fon }; return
- TEST + ""} SetValue={setValue} contents="...">
; -- cgit v1.2.3-70-g09d2 From 7bad6629a38f2e338be2150d86248d06a4fddda5 Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Tue, 24 Aug 2021 09:31:32 -0400 Subject: create text document --- src/client/documents/Documents.ts | 2 + src/client/util/CurrentUserUtils.ts | 19 ++++--- src/client/views/EditableView.tsx | 1 + src/client/views/nodes/button/FontIconBox.tsx | 81 ++++++++++++++++++++++----- 4 files changed, 81 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 57015e241..9ba31da3b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1151,6 +1151,8 @@ export namespace DocUtils { description: ":" + StrCast(note.title), event: undoBatch((args: { x: number, y: number }) => { const textDoc = Docs.Create.TextDocument("", { + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), + _fontSize: StrCast(Doc.UserDoc()._fontSize), _width: 200, x, y, _autoHeight: note._autoHeight !== false, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c305a495c..8abac9975 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -37,7 +37,7 @@ import { ColorScheme } from "./SettingsManager"; import { SharingManager } from "./SharingManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; -import { ButtonType } from "../views/nodes/button/FontIconBox"; +import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { IconName } from "@fortawesome/fontawesome-svg-core"; interface Button { @@ -46,6 +46,7 @@ interface Button { icon?: string; btnType?: ButtonType; click?: string; + numType?: NumButtonType; switchToggle?: boolean; script?: string; width?: number; @@ -939,7 +940,7 @@ export class CurrentUserUtils { "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], script: 'changeFont' }, - { title: "Font size", toolTip: "Font size", btnType: ButtonType.NumberButton, script: 'changeFontSize'}, + { title: "Font size", toolTip: "Font size", btnType: ButtonType.NumberButton, numType: NumButtonType.DropdownOptions, script: 'changeFontSize'}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' }, { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' }, @@ -958,11 +959,12 @@ export class CurrentUserUtils { static inkTools(doc: Doc) { const tools:Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'togglePen()', script: 'togglePen' }, - { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setHighlighter()', script: 'toggleHighlighter' }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'toggleCircle()', script: 'toggleCircle' }, - { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'toggleSquare()', script: 'toggleSquare' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'toggleLine()', script: 'toggleLine' }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")'}, + { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")' }, + { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'setActiveInkTool("line")' }, + { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numType: NumButtonType.Slider, script: 'setLineWidth'}, ]; return tools; } @@ -1044,7 +1046,7 @@ export class CurrentUserUtils { default: break; } - tools.map(({ title, toolTip, icon, btnType, click, script, width, list, ignoreClick }) => { + tools.map(({ title, toolTip, icon, btnType, numType, click, script, width, list, ignoreClick }) => { menuDocList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, _nativeHeight: 25, @@ -1052,6 +1054,7 @@ export class CurrentUserUtils { _height: 25, icon, toolTip, + numType, script, btnType: btnType, btnList: new List(list), diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 03d9efff3..d5f8d3fcf 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -156,6 +156,7 @@ export class EditableView extends React.Component { } renderEditor() { + console.log("render editor", this.props.autosuggestProps); return this.props.autosuggestProps ? (Fon * - Number button **/ + _batch: UndoManager.Batch | undefined = undefined; /** * Number button */ @computed get numberButton() { - const numType: string = StrCast(this.rootDoc.numButtonType); + const numType: string = StrCast(this.rootDoc.numType); + const setValue = (value: number) => { + // Script for running the toggle + const script: string = StrCast(this.rootDoc.script) + "(" + value + ")"; + ScriptField.MakeScript(script)?.script.run(); + }; + + // Script for checking the outcome of the toggle + const checkScript: string = StrCast(this.rootDoc.script) + "(true)"; + const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result; - const dropdown = numType ? -
+ const dropdown = +
{/* DROPDOWN BOX CONTENTS */} -
: null; +
; + + if (numType === NumButtonType.Slider) { + return ( +
+ { this._batch = UndoManager.StartBatch("presDuration"); }} + onPointerUp={() => { if (this._batch) this._batch.end(); }} + onChange={(e: React.ChangeEvent) => { e.stopPropagation(); setValue(Number(e.target.value)); }} + /> +
+ ); + } else if (numType === NumButtonType.DropdownOptions) { + return ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} + > +
+ +
+
+ setValue(Number(e.target.value)))} + /> +
+
+ +
+ {this.rootDoc.dropDownOpen ? dropdown : null} +
+ ); + } else { + return ( +
+ +
+ ); + } + - return ( -
- {this.rootDoc.dropDownOpen ? dropdown : null} -
- ); } /** @@ -172,7 +224,9 @@ export class FontIconBox extends DocComponent(Fon if (selected && StrCast(selected.Document.type) === DocumentType.RTF) { text = StrCast(selected.Document._fontFamily); } else { - text = StrCast(Doc.UserDoc()._fontFamily); + const fontFamily = StrCast(Doc.UserDoc()._fontFamily); + console.log(fontFamily); + text = fontFamily; } noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; @@ -485,9 +539,8 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { // toggle: Set overlay status of selected document Scripting.addGlobal(function changeFont(font: string) { SelectionManager.Views().map(dv => dv.props.Document._fontFamily = font); - console.log("[changeFont]: ", font); - console.log(RichTextMenu.Instance.getActiveMarksOnSelection()); Doc.UserDoc()._fontFamily = font; + console.log(Doc.UserDoc()._fontFamily); return Doc.UserDoc()._fontFamily; }); @@ -555,7 +608,7 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { return Doc.UserDoc().italic; }); -Scripting.addGlobal(function setActiveInkTool(checkResult?: boolean, tool?: InkTool) { +Scripting.addGlobal(function setActiveInkTool(tool: InkTool, checkResult?: boolean) { if (checkResult) { return Doc.UserDoc().activeInkTool === tool; } -- cgit v1.2.3-70-g09d2 From d1ea2f764d0207963788eb4238ede79bcffa23f6 Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Tue, 24 Aug 2021 10:48:38 -0400 Subject: more bits and pieces (ahhhh) --- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 22 ++++++--- src/client/util/SettingsManager.scss | 5 +- src/client/views/MainViewModal.scss | 4 +- src/client/views/global/globalCssVariables.scss | 1 + src/client/views/nodes/DocumentLinksButton.scss | 1 + src/client/views/nodes/DocumentLinksButton.tsx | 1 + src/client/views/nodes/button/FontIconBox.tsx | 66 ++++++++++++++++++------- 8 files changed, 71 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9ba31da3b..63acd004f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1151,8 +1151,7 @@ export namespace DocUtils { description: ":" + StrCast(note.title), event: undoBatch((args: { x: number, y: number }) => { const textDoc = Docs.Create.TextDocument("", { - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), - _fontSize: StrCast(Doc.UserDoc()._fontSize), + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), _width: 200, x, y, _autoHeight: note._autoHeight !== false, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8abac9975..73a66332f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -99,7 +99,7 @@ export class CurrentUserUtils { const slideTemplate = Docs.Create.MultirowDocument( [ Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true }) + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) ], { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } ); @@ -271,24 +271,28 @@ export class CurrentUserUtils { // setup the different note type skins static setupNoteTemplates(doc: Doc) { if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true }); + const noteView = Docs.Create.TextDocument("", { title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); doc["template-note-Note"] = new PrefetchProxy(noteView); } if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "pink", system: true }); + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "pink", system: true, + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); doc["template-note-Idea"] = new PrefetchProxy(noteView); } if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "lightblue", system: true }); + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "lightblue", system: true, + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); doc["template-note-Topic"] = new PrefetchProxy(noteView); } if (doc["template-note-Todo"] === undefined) { const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", - layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true + layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true, + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), }); noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); doc["template-note-Todo"] = new PrefetchProxy(noteView); @@ -472,7 +476,9 @@ export class CurrentUserUtils { ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), + cloneFieldFilter: new List(["system"]) }); ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyImage === undefined) { @@ -940,7 +946,7 @@ export class CurrentUserUtils { "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], script: 'changeFont' }, - { title: "Font size", toolTip: "Font size", btnType: ButtonType.NumberButton, numType: NumButtonType.DropdownOptions, script: 'changeFontSize'}, + { title: "Font size", toolTip: "Font size", btnType: ButtonType.NumberButton, numType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'changeFontSize'}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' }, { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' }, @@ -964,7 +970,7 @@ export class CurrentUserUtils { { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")' }, { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'setActiveInkTool("line")' }, - { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numType: NumButtonType.Slider, script: 'setLineWidth'}, + { title: "Stroke width", toolTip: "Stroke width", width:75, btnType: ButtonType.NumberButton, numType: NumButtonType.Slider, ignoreClick: true, script: 'setLineWidth'}, ]; return tools; } diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index c9db94419..b7199f433 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -360,17 +360,18 @@ flex-direction: row; position: relative; min-height: 250px; + height: 100%; width: 100%; .settings-content { - background-color: #fdfdfd; + background-color: $off-white; } } .settings-panel { position: relative; min-width: 150px; - background-color: #e4e4e4; + background-color: $light-blue; .settings-user { position: absolute; diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss index 5f19590b4..03cb5cc84 100644 --- a/src/client/views/MainViewModal.scss +++ b/src/client/views/MainViewModal.scss @@ -4,6 +4,7 @@ z-index: 10000; width: 100%; height: 100%; + .dialogue-box { position: absolute; z-index: 1000; @@ -11,11 +12,8 @@ justify-content: center; align-self: center; align-content: center; - padding: 20px; - // background: gainsboro; background: white; border-radius: 10px; - border: 0.5px solid black; box-shadow: #00000044 5px 5px 10px; transform: translate(-50%, -50%); top: 50%; diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index 2fd67a8dc..caa9f4fe5 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -1,6 +1,7 @@ @import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap"); // colors $white: #ffffff; +$off-white: #fdfdfd; $light-gray: #dfdfdf; $medium-gray: #9f9f9f; $dark-gray: #323232; diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index b37b68249..228e1bdcb 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -50,6 +50,7 @@ width: 80%; height: 80%; font-size: 100%; + font-family: 'Roboto'; transition: 0.2s ease all; &:hover { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 7648e866e..bf9499d1d 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -266,6 +266,7 @@ export class DocumentLinksButton extends React.Component diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 7ff3e388f..f78bb6842 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -106,21 +106,15 @@ export class FontIconBox extends DocComponent(Fon }; // Script for checking the outcome of the toggle - const checkScript: string = StrCast(this.rootDoc.script) + "(true)"; + const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)"; const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result; - const dropdown = -
- {/* DROPDOWN BOX CONTENTS */} -
; if (numType === NumButtonType.Slider) { - return ( + const dropdown =
(Fon onPointerUp={() => { if (this._batch) this._batch.end(); }} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); setValue(Number(e.target.value)); }} /> +
; + return ( +
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} + > + {checkResult} + {dropdown}
); } else if (numType === NumButtonType.DropdownOptions) { + const items: number[] = []; + for (let i = 0; i < 100; i++) { + if (i % 2 === 0) { + items.push(i); + } + } + items.map((value) => { + return
setValue(value)}> + {value} +
; + }); return (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} > -
+
setValue(checkResult - 1))}>
-
+
e.stopPropagation()} + onDoubleClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} + > setValue(Number(e.target.value)))} />
-
+
setValue(checkResult + 1))}>
- {this.rootDoc.dropDownOpen ? dropdown : null} + {this.rootDoc.dropDownOpen ? +
+
+ {items} +
+
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> +
: null} +
); } else { @@ -576,9 +604,13 @@ Scripting.addGlobal(function changeFontHighlight(color?: string, checkResult?: b // toggle: Set overlay status of selected document -Scripting.addGlobal(function changeFontSize(size: string) { +Scripting.addGlobal(function changeFontSize(size: string, checkResult?: boolean) { + if (checkResult) { + const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10); + return size; + } console.log(size); - Doc.UserDoc()._fontSize = size; + Doc.UserDoc()._fontSize = size + "px"; }); Scripting.addGlobal(function toggleBold(checkResult?: boolean) { -- cgit v1.2.3-70-g09d2 From addda2b98e450fcad1e2cb328788048ff2fe578f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Aug 2021 12:09:06 -0400 Subject: started to fix doc filters to work on any document --- src/client/views/PropertiesView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 ++ src/client/views/nodes/FilterBox.tsx | 30 +++++++++++++++++++----------- 3 files changed, 22 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index de437e1df..17b137c31 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -903,7 +903,7 @@ export class PropertiesView extends React.Component { * If it doesn't exist, it creates it. */ checkFilterDoc() { - if (this.selectedDoc.type === DocumentType.COL && !this.selectedDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.selectedDoc); + if (!this.selectedDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.selectedDoc); } /** diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 121d2d556..bb259da3e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -88,6 +88,8 @@ export interface DocComponentView { setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) playFrom?: (time: number, endTime?: number) => void; setFocus?: () => void; + fieldKey?: string; + annotationKey?: string; } export interface DocumentViewSharedProps { renderDepth: number; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index beefc4a82..1e13d1b5a 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -79,10 +79,19 @@ export class FilterBox extends ViewBoxBaseComponent(); - const activeTabs = DocListCast(targetDoc.data); + const activeTabs = FilterBox.targetDocChildren; SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc)); setTimeout(action(() => this._allDocs = Array.from(allDocs))); } @@ -133,8 +142,7 @@ export class FilterBox extends ViewBoxBaseComponent StrCast(attribute.title)); } - gatherFieldValues(dashboard: Doc, facetKey: string) { - const childDocs = DocListCast(dashboard.data); + gatherFieldValues(childDocs: Doc[], facetKey: string) { const valueSet = new Set(); let rtFields = 0; childDocs.forEach((d) => { @@ -194,13 +202,13 @@ export class FilterBox extends ViewBoxBaseComponent { - const { targetDoc } = FilterBox; + const { targetDoc, targetDocChildren } = FilterBox; const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader); if (found !== -1) { this.removeFilter(facetHeader); } else { - const allCollectionDocs = DocListCast((targetDoc.data as any)?.[0].data); - const facetValues = this.gatherFieldValues(targetDoc, facetHeader); + const allCollectionDocs = targetDocChildren; + const facetValues = this.gatherFieldValues(targetDocChildren, facetHeader); let nonNumbers = 0; let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; @@ -248,7 +256,7 @@ export class FilterBox extends ViewBoxBaseComponent(); - const activeTabs = DocListCast(layoutDoc.data); + const activeTabs = DocListCast(layoutDoc[childKey]); SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); const set = new Set(); if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key))); -- cgit v1.2.3-70-g09d2 From a38501569769c00dc1c25fb57d09ca88f3951c3d Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Aug 2021 12:21:01 -0400 Subject: from last --- src/client/views/collections/CollectionSubView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 2f07c0241..c7629acbe 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,6 +1,6 @@ import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import CursorField from "../../../fields/CursorField"; -import { Doc, Opt, Field, DocListCast, AclPrivate } from "../../../fields/Doc"; +import { Doc, Opt, Field, DocListCast, AclPrivate, StrListCast } from "../../../fields/Doc"; import { Id, ToString } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; @@ -88,7 +88,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: get childDocList() { return Cast(this.dataField, listSpec(Doc)); } - collectionFilters = () => this._focusFilters ?? Cast(this.props.Document._docFilters, listSpec("string"), []); + collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters); collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec("string"), []); childDocFilters = () => [...this.props.docFilters(), ...this.collectionFilters()]; childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()]; -- cgit v1.2.3-70-g09d2 From 5867ca16f8a8beaf7daf754ee5c42a5cc249346f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Aug 2021 14:28:13 -0400 Subject: fixed undo for dragging docs. made separate layers for transparent and other annotations on pdfs/webs so that transparency will work better. --- src/Utils.ts | 10 ++++++++ src/client/util/DragManager.ts | 12 +++++----- src/client/views/nodes/WebBox.tsx | 48 +++++++++++++++++++++----------------- src/client/views/pdf/PDFViewer.tsx | 18 +++++++++----- src/fields/Doc.ts | 5 ++++ 5 files changed, 60 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index f251f776c..f90296121 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -115,6 +115,16 @@ export namespace Utils { return { r: r, g: g, b: b, a: a }; } + export function IsTransparentFilter() { + // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one + return ["backgroundColor:isTransparent(__value__):check"]; + } + export function IsOpaqueFilter() { + // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one + return ["backgroundColor:isTransparent(__value__):x"]; + } + + export function toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")"; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 5e16de617..f7ef9ae6f 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -425,10 +425,10 @@ export namespace DragManager { AbortDrag = () => { options?.dragComplete?.(new DragCompleteEvent(true, dragData)); - endDrag(); + cleanupDrag(); }; - const endDrag = action(() => { + const cleanupDrag = action(() => { hideDragShowOriginalElements(false); document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); @@ -518,15 +518,14 @@ export namespace DragManager { `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) ); }; - const upHandler = async (e: PointerEvent) => { - dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options); - endDrag(); + const upHandler = (e: PointerEvent) => { + dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, cleanupDrag); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } - async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions) { + async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { const dropArgs = { bubbles: true, detail: { @@ -543,5 +542,6 @@ export namespace DragManager { await finishDrag?.(complete); target.dispatchEvent(new CustomEvent("dashOnDrop", dropArgs)); options?.dragComplete?.(complete); + endDrag?.(); } } \ No newline at end of file diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 0c32a30db..94697464a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -512,6 +512,29 @@ export class WebBox extends ViewBoxAnnotatableComponent string[]) => + ; return (
@@ -529,27 +552,10 @@ export class WebBox extends ViewBoxAnnotatableComponent
{this.content} - +
+ {renderAnnotations(Utils.IsTransparentFilter)} +
+ {renderAnnotations(Utils.IsOpaqueFilter)} {this.annotationLayer}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 41a60bedf..2c00b005d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -507,12 +507,7 @@ export class PDFViewer extends React.Component { panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); @computed get overlayLayer() { - return
anno.mixBlendMode) ? "hard-light" : undefined, - transform: `scale(${this._zoomed})` - }}> + const renderAnnotations = (docFilters: () => string[]) => { select={emptyFunction} ContentScaling={this.contentZoom} bringToFront={emptyFunction} + docFilters={doFilters} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} renderDepth={this.props.renderDepth + 1} childPointerEvents={true} /> + return
anno.mixBlendMode) ? "hard-light" : undefined, + transform: `scale(${this._zoomed})` + }}> +
+ {renderAnnotations(Utils.IsTransparentFilter)} +
+ {renderAnnotations(Utils.IsOpaqueFilter)}
; } @computed get pdfViewerDiv() { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index e4087cf43..f6efefdf9 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -25,6 +25,7 @@ import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, m import JSZip = require("jszip"); import { CurrentUserUtils } from "../client/util/CurrentUserUtils"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import Color = require("color"); export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -1087,6 +1088,10 @@ export namespace Doc { } export function matchFieldValue(doc: Doc, key: string, value: any): boolean { + if (value === "isTransparent(__value__)") { + const isTransparent = (color: string) => color !== "" && (Color(color).alpha() !== 1); + return isTransparent(StrCast(doc[key])); + } const fieldVal = doc[key]; if (Cast(fieldVal, listSpec("string"), []).length) { const vals = Cast(fieldVal, listSpec("string"), []); -- cgit v1.2.3-70-g09d2 From 66dd6788fbf46cd827d80eb97810e4d2af24eb26 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Aug 2021 14:52:35 -0400 Subject: suppressed filter icon for annotations that are filtered based on transparency. fixed drawing ink on webbox's. --- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index c7629acbe..55b1e4031 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -93,7 +93,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: childDocFilters = () => [...this.props.docFilters(), ...this.collectionFilters()]; childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()]; IsFiltered = () => this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" : - this.props.docFilters().length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined + this.props.docFilters().filter(f => !f.includes("__value__")).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 94697464a..cdc79678a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -534,7 +534,7 @@ export class WebBox extends ViewBoxAnnotatableComponent; + pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />; return (
-- cgit v1.2.3-70-g09d2 From e221001a24e8615aa6113dd3f25b8c6e10c74999 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Aug 2021 15:10:48 -0400 Subject: fixed pdf mixBlendMode for sem transparent annos. changed ink to have no backgound by default - transparent backgroudn will make trokes mixBlend as multiply --- src/client/documents/Documents.ts | 1 - src/client/views/pdf/PDFViewer.tsx | 25 ++++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f8e9e8702..a579fad4d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -694,7 +694,6 @@ export namespace Docs { I.title = "ink"; I.x = options.x; I.y = options.y; - I._backgroundColor = "transparent"; I._width = options._width as number; I._height = options._height as number; I._fontFamily = "cursive"; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 2c00b005d..0261d24d9 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -519,21 +519,28 @@ export class PDFViewer extends React.Component { select={emptyFunction} ContentScaling={this.contentZoom} bringToFront={emptyFunction} - docFilters={doFilters} + docFilters={docFilters} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} renderDepth={this.props.renderDepth + 1} childPointerEvents={true} /> - return
anno.mixBlendMode) ? "hard-light" : undefined, - transform: `scale(${this._zoomed})` - }}> -
+ return
+
{renderAnnotations(Utils.IsTransparentFilter)}
- {renderAnnotations(Utils.IsOpaqueFilter)} +
anno.mixBlendMode) ? "hard-light" : undefined, + transform: `scale(${this._zoomed})` + }}> + {renderAnnotations(Utils.IsOpaqueFilter)} +
; } @computed get pdfViewerDiv() { -- cgit v1.2.3-70-g09d2 From 325d3a8c931cb9ff7eadfa29603a3c7ca747911b Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Tue, 24 Aug 2021 18:10:13 -0400 Subject: css changes for toggle button --- src/client/documents/Documents.ts | 4 + src/client/util/CurrentUserUtils.ts | 15 ++- src/client/views/nodes/button/FontIconBox.scss | 177 +++++++++++++++---------- src/client/views/nodes/button/FontIconBox.tsx | 85 +++++++----- 4 files changed, 176 insertions(+), 105 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 63acd004f..0b33943d6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -224,6 +224,10 @@ export class DocumentOptions { userColorBtn?: string; canClick?: string; script?: string; + numBtnType?: string; + numBtnMax?: number; + numBtnMin?: number; + switchToggle?: boolean; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 73a66332f..8044f59f0 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -46,7 +46,9 @@ interface Button { icon?: string; btnType?: ButtonType; click?: string; - numType?: NumButtonType; + numBtnType?: NumButtonType; + numBtnMin?: number; + numBtnMax?: number; switchToggle?: boolean; script?: string; width?: number; @@ -946,7 +948,7 @@ export class CurrentUserUtils { "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], script: 'changeFont' }, - { title: "Font size", toolTip: "Font size", btnType: ButtonType.NumberButton, numType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'changeFontSize'}, + { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'changeFontSize'}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' }, { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' }, @@ -970,7 +972,7 @@ export class CurrentUserUtils { { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")' }, { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'setActiveInkTool("line")' }, - { title: "Stroke width", toolTip: "Stroke width", width:75, btnType: ButtonType.NumberButton, numType: NumButtonType.Slider, ignoreClick: true, script: 'setLineWidth'}, + { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, ignoreClick: true, script: 'setStrokeWidth'}, ]; return tools; } @@ -982,6 +984,7 @@ export class CurrentUserUtils { title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, + width: 50, switchToggle: true, icon: "eye", click: 'toggleSchemaShow()', @@ -1050,9 +1053,10 @@ export class CurrentUserUtils { tools = CurrentUserUtils.textTools(doc); break; default: + tools = CurrentUserUtils.textTools(doc); break; } - tools.map(({ title, toolTip, icon, btnType, numType, click, script, width, list, ignoreClick }) => { + tools.map(({ title, toolTip, icon, btnType, numBtnType, click, script, width, list, ignoreClick, switchToggle }) => { menuDocList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, _nativeHeight: 25, @@ -1060,7 +1064,7 @@ export class CurrentUserUtils { _height: 25, icon, toolTip, - numType, + numBtnType, script, btnType: btnType, btnList: new List(list), @@ -1070,6 +1074,7 @@ export class CurrentUserUtils { system: true, dontUndo: true, title, + switchToggle, color: Colors.WHITE, backgroundColor: "transparent", _dropAction: "alias", diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index b6aa2ba60..7a6887d38 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -52,6 +52,71 @@ &.tglBtn { cursor: pointer; + &.switch { + //TOGGLE + + .switch { + position: relative; + display: inline-block; + width: 100%; + height: 25px; + margin: 0; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: lightgrey; + -webkit-transition: .4s; + transition: .4s; + } + + .slider:before { + position: absolute; + content: ""; + height: 21px; + width: 21px; + left: 2px; + bottom: 2px; + background-color: $white; + -webkit-transition: .4s; + transition: .4s; + } + + input:checked+.slider { + background-color: $medium-blue; + } + + input:focus+.slider { + box-shadow: 0 0 1px $medium-blue; + } + + input:checked+.slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); + } + + /* Rounded sliders */ + .slider.round { + border-radius: $standard-border-radius; + } + + .slider.round:before { + border-radius: $standard-border-radius; + } + } + svg { width: 50% !important; height: 50%; @@ -115,7 +180,7 @@ } &.drpdownList { - width: 100px; + width: 100%; display: grid; grid-auto-columns: 80px 20px; justify-items: center; @@ -165,7 +230,16 @@ cursor: pointer; background: transparent; + &:hover { + background-color: rgba(0, 0, 0, 0.3) !important; + } + &.slider { + color: $white; + cursor: pointer; + flex-direction: column; + background: transparent; + .numberBtn-slider {} .menuButton-dropdownBox { @@ -180,11 +254,43 @@ } } - &.dropdown { + .button { + width: 30%; + display: flex; + align-items: center; + justify-content: center; + + &.number { + width: 40%; + + .button-input { + background: none; + border: none; + text-align: right; + width: 100%; + color: black; + height: 100%; + text-align: center; + } + + .button-input:focus { + outline: none; + } + } + } + + &.list { + width: 100%; + justify-content: space-around; + border: $standard-border; + .menuButton-dropdownList { position: absolute; width: fit-content; height: fit-content; + min-width: 50px; + max-height: 50vh; + overflow: scroll; top: 100%; z-index: 21; background-color: $white; @@ -197,9 +303,8 @@ height: 25px; font-weight: 400; display: flex; - justify-content: left; + justify-content: center; align-items: center; - padding-left: 5px; } .list-item:hover { @@ -219,7 +324,6 @@ background-color: $close-red; } - } &.drpDownBtn { @@ -274,67 +378,4 @@ position: fixed; } -} - - -//TOGGLE - -.switch { - position: relative; - display: inline-block; - width: 50px; - height: 25px; -} - -.switch input { - opacity: 0; - width: 0; - height: 0; -} - -.slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: lightgrey; - -webkit-transition: .4s; - transition: .4s; -} - -.slider:before { - position: absolute; - content: ""; - height: 22px; - width: 22px; - left: 4px; - bottom: 4px; - background-color: white; - -webkit-transition: .4s; - transition: .4s; -} - -input:checked+.slider { - background-color: $medium-blue; -} - -input:focus+.slider { - box-shadow: 0 0 1px $medium-blue; -} - -input:checked+.slider:before { - -webkit-transform: translateX(26px); - -ms-transform: translateX(26px); - transform: translateX(26px); -} - -/* Rounded sliders */ -.slider.round { - border-radius: $standard-border-radius; -} - -.slider.round:before { - border-radius: $standard-border-radius; } \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index f78bb6842..4778c2f10 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -44,7 +44,7 @@ export enum ButtonType { export enum NumButtonType { Slider = "slider", - DropdownOptions = "dropdown", + DropdownOptions = "list", Inline = "inline" } @@ -98,7 +98,7 @@ export class FontIconBox extends DocComponent(Fon * Number button */ @computed get numberButton() { - const numType: string = StrCast(this.rootDoc.numType); + const numBtnType: string = StrCast(this.rootDoc.numBtnType); const setValue = (value: number) => { // Script for running the toggle const script: string = StrCast(this.rootDoc.script) + "(" + value + ")"; @@ -110,14 +110,14 @@ export class FontIconBox extends DocComponent(Fon const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result; - if (numType === NumButtonType.Slider) { + if (numBtnType === NumButtonType.Slider) { const dropdown =
{ this._batch = UndoManager.StartBatch("presDuration"); }} onPointerUp={() => { if (this._batch) this._batch.end(); }} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); setValue(Number(e.target.value)); }} @@ -125,21 +125,21 @@ export class FontIconBox extends DocComponent(Fon
; return (
this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} > {checkResult} - {dropdown} + {this.rootDoc.dropDownOpen ? dropdown : null}
); - } else if (numType === NumButtonType.DropdownOptions) { + } else if (numBtnType === NumButtonType.DropdownOptions) { const items: number[] = []; for (let i = 0; i < 100; i++) { if (i % 2 === 0) { items.push(i); } } - items.map((value) => { + const list = items.map((value) => { return
(Fon }); return (
-
setValue(checkResult - 1))}> - +
setValue(checkResult - 1))}> +
e.stopPropagation()} + className={`button ${'number'}`} + onPointerDown={(e) => { + e.stopPropagation(); + e.preventDefault(); + }} onDoubleClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} > setValue(Number(e.target.value)))} />
-
setValue(checkResult + 1))}> +
setValue(checkResult + 1))}>
{this.rootDoc.dropDownOpen ?
- {items} + {list}
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
: null} @@ -396,17 +400,12 @@ export class FontIconBox extends DocComponent(Fon
{this.label}
; - console.log("switchToggle"); - return ( - !switchToggle ? -
- - {label} -
: -
- ); + ); + } else { + return ( +
+ + {label} +
+ ); + } } @@ -444,11 +454,13 @@ export class FontIconBox extends DocComponent(Fon return true; }; - return
- - ""} SetValue={setValue} contents="..."> - -
; + return ( +
+ + ""} SetValue={setValue} contents="..."> + +
+ ); } @@ -649,4 +661,13 @@ Scripting.addGlobal(function setActiveInkTool(tool: InkTool, checkResult?: boole } else { Doc.UserDoc().activeInkTool = InkTool.None; } +}); + +Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean) { + if (checkResult) { + const width: number = NumCast(Doc.UserDoc().activeInkWidth); + return width; + } + console.log("[width]: " + width); + Doc.UserDoc().activeInkWidth = width; }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 0c5a95f37e91a07041699514155fd214f544a0de Mon Sep 17 00:00:00 2001 From: geireann Date: Tue, 24 Aug 2021 19:06:43 -0400 Subject: updates --- src/client/util/CurrentUserUtils.ts | 14 ++++++----- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.scss | 15 ++++++------ src/client/views/nodes/button/FontIconBox.tsx | 32 ++++++++++++++++++-------- 4 files changed, 40 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8044f59f0..20274672a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -967,11 +967,11 @@ export class CurrentUserUtils { static inkTools(doc: Doc) { const tools:Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")'}, - { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")' }, - { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'setActiveInkTool("line")' }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', script: 'setActiveInkTool("pen" , true)'}, + { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', script:'setActiveInkTool("highlighter", true)' }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', script: 'setActiveInkTool("circle" , true)' }, + { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', script: 'setActiveInkTool("square" , true)' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'setActiveInkTool("line")', script: 'setActiveInkTool("line" , true)' }, { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, ignoreClick: true, script: 'setStrokeWidth'}, ]; return tools; @@ -987,7 +987,7 @@ export class CurrentUserUtils { width: 50, switchToggle: true, icon: "eye", - click: 'toggleSchemaShow()', + ignoreClick: true, script: 'toggleSchemaShow' }, ]; @@ -1071,6 +1071,7 @@ export class CurrentUserUtils { ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, + _lockedPosition: true, system: true, dontUndo: true, title, @@ -1097,6 +1098,7 @@ export class CurrentUserUtils { ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, + _lockedPosition: true, system: true, dontUndo: true, title, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5c1408148..75c57909d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -331,7 +331,7 @@ export class MainView extends React.Component { PanelHeight={this.getContentsHeight} renderDepth={0} isContentActive={returnTrue} - scriptContext={CollectionDockingView.Instance.props.Document} + scriptContext={CollectionDockingView.Instance?.props.Document} focus={DocUtils.DefaultFocus} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 7a6887d38..dfb549b95 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -240,7 +240,9 @@ flex-direction: column; background: transparent; - .numberBtn-slider {} + .menu-slider { + width: 100px; + } .menuButton-dropdownBox { position: absolute; @@ -255,20 +257,20 @@ } .button { - width: 30%; + width: 25%; display: flex; align-items: center; justify-content: center; &.number { - width: 40%; + width: 50%; .button-input { background: none; border: none; text-align: right; width: 100%; - color: black; + color: $white; height: 100%; text-align: center; } @@ -288,9 +290,9 @@ position: absolute; width: fit-content; height: fit-content; - min-width: 50px; + min-width: 50%; max-height: 50vh; - overflow: scroll; + overflow-y: scroll; top: 100%; z-index: 21; background-color: $white; @@ -323,7 +325,6 @@ &:hover { background-color: $close-red; } - } &.drpDownBtn { diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 4778c2f10..744064b9f 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -25,6 +25,7 @@ import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '.././FieldView'; import { RichTextMenu } from '../formattedText/RichTextMenu'; import './FontIconBox.scss'; +import { SetActiveInkWidth } from '../../InkingStroke'; const FontIconSchema = createSchema({ icon: "string", }); @@ -177,7 +178,7 @@ export class FontIconBox extends DocComponent(Fon {this.rootDoc.dropDownOpen ?
+ style={{ left: "25%" }}> {list}
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> @@ -383,12 +384,19 @@ export class FontIconBox extends DocComponent(Fon const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle); // Script for running the toggle - const script: string = StrCast(this.rootDoc.script) + "()"; + const script: string = StrCast(this.rootDoc.script); + // Script for checking the outcome of the toggle - const checkScript: string = StrCast(this.rootDoc.script) + "(true)"; + let checkScript:string; + if (StrCast(this.rootDoc.script).includes("(")){ + checkScript = StrCast(this.rootDoc.script) + + } else { + checkScript = StrCast(this.rootDoc.script) + "(true)"; + } // Function to run the script - const runScript = () => ScriptField.MakeScript(script)?.script.run(); + const runScript = () => ScriptField.MakeScript(script + "()")?.script.run(); const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result; // Colors @@ -456,9 +464,10 @@ export class FontIconBox extends DocComponent(Fon return (
- + HELLO + {/* ""} SetValue={setValue} contents="..."> - + */}
); } @@ -492,6 +501,7 @@ export class FontIconBox extends DocComponent(Fon switch (this.type) { case ButtonType.EditableText: + console.log("Editable text"); button = this.editableText; case ButtonType.NumberButton: button = this.numberButton; @@ -657,7 +667,11 @@ Scripting.addGlobal(function setActiveInkTool(tool: InkTool, checkResult?: boole return Doc.UserDoc().activeInkTool === tool; } if (tool) { - Doc.UserDoc().activeInkTool = tool; + if (Doc.UserDoc().activeInkTool === tool){ + Doc.UserDoc().activeInkTool = InkTool.None; + } else { + Doc.UserDoc().activeInkTool = tool; + } } else { Doc.UserDoc().activeInkTool = InkTool.None; } @@ -668,6 +682,6 @@ Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean const width: number = NumCast(Doc.UserDoc().activeInkWidth); return width; } - console.log("[width]: " + width); - Doc.UserDoc().activeInkWidth = width; + SetActiveInkWidth(StrCast(width)); + SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(width)); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From bc90c2b624733c7175f9859a0f1fa3040dafe74b Mon Sep 17 00:00:00 2001 From: geireann Date: Tue, 24 Aug 2021 20:15:20 -0400 Subject: updates (final) --- src/client/util/CurrentUserUtils.ts | 2 -- src/client/views/MainView.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 20274672a..bae4e3825 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1071,7 +1071,6 @@ export class CurrentUserUtils { ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, - _lockedPosition: true, system: true, dontUndo: true, title, @@ -1098,7 +1097,6 @@ export class CurrentUserUtils { ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, - _lockedPosition: true, system: true, dontUndo: true, title, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 75c57909d..5c1408148 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -331,7 +331,7 @@ export class MainView extends React.Component { PanelHeight={this.getContentsHeight} renderDepth={0} isContentActive={returnTrue} - scriptContext={CollectionDockingView.Instance?.props.Document} + scriptContext={CollectionDockingView.Instance.props.Document} focus={DocUtils.DefaultFocus} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} -- cgit v1.2.3-70-g09d2 From 685f31eee39ba076c659fb90c5cb9e29d2d0e4dc Mon Sep 17 00:00:00 2001 From: geireann Date: Tue, 24 Aug 2021 23:27:24 -0400 Subject: Revert "updates (final)" This reverts commit bc90c2b624733c7175f9859a0f1fa3040dafe74b. --- src/client/util/CurrentUserUtils.ts | 2 ++ src/client/views/MainView.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index bae4e3825..20274672a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1071,6 +1071,7 @@ export class CurrentUserUtils { ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, + _lockedPosition: true, system: true, dontUndo: true, title, @@ -1097,6 +1098,7 @@ export class CurrentUserUtils { ignoreClick: ignoreClick, _stayInCollection: true, _hideContextMenu: true, + _lockedPosition: true, system: true, dontUndo: true, title, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5c1408148..75c57909d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -331,7 +331,7 @@ export class MainView extends React.Component { PanelHeight={this.getContentsHeight} renderDepth={0} isContentActive={returnTrue} - scriptContext={CollectionDockingView.Instance.props.Document} + scriptContext={CollectionDockingView.Instance?.props.Document} focus={DocUtils.DefaultFocus} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} -- cgit v1.2.3-70-g09d2 From 6376a4b6d6a730c6c170fa25dd5f19dea6b8f82a Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 25 Aug 2021 05:20:50 -0400 Subject: many updates --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 73 ++++--- src/client/util/LinkManager.ts | 7 +- src/client/util/Scripting.ts | 6 - src/client/views/EditableView.tsx | 2 + src/client/views/GestureOverlay.tsx | 5 +- src/client/views/InkingStroke.tsx | 4 +- src/client/views/MainView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collectionLinearView/CollectionLinearView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 12 +- src/client/views/nodes/button/FontIconBox.scss | 3 +- src/client/views/nodes/button/FontIconBox.tsx | 238 ++++++++++++++------- .../views/nodes/formattedText/RichTextMenu.tsx | 1 + src/fields/InkField.ts | 6 +- 15 files changed, 234 insertions(+), 133 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1c4b5218c..f3a472329 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -698,14 +698,14 @@ export namespace Docs { return linkDoc; } - export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { + export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { const I = new Doc(); I[Initializing] = true; I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString("data"); I.color = color; I.fillColor = fillColor; - I.strokeWidth = Number(strokeWidth); + I.strokeWidth = strokeWidth; I.strokeBezier = strokeBezier; I.strokeStartMarker = arrowStart; I.strokeEndMarker = arrowEnd; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 20274672a..7d1cbebdf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -946,21 +946,19 @@ export class CurrentUserUtils { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - script: 'changeFont' + script: 'setFont' }, - { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'changeFontSize'}, + { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize'}, + { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script:'setFontColor' }, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', script: 'toggleBold' }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', script: 'toggleItalic' }, { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', script: 'toggleUnderline' }, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'}, - { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", ignoreClick: true, script:'changeAlignment("left")' }, - { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", ignoreClick: true, script:'changeAlignment("center")' }, - { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", ignoreClick: true, script:'changeAlignment("right")' }, - - { title: "Highlight", toolTip: "Highlight", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script:'changeFontColor' }, - { title: "Text color", toolTip: "Text color", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, script:'changeHighlightColor' }, + { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", ignoreClick: true, script:'setAlignment("left")' }, + { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", ignoreClick: true, script:'setAlignment("center")' }, + { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", ignoreClick: true, script:'setAlignment("right")' }, ]; return tools; } @@ -971,8 +969,10 @@ export class CurrentUserUtils { { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', script:'setActiveInkTool("highlighter", true)' }, { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', script: 'setActiveInkTool("circle" , true)' }, { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', script: 'setActiveInkTool("square" , true)' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "fill-drip", click: 'setActiveInkTool("line")', script: 'setActiveInkTool("line" , true)' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', script: 'setActiveInkTool("line" , true)' }, { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, ignoreClick: true, script: 'setStrokeWidth'}, + { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "minus", ignoreClick: true, script:'setStrokeColor' }, + ]; return tools; } @@ -1015,18 +1015,18 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid], - script: 'changeView', - }, + script: 'setView', + }, // Always show { title: "Background", toolTip: "Background", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", - script: "changeBackgroundColor" - }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', script:'toggleOverlay'}, - { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy" }, - { title: "Text", type: "textTools", subMenu: true }, - { title: "Ink & GFX", type: "inkTools", subMenu: true }, - { title: "Web", type: "webTools", subMenu: true }, - { title: "Schema", type: "schemaTools", subMenu: true } + script: "setBackgroundColor", hidden:'selectedDocumentType()' + }, // Only when a document is selected + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', script:'toggleOverlay', hidden:'selectedDocumentType(undefined, "freeform", true)'}, // Only when floating document is selected in freeform + { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden:'selectedDocumentType()' }, // Only when a document is selected + { title: "Text", type: "textTools", subMenu: true, expanded:'selectedDocumentType("rtf")' }, // Always available + { title: "Ink & GFX", type: "inkTools", subMenu: true, expanded:'selectedDocumentType("ink")' }, // Always available + { title: "Web", type: "webTools", subMenu: true, hidden:'selectedDocumentType("web")' }, // Only when Web is selected + { title: "Schema", type: "schemaTools", subMenu: true, hidden:'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected ]; } @@ -1034,7 +1034,7 @@ export class CurrentUserUtils { static async setupContextMenuButtons(doc: Doc) { const docList: Doc[] = []; - (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu }) => { + (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => { const menuDocList: Doc[] = []; if (subMenu) { // default is textTools @@ -1083,7 +1083,17 @@ export class CurrentUserUtils { onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined })); }); - docList.push(CurrentUserUtils.linearButtonList({ linearViewSubMenu: true, flexGap: 5, ignoreClick: true, linearViewExpandable: true, icon:title, _height: 30, backgroundColor: "transparent" }, menuDocList)); + docList.push(CurrentUserUtils.linearButtonList({ + linearViewSubMenu: true, + flexGap: 5, + ignoreClick: true, + linearViewExpandable: true, + icon:title, + _height: 30, + backgroundColor: "transparent", + linearViewIsExpanded: expanded ? ComputedField.MakeFunction(expanded) as any : undefined, + hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined, + }, menuDocList)); } else { docList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, @@ -1105,6 +1115,7 @@ export class CurrentUserUtils { color: Colors.WHITE, backgroundColor: "transparent", _dropAction: "alias", + hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined, _removeDropProperties: new List(["dropAction", "_stayInCollection"]), onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined })); @@ -1177,7 +1188,7 @@ export class CurrentUserUtils { } if (doc.myImportPanel === undefined) { const uploads = Cast(doc.myImportDocs, Doc, null); - const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", type: ButtonType.ToolButton, icon: "upload", system: true }); + const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ToolButton, icon: "upload", system: true }); doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, _showTitle: "title", ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true, boxShadow: "0 0" })); } } @@ -1257,8 +1268,8 @@ export class CurrentUserUtils { doc._raiseWhenDragged = true; doc._showLabel = false; doc._showMenuLabel = true; - doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); - doc.activeInkWidth = StrCast(doc.activeInkWidth, "1"); + doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0, 0)"); + doc.activeInkWidth = NumCast(doc.activeInkWidth, 1); doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); doc.activeFillColor = StrCast(doc.activeFillColor, ""); doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); @@ -1279,7 +1290,7 @@ export class CurrentUserUtils { doc.filterDocCount = 0; this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates - this.setupImportSidebar(doc); + this.setupImportSidebar(doc); // sets up the import sidebar this.setupSearchSidebar(doc); // sets up the search sidebar this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile this.setupOverlays(doc); // documents in overlay layer @@ -1567,3 +1578,15 @@ Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) { } return []; }); +Scripting.addGlobal(function selectedDocumentType(docType?:DocumentType, colType?:CollectionViewType, checkParent?:boolean) { + let selected = SelectionManager.Views().length ? SelectionManager.Views()[0].rootDoc : undefined; + if (selected && checkParent){ + const parentDoc: Doc = Cast(selected.context, Doc, null); + selected = parentDoc; + } + console.log(selected, docType, colType); + if (selected && docType && selected.type === docType) return false; + else if (selected && colType && selected.viewType === colType) return false; + else if (selected && !colType && !docType) return false; + else return true; +}); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 3579083e4..219c83b12 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -107,7 +107,12 @@ export class LinkManager { public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor public getAllDirectLinks(anchor: Doc): Doc[] { - return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); + // FIXME:glr Why is Doc undefined? + if (Doc.GetProto(anchor)[DirectLinksSym]){ + return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); + } else { + return []; + } } // finds all links that contain the given anchor relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] { diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index aa7c23527..efee37419 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -171,18 +171,12 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an if (!options.editable) { batch = Doc.MakeReadOnly(); } - const result1 = compiledFunction.apply(thisParam, params); const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); if (batch) { batch.end(); } - if (script.includes('toggleOverlay(true)')){ - console.log("[Scripting.ts] r1: ", result1); - console.log("[Scripting.ts]: ", result); - } - return { success: true, result }; } catch (error) { diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d5f8d3fcf..83336c180 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -155,6 +155,8 @@ export class EditableView extends React.Component { return wasFocused !== this._editing; } + + renderEditor() { console.log("render editor", this.props.autosuggestProps); return this.props.autosuggestProps diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index bbf21f22c..24f9501ce 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -14,6 +14,7 @@ import { DocUtils } from "../documents/Documents"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { Scripting } from "../util/Scripting"; +import { SelectionManager } from "../util/SelectionManager"; import { Transform } from "../util/Transform"; import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; import "./GestureOverlay.scss"; @@ -23,7 +24,6 @@ import { RadialMenu } from "./nodes/RadialMenu"; import HorizontalPalette from "./Palette"; import { Touchable } from "./Touchable"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; -import { SelectionManager } from "../util/SelectionManager"; @observer export class GestureOverlay extends Touchable { @@ -588,8 +588,9 @@ export class GestureOverlay extends Touchable { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - if (!CollectionFreeFormViewChrome.Instance._keepPrimitiveMode) { + if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ""; + Doc.UserDoc().activeInkTool = InkTool.None; } } // if we're not drawing in a toolglass try to recognize as gesture diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 5fc159f14..db09849fd 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; import { makeInterface } from "../../fields/Schema"; -import { Cast, StrCast } from "../../fields/Types"; +import { Cast, StrCast, NumCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; @@ -168,7 +168,7 @@ export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activ export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); } export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); } export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); } -export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); } +export function ActiveInkWidth(): number { return NumCast(ActiveInkPen()?.activeInkWidth); } export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) { CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 75c57909d..dcdf6b9c3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -447,7 +447,6 @@ export class MainView extends React.Component { this._panelContent = "none"; this._sidebarContent.proto = undefined; this._flyoutWidth = 0; - console.log("close flyout"); }); remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d09d9b9d7..3c5a63c77 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -506,7 +506,7 @@ export class CollectionFreeFormView extends CollectionSubView { const nested = pair.layout._viewType === CollectionViewType.Linear; const dref = React.createRef(); + const hidden = pair.layout.hidden === true; // const scalable = pair.layout.onClick || pair.layout.onDragStart; - return