From 605137223d8afa0eaf8818de5b556601fca8441f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Thu, 14 Apr 2022 17:23:49 -0400 Subject: Create smooth record and playback of tracking. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..75855f5d9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -50,6 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import e = require("connect-flash"); export const panZoomSchema = createSchema({ _panX: "number", @@ -117,6 +118,8 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); } @@ -956,8 +959,37 @@ export class CollectionFreeFormView extends CollectionSubView { + // need the first for subtraction + let first = null; + + this.storedMovements.forEach(movement => { + if (first === null) first = movement.time; + + // set the pan to what was stored + setTimeout(() => { + this.Document._panX = movement.panX; + this.Document._panY = movement.panY; + }, movement.time - first) + }) + + // for now, clear the movements + this.storedMovements = [] + } + @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { + // if not presenting, just retrace the movements + if (Doc.UserDoc()?.presentationMode === 'watching') { + this.followMovements() + return; + } + + if (Doc.UserDoc()?.presentationMode === 'recording') { + // store as many movments as possible + this.storedMovements.push({time: new Date().getTime(), panX, panY}) + } + if (!this.isAnnotationOverlay && clamp) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); -- cgit v1.2.3-70-g09d2 From c1aead50030121554bf95ad392c80e042ec9c4d6 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 19 Apr 2022 14:53:16 -0400 Subject: Extracted view tracking into api class --- src/client/apis/recording/recordingApi.tsx | 151 +++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/client/apis/recording/recordingApi.tsx (limited to 'src/client/views/collections') diff --git a/src/client/apis/recording/recordingApi.tsx b/src/client/apis/recording/recordingApi.tsx new file mode 100644 index 000000000..55714f03b --- /dev/null +++ b/src/client/apis/recording/recordingApi.tsx @@ -0,0 +1,151 @@ +import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; +import React, { useState } from "react"; + +export function RecordingApi() { + + type Movement = { + time: number, + panX: number, + panY: number, + } + + type Presentation = { + movements: Array + meta: Object, + startDate: Date | null, + } + + const NULL_PRESENTATION = { + movements: [], + meta: {}, + startDate: null, + } + + const [currentPresentation, setCurrentPresenation] = useState(NULL_PRESENTATION) + const [isRecording, setIsRecording] = useState(false) + const [absoluteStart, setAbsoluteStart] = useState(-1) + + const initAndStart = (view: CollectionFreeFormView, meta?: Object): Error | undefined => { + // check if already init a presentation + if (currentPresentation.startDate !== null) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') + return new Error('[recordingApi.ts] start()') + } + + // (1a) get start date for presenation + const startDate = new Date() + // (1b) set start timestamp to absolute timestamp + setAbsoluteStart(startDate.getTime()) + + // TODO: (2) assign meta content + + // (3) assign init values to currentPresenation + setCurrentPresenation({ ...currentPresentation, startDate }) + + // (4) set isRecording true to allow trackMovements + setIsRecording(true) + } + + const clear = (): Error | undefined => { + // TODO: maybe archive the data? + if (isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first') + return new Error('[recordingApi.ts] clear()') + } + // clear presenation data + setCurrentPresenation(NULL_PRESENTATION) + + // clear isRecording + setIsRecording(false) + + // clear absoluteStart + setAbsoluteStart(-1) + } + + const pause = (): Error | undefined => { + if (currentPresentation.startDate === null) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] pause(): no presenation') + } + // don't allow track movments + setIsRecording(false) + + // set relativeStart to the pausedTimestamp + const timestamp = new Date().getTime() + setAbsoluteStart(timestamp) + } + + const resume = () => { + if (currentPresentation.startDate === null) { + console.error('[recordingApi.ts] resume() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] resume()') + } + + const timestamp = new Date().getTime() + const startTimestamp = currentPresentation.startDate?.getTime() + if (!startTimestamp) { + console.error('[recordingApi.ts] resume() failed: no presentation data. try calling init() first') + return new Error('[recordingApi.ts] pause()') + } + + setAbsoluteStart(prevTime => { + // const relativeUnpause = timestamp - absoluteStart + // const timePaused = relativeUnpause - prevTime + // return timePaused + absoluteStart + const absoluteTimePaused = timestamp - prevTime + return absoluteTimePaused + }) + } + + const finish = (): Error | Presentation => { + if (currentPresentation.movements === null) { + console.error('[recordingApi.ts] finish() failed: no presentation data. try calling init() first') + return new Error('[recordingApi.ts] finish()') + } + + // make copy and clear this class's data + const returnCopy = { ...currentPresentation } + clear() + + // return the copy + return returnCopy + } + + const trackMovements = (panX: number, panY: number): Error | undefined => { + // ensure we are recording + if (!isRecording) { + console.error('[recordingApi.ts] pause() failed: recording is paused()') + return new Error('[recordingApi.ts] pause()') + } + + // get the relative time + const timestamp = new Date().getTime() + const relativeTime = timestamp - absoluteStart + + // make new movement struct + const movement: Movement = { time: relativeTime, panX, panY } + + // add that movement struct to the current presentation data + setCurrentPresenation(prevPres => { + const movements = [...prevPres.movements, movement] + return {...prevPres, movements} + }) + } + + // TOOD: need to pause all intervals if possible lol + // TODO: extract this into different class with pause and resume recording + const followMovements = (presentation: Presentation, docView: CollectionFreeFormView): void => { + const document = docView.Document + + const { movements } = presentation + movements.forEach(movement => { + const { panX, panY, time } = movement + // set the pan to what was stored + setTimeout(() => { + document._panX = panX; + document._panY = panY; + }, time) + }) + } + +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 75855f5d9..1db90a65a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -118,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @@ -959,6 +959,7 @@ export class CollectionFreeFormView extends CollectionSubView { // need the first for subtraction let first = null; -- cgit v1.2.3-70-g09d2 From 0af393318adafa885d66c0fc43ffbf23f91e3c73 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Thu, 21 Apr 2022 16:48:51 -0400 Subject: Integrate with jenny's videobox api --- src/client/apis/recording/RecordingApi.ts | 153 +++++++++++++++++++++ src/client/apis/recording/recordingApi.tsx | 153 --------------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 27 +--- .../views/nodes/RecordingBox/RecordingView.tsx | 18 +-- 4 files changed, 167 insertions(+), 184 deletions(-) create mode 100644 src/client/apis/recording/RecordingApi.ts delete mode 100644 src/client/apis/recording/recordingApi.tsx (limited to 'src/client/views/collections') diff --git a/src/client/apis/recording/RecordingApi.ts b/src/client/apis/recording/RecordingApi.ts new file mode 100644 index 000000000..64243e443 --- /dev/null +++ b/src/client/apis/recording/RecordingApi.ts @@ -0,0 +1,153 @@ +import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; +import React, { useState } from "react"; + +export namespace RecordingApi { + + type Movement = { + time: number, + panX: number, + panY: number, + } + + export type Presentation = { + movements: Array + meta: Object, + startDate: Date | null, + } + + const NULL_PRESENTATION = { + movements: [], + meta: {}, + startDate: null, + } + + const [currentPresentation, setCurrentPresenation] = useState(NULL_PRESENTATION) + const [isRecording, setIsRecording] = useState(false) + const [absoluteStart, setAbsoluteStart] = useState(-1) + + export const initAndStart = (meta?: Object): Error | undefined => { + // check if already init a presentation + if (currentPresentation.startDate !== null) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') + return new Error('[recordingApi.ts] start()') + } + + // (1a) get start date for presenation + const startDate = new Date() + // (1b) set start timestamp to absolute timestamp + setAbsoluteStart(startDate.getTime()) + + // TODO: (2) assign meta content + + // (3) assign init values to currentPresenation + setCurrentPresenation({ ...currentPresentation, startDate }) + + // (4) set isRecording true to allow trackMovements + setIsRecording(true) + } + + export const clear = (): Error | undefined => { + // TODO: maybe archive the data? + if (isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first') + return new Error('[recordingApi.ts] clear()') + } + // clear presenation data + setCurrentPresenation(NULL_PRESENTATION) + + // set isRecording false + setIsRecording(false) + + // default absoluteStart + setAbsoluteStart(-1) + } + + export const pause = (): Error | undefined => { + if (currentPresentation.startDate === null) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] pause()') + } + // don't allow track movments + setIsRecording(false) + + // set relativeStart to the pausedTimestamp + const timestamp = new Date().getTime() + setAbsoluteStart(timestamp) + } + + export const resume = () => { + if (currentPresentation.startDate === null) { + console.error('[recordingApi.ts] resume() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] resume()') + } + + const timestamp = new Date().getTime() + const startTimestamp = currentPresentation.startDate?.getTime() + if (!startTimestamp) { + console.error('[recordingApi.ts] resume() failed: no presentation data. try calling init() first') + return new Error('[recordingApi.ts] pause()') + } + + setAbsoluteStart(prevTime => { + // const relativeUnpause = timestamp - absoluteStart + // const timePaused = relativeUnpause - prevTime + // return timePaused + absoluteStart + const absoluteTimePaused = timestamp - prevTime + return absoluteTimePaused + }) + } + + export const finish = (): Error | Presentation => { + if (currentPresentation.movements === null) { + console.error('[recordingApi.ts] finish() failed: no presentation data. try calling init() first') + return new Error('[recordingApi.ts] finish()') + } + + // make copy and clear this class's data + // const returnCopy = { ...currentPresentation } + // clear() + + // // return the copy + // return returnCopy + + return currentPresentation + } + + export const trackMovements = (panX: number, panY: number): Error | undefined => { + // ensure we are recording + if (!isRecording) { + console.error('[recordingApi.ts] pause() failed: recording is paused()') + return new Error('[recordingApi.ts] pause()') + } + + // get the relative time + const timestamp = new Date().getTime() + const relativeTime = timestamp - absoluteStart + + // make new movement struct + const movement: Movement = { time: relativeTime, panX, panY } + + // add that movement struct to the current presentation data + setCurrentPresenation(prevPres => { + const movements = [...prevPres.movements, movement] + return {...prevPres, movements} + }) + } + + // TOOD: need to pause all intervals if possible lol + // TODO: extract this into different class with pause and resume recording + export const followMovements = (presentation: Presentation, docView: CollectionFreeFormView): void => { + const document = docView.Document + + const { movements } = presentation + movements.forEach(movement => { + const { panX, panY, time } = movement + // set the pan to what was stored + setTimeout(() => { + document._panX = panX; + document._panY = panY; + }, time) + }) + } + +} \ No newline at end of file diff --git a/src/client/apis/recording/recordingApi.tsx b/src/client/apis/recording/recordingApi.tsx deleted file mode 100644 index 97d4e2e7e..000000000 --- a/src/client/apis/recording/recordingApi.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; -import React, { useState } from "react"; - -export function RecordingApi() { - - type Movement = { - time: number, - panX: number, - panY: number, - } - - type Presentation = { - movements: Array - meta: Object, - startDate: Date | null, - } - - const NULL_PRESENTATION = { - movements: [], - meta: {}, - startDate: null, - } - - const [currentPresentation, setCurrentPresenation] = useState(NULL_PRESENTATION) - const [isRecording, setIsRecording] = useState(false) - const [absoluteStart, setAbsoluteStart] = useState(-1) - - const initAndStart = (view: CollectionFreeFormView, meta?: Object): Error | undefined => { - // check if already init a presentation - if (currentPresentation.startDate !== null) { - console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') - return new Error('[recordingApi.ts] start()') - } - - // (1a) get start date for presenation - const startDate = new Date() - // (1b) set start timestamp to absolute timestamp - setAbsoluteStart(startDate.getTime()) - - // TODO: (2) assign meta content - - // (3) assign init values to currentPresenation - setCurrentPresenation({ ...currentPresentation, startDate }) - - // (4) set isRecording true to allow trackMovements - setIsRecording(true) - } - - const clear = (): Error | undefined => { - // TODO: maybe archive the data? - if (isRecording) { - console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first') - return new Error('[recordingApi.ts] clear()') - } - // clear presenation data - setCurrentPresenation(NULL_PRESENTATION) - - // clear isRecording - setIsRecording(false) - - // clear absoluteStart - setAbsoluteStart(-1) - } - - const pause = (): Error | undefined => { - if (currentPresentation.startDate === null) { - console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] pause(): no presenation') - } - // don't allow track movments - setIsRecording(false) - - // set relativeStart to the pausedTimestamp - const timestamp = new Date().getTime() - setAbsoluteStart(timestamp) - } - - const resume = () => { - if (currentPresentation.startDate === null) { - console.error('[recordingApi.ts] resume() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] resume()') - } - - const timestamp = new Date().getTime() - const startTimestamp = currentPresentation.startDate?.getTime() - if (!startTimestamp) { - console.error('[recordingApi.ts] resume() failed: no presentation data. try calling init() first') - return new Error('[recordingApi.ts] pause()') - } - - setAbsoluteStart(prevTime => { - // const relativeUnpause = timestamp - absoluteStart - // const timePaused = relativeUnpause - prevTime - // return timePaused + absoluteStart - const absoluteTimePaused = timestamp - prevTime - return absoluteTimePaused - }) - } - - const finish = (): Error | Presentation => { - if (currentPresentation.movements === null) { - console.error('[recordingApi.ts] finish() failed: no presentation data. try calling init() first') - return new Error('[recordingApi.ts] finish()') - } - - // make copy and clear this class's data - const returnCopy = { ...currentPresentation } - clear() - - // return the copy - return returnCopy - } - - const trackMovements = (panX: number, panY: number): Error | undefined => { - // ensure we are recording - if (!isRecording) { - console.error('[recordingApi.ts] pause() failed: recording is paused()') - return new Error('[recordingApi.ts] pause()') - } - - // get the relative time - const timestamp = new Date().getTime() - const relativeTime = timestamp - absoluteStart - - // make new movement struct - const movement: Movement = { time: relativeTime, panX, panY } - - // add that movement struct to the current presentation data - setCurrentPresenation(prevPres => { - const movements = [...prevPres.movements, movement] - return {...prevPres, movements} - }) - } - - // TOOD: need to pause all intervals if possible lol - // TODO: extract this into different class with pause and resume recording - const followMovements = (presentation: Presentation, docView: CollectionFreeFormView): void => { - const document = docView.Document - - const { movements } = presentation - movements.forEach(movement => { - const { panX, panY, time } = movement - // set the pan to what was stored - setTimeout(() => { - document._panX = panX; - document._panY = panY; - }, time) - }) - } - - return (<>) - -} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6db2269c4..239aacd4f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -51,6 +51,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import e = require("connect-flash"); +import { RecordingApi } from "../../../apis/recording/RecordingApi"; export const panZoomSchema = createSchema({ _panX: "number", @@ -959,36 +960,18 @@ export class CollectionFreeFormView extends CollectionSubView { - // need the first for subtraction - let first = null; - - this.storedMovements.forEach(movement => { - if (first === null) first = movement.time; - - // set the pan to what was stored - setTimeout(() => { - this.Document._panX = movement.panX; - this.Document._panY = movement.panY; - }, movement.time - first) - }) - - // for now, clear the movements - this.storedMovements = [] - } - @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { // if not presenting, just retrace the movements if (Doc.UserDoc()?.presentationMode === 'watching') { - this.followMovements() - return; + // RecordingApi.followMovements(presentation, this) + return } if (Doc.UserDoc()?.presentationMode === 'recording') { // store as many movments as possible - this.storedMovements.push({time: new Date().getTime(), panX, panY}) + // this.storedMovements.push({time: new Date().getTime(), panX, panY}) + const err = RecordingApi.trackMovements(panX, panY) } if (!this.isAnnotationOverlay && clamp) { diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 9be972d53..8c1ab9e2b 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -6,7 +6,7 @@ import { MdBackspace } from 'react-icons/md'; import { FaCheckCircle } from 'react-icons/fa'; import { IconContext } from "react-icons"; -import { RecordingApi } from '../../../apis/recording/recordingApi'; +import { RecordingApi } from '../../../apis/recording/RecordingApi'; enum RecordingStatus { @@ -39,8 +39,6 @@ export function RecordingView() { const videoElementRef = useRef(null); const [finished, setFinished] = useState(false) - - const recordingApiRef = useRef(null); @@ -75,7 +73,11 @@ export function RecordingView() { videoElementRef.current!.srcObject = null videoElementRef.current!.src = blobUrl - videoElementRef.current!.muted = false + videoElementRef.current!.muted = false + + // clear the recording api + const presentation = RecordingApi.finish() + RecordingApi.clear() } @@ -202,7 +204,8 @@ export function RecordingView() { const pause = () => { if (recorder.current) { if (recorder.current.state === "recording") { - recorder.current.pause(); + recorder.current.pause(); + const err = RecordingApi.pause() } } } @@ -211,7 +214,7 @@ export function RecordingView() { console.log('[RecordingView.tsx] startOrResume') if (!recorder.current || recorder.current.state === "inactive") { record(); - recordingApiRef.current.startAndInit() + const err = RecordingApi.initAndStart() } else if (recorder.current.state === "paused") { recorder.current.resume(); } @@ -325,9 +328,6 @@ export function RecordingView() { )} */} - - - ) } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 561890c44833dbd4725cb8d818edb9856a464066 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Thu, 28 Apr 2022 17:10:04 -0400 Subject: Remove demo code from CollectionFFView and implment observer call into recordingApi --- src/client/apis/recording/recordingApi.ts | 54 ++++++++++++++++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 32 ------------- 2 files changed, 45 insertions(+), 41 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/apis/recording/recordingApi.ts b/src/client/apis/recording/recordingApi.ts index 9ad7b5165..50d6f8038 100644 --- a/src/client/apis/recording/recordingApi.ts +++ b/src/client/apis/recording/recordingApi.ts @@ -1,6 +1,7 @@ import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; import React, { useState } from "react"; -import { observable, observe } from "mobx"; +import { IReactionDisposer, observable, reaction } from "mobx"; +import { NumCast } from "../../../fields/Types"; type Movement = { time: number, @@ -39,6 +40,9 @@ export class RecordingApi { this.currentPresentation = RecordingApi.NULL_PRESENTATION this.isRecording = false; this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; } // little helper :) @@ -79,6 +83,9 @@ export class RecordingApi { this.isRecording = false // clear absoluteStart this.absoluteStart = -1 + + // clear the disposeFunc + this.disposeFunc = null } public pause = (): Error | undefined => { @@ -117,7 +124,7 @@ export class RecordingApi { return { ...this.currentPresentation } } - public trackMovements = (panX: number, panY: number): Error | undefined => { + private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { console.error('[recordingApi.ts] pause() failed: recording is paused()') @@ -151,13 +158,42 @@ export class RecordingApi { }) } - // observer that can be updated to track the relevant FreeFormView - // public setFreeFormView = (view: CollectionFreeFormView): void => { - // observe(view, 'Document', (change) => { - // if (change.name === '_panX') { - // this.trackMovements(view.Document._panX, view.Document._panY) - // } - // } + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === null) return; + + //this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), + (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y) + ) + } + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + } + + + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) // } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1db90a65a..100526983 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -118,8 +118,6 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); } @@ -959,38 +957,8 @@ export class CollectionFreeFormView extends CollectionSubView { - // need the first for subtraction - let first = null; - - this.storedMovements.forEach(movement => { - if (first === null) first = movement.time; - - // set the pan to what was stored - setTimeout(() => { - this.Document._panX = movement.panX; - this.Document._panY = movement.panY; - }, movement.time - first) - }) - - // for now, clear the movements - this.storedMovements = [] - } - @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { - // if not presenting, just retrace the movements - if (Doc.UserDoc()?.presentationMode === 'watching') { - this.followMovements() - return; - } - - if (Doc.UserDoc()?.presentationMode === 'recording') { - // store as many movments as possible - this.storedMovements.push({time: new Date().getTime(), panX, panY}) - } - if (!this.isAnnotationOverlay && clamp) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); -- cgit v1.2.3-70-g09d2 From 1eb2c362b020b3cbe446bbc1585108129fda6977 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 01:11:54 -0400 Subject: Get storing pres data working, but is choppy due to mobx usage. --- src/client/apis/recording/recordingApi.ts | 300 -------------------- src/client/util/RecordingApi.ts | 303 +++++++++++++++++++++ src/client/views/Main.tsx | 2 + .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 14 +- .../views/nodes/RecordingBox/RecordingView.tsx | 26 +- src/client/views/nodes/VideoBox.tsx | 14 + 7 files changed, 348 insertions(+), 316 deletions(-) delete mode 100644 src/client/apis/recording/recordingApi.ts create mode 100644 src/client/util/RecordingApi.ts (limited to 'src/client/views/collections') diff --git a/src/client/apis/recording/recordingApi.ts b/src/client/apis/recording/recordingApi.ts deleted file mode 100644 index b57e675b7..000000000 --- a/src/client/apis/recording/recordingApi.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; -import React, { useState } from "react"; -import { IReactionDisposer, observable, reaction } from "mobx"; -import { NumCast } from "../../../fields/Types"; - -type Movement = { - time: number, - panX: number, - panY: number, -} - -type Presentation = { - movements: Array - meta: Object, - startDate: Date | null, -} - -export class RecordingApi { - - private static NULL_PRESENTATION: Presentation = { - movements: [], - meta: {}, - startDate: null, - } - - // instance variables - private currentPresentation: Presentation; - private isRecording: boolean; - private absoluteStart: number; - - - // create static instance and getter for global use - @observable static _instance: RecordingApi; - public static get instance(): RecordingApi { return RecordingApi._instance } - public constructor() { - // init the global instance - RecordingApi._instance = this; - - // init the instance variables - this.currentPresentation = RecordingApi.NULL_PRESENTATION - this.isRecording = false; - this.absoluteStart = -1; - - // used for tracking movements in the view frame - this.disposeFunc = null; - - // for now, set playFFView - this.playFFView = null; - this.timers = null; - } - - // little helper :) - private get isInitPresenation(): boolean { - return this.currentPresentation.startDate === null - } - - public start = (view: CollectionFreeFormView, meta?: Object): Error | undefined => { - // check if already init a presentation - if (!this.isInitPresenation) { - console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') - return new Error('[recordingApi.ts] start()') - } - - // (1a) get start date for presenation - const startDate = new Date() - // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime() - - // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} - // (3) assign start date to currentPresenation - this.currentPresentation.startDate = startDate - // (4) set isRecording true to allow trackMovements - this.isRecording = true - } - - public clear = (): Error | undefined => { - // TODO: maybe archive the data? - if (this.isRecording) { - console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first') - return new Error('[recordingApi.ts] clear()') - } - - // clear presenation data - this.currentPresentation = RecordingApi.NULL_PRESENTATION - // clear isRecording - this.isRecording = false - // clear absoluteStart - this.absoluteStart = -1 - - // clear the disposeFunc - this.disposeFunc = null - } - - public pause = (): Error | undefined => { - if (this.currentPresentation.startDate === null) { - console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] pause(): no presenation') - } - // don't allow track movments - this.isRecording = false - - // set relativeStart to the pausedTimestamp - const timestamp = new Date().getTime() - this.absoluteStart = timestamp - } - - public resume = () => { - const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startDate?.getTime() - if (!startTimestamp) { - console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] pause()') - } - - // update absoluteStart to bridge the paused time - const absoluteTimePaused = timestamp - this.absoluteStart - this.absoluteStart = absoluteTimePaused - } - - public finish = (): Error | Presentation => { - if (this.isInitPresenation) { - console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] finish()') - } - - // return a copy of the the presentation data - return { ...this.currentPresentation } - } - - private trackMovements = (panX: number, panY: number): Error | undefined => { - // ensure we are recording - if (!this.isRecording) { - console.error('[recordingApi.ts] pause() failed: recording is paused()') - return new Error('[recordingApi.ts] pause()') - } - - // get the relative time - const timestamp = new Date().getTime() - const relativeTime = timestamp - this.absoluteStart - - // make new movement struct - const movement: Movement = { time: relativeTime, panX, panY } - - // add that movement to the current presentation data's movement array - this.currentPresentation.movements.push(movement) - } - - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === null) return; - - //this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), - (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.playFFView = view; - } - - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - - // TODO: extract this into different class with pause and resume recording - private playFFView: CollectionFreeFormView | null; - private timers: Timer[] | null; - - public followMovements = (presentation: Presentation): undefined | Error => { - if (presentation.startDate === null || this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - } - - const document = this.playFFView.Document - const { movements } = presentation - this.timers = movements.map(movement => { - const { panX, panY, time } = movement - return new Timer(() => { - document._panX = panX; - document._panY = panY; - // TODO: consider cleaning this array to null or some state - }, time) - }) - } - - public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') - } - // TODO: set userdoc presentMode to browsing - this.timers?.forEach(timer => timer.pause()) - } - - public resumeMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] resumeMovements() failed: no view') - } - this.timers?.forEach(timer => timer.resume()) - } - - // public followMovements = (presentation: Presentation): undefined | Error => { - // if (presentation.startDate === null || this.playFFView === null) { - // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - // } - - // const document = this.playFFView.Document - // const { movements } = presentation - // movements.forEach(movement => { - // const { panX, panY, time } = movement - // // set the pan to what was stored - // setTimeout(() => { - // document._panX = panX; - // document._panY = panY; - // }, time) - // }) - // } - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } -} - -/** Represents the `setTimeout` with an ability to perform pause/resume actions - * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout - */ -export class Timer { - private _start: Date; - private _remaining: number; - private _durationTimeoutId?: NodeJS.Timeout; - private _callback: (...args: any[]) => void; - private _done = false; - get done () { - return this._done; - } - - public constructor(callback: (...args: any[]) => void, ms = 0) { - this._callback = () => { - callback(); - this._done = true; - }; - this._remaining = ms; - this.resume(); - } - - /** pauses the timer */ - public pause(): Timer { - if (this._durationTimeoutId && !this._done) { - this._clearTimeoutRef(); - this._remaining -= new Date().getTime() - this._start.getTime(); - } - return this; - } - - /** resumes the timer */ - public resume(): Timer { - if (!this._durationTimeoutId && !this._done) { - this._start = new Date; - this._durationTimeoutId = setTimeout(this._callback, this._remaining); - } - return this; - } - - /** - * clears the timeout and marks it as done. - * - * After called, the timeout will not resume - */ - public clearTimeout() { - this._clearTimeoutRef(); - this._done = true; - } - - private _clearTimeoutRef() { - if (this._durationTimeoutId) { - clearTimeout(this._durationTimeoutId); - this._durationTimeoutId = undefined; - } - } - -} \ No newline at end of file diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts new file mode 100644 index 000000000..c4f76282c --- /dev/null +++ b/src/client/util/RecordingApi.ts @@ -0,0 +1,303 @@ +import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; +import { IReactionDisposer, observable, reaction } from "mobx"; +import { NumCast } from "../../fields/Types"; + +type Movement = { + time: number, + panX: number, + panY: number, +} + +type Presentation = { + movements: Array + meta: Object, + startDate: Date | null, +} + +export class RecordingApi { + + private static NULL_PRESENTATION: Presentation = { + movements: [], + meta: {}, + startDate: null, + } + + // instance variables + private currentPresentation: Presentation; + private isRecording: boolean; + private absoluteStart: number; + + + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { return RecordingApi._instance } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION + this.isRecording = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + } + + // little helper :) + private get isInitPresenation(): boolean { + return this.currentPresentation.startDate === null + } + + public start = (meta?: Object): Error | undefined => { + // check if already init a presentation + if (!this.isInitPresenation) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') + return new Error('[recordingApi.ts] start()') + } + + // (1a) get start date for presenation + const startDate = new Date() + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime() + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {} + // (3) assign start date to currentPresenation + this.currentPresentation.startDate = startDate + // (4) set isRecording true to allow trackMovements + this.isRecording = true + } + + public clear = (): Error | Presentation => { + // TODO: maybe archive the data? + if (this.isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first') + return new Error('[recordingApi.ts] clear()') + } + + const presCopy = { ...this.currentPresentation } + + // clear presenation data + this.currentPresentation = RecordingApi.NULL_PRESENTATION + // clear isRecording + this.isRecording = false + // clear absoluteStart + this.absoluteStart = -1 + // clear the disposeFunc + this.disposeFunc = null + + return presCopy; + } + + public pause = (): Error | undefined => { + if (this.currentPresentation.startDate === null) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] pause(): no presenation') + } + // don't allow track movments + this.isRecording = false + + // set relativeStart to the pausedTimestamp + const timestamp = new Date().getTime() + this.absoluteStart = timestamp + } + + public resume = () => { + const timestamp = new Date().getTime() + const startTimestamp = this.currentPresentation.startDate?.getTime() + if (!startTimestamp) { + console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') + return new Error('[recordingApi.ts] pause()') + } + + // update absoluteStart to bridge the paused time + const absoluteTimePaused = timestamp - this.absoluteStart + this.absoluteStart = absoluteTimePaused + } + + // public finish = (): Error | Presentation => { + // if (this.isInitPresenation) { + // console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') + // return new Error('[recordingApi.ts] finish()') + // } + + // // return a copy of the the presentation data + // return { ...this.currentPresentation } + // } + + private trackMovements = (panX: number, panY: number): Error | undefined => { + // ensure we are recording + if (!this.isRecording) { + console.error('[recordingApi.ts] pause() failed: recording is paused()') + return new Error('[recordingApi.ts] pause()') + } + + // get the relative time + const timestamp = new Date().getTime() + const relativeTime = timestamp - this.absoluteStart + + // make new movement struct + const movement: Movement = { time: relativeTime, panX, panY } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements.push(movement) + } + + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === null || view === this.playFFView) return; + + //this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), + (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y) + ) + + // for now, set the most recent recordingFFView to the playFFView + this.playFFView = view; + } + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + } + + // TODO: extract this into different class with pause and resume recording + private playFFView: CollectionFreeFormView | null; + private timers: Timer[] | null; + + // public followMovements = (presentation: Presentation): undefined | Error => { + // console.log(presentation) + // if (presentation.startDate === null || this.playFFView === null) { + // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + // } + + // const document = this.playFFView.Document + // const { movements } = presentation + // this.timers = movements.map(movement => { + // const { panX, panY, time } = movement + // return new Timer(() => { + // document._panX = panX; + // document._panY = panY; + // // TODO: consider cleaning this array to null or some state + // }, time) + // }) + // } + + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') + } + // TODO: set userdoc presentMode to browsing + this.timers?.forEach(timer => timer.pause()) + } + + public resumeMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] resumeMovements() failed: no view') + } + this.timers?.forEach(timer => timer.resume()) + } + + public followMovements = (presentation: Presentation): undefined | Error => { + if (presentation.startDate === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + } + + const document = this.playFFView.Document + const { movements } = presentation + movements.forEach(movement => { + const { panX, panY, time } = movement + // set the pan to what was stored + setTimeout(() => { + document._panX = panX; + document._panY = panY; + }, time) + }) + } + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } +} + +/** Represents the `setTimeout` with an ability to perform pause/resume actions + * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout + */ +export class Timer { + private _start: Date; + private _remaining: number; + private _durationTimeoutId?: NodeJS.Timeout; + private _callback: (...args: any[]) => void; + private _done = false; + get done () { + return this._done; + } + + public constructor(callback: (...args: any[]) => void, ms = 0) { + this._callback = () => { + callback(); + this._done = true; + }; + this._remaining = ms; + this.resume(); + } + + /** pauses the timer */ + public pause(): Timer { + if (this._durationTimeoutId && !this._done) { + this._clearTimeoutRef(); + this._remaining -= new Date().getTime() - this._start.getTime(); + } + return this; + } + + /** resumes the timer */ + public resume(): Timer { + if (!this._durationTimeoutId && !this._done) { + this._start = new Date; + this._durationTimeoutId = setTimeout(this._callback, this._remaining); + } + return this; + } + + /** + * clears the timeout and marks it as done. + * + * After called, the timeout will not resume + */ + public clearTimeout() { + this._clearTimeoutRef(); + this._done = true; + } + + private _clearTimeoutRef() { + if (this._durationTimeoutId) { + clearTimeout(this._durationTimeoutId); + this._durationTimeoutId = undefined; + } + } + +} \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 8560ccb29..517fe097c 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -8,6 +8,7 @@ import { AssignAllExtensions } from "../../extensions/General/Extensions"; import { Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { LinkManager } from "../util/LinkManager"; +import { RecordingApi } from "../util/RecordingApi"; import { CollectionView } from "./collections/CollectionView"; import { MainView } from "./MainView"; @@ -36,5 +37,6 @@ AssignAllExtensions(); const expires = "expires=" + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; new LinkManager(); + new RecordingApi; ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 35bd9cf79..f74e526b6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -54,7 +54,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import e = require("connect-flash"); -import { RecordingApi } from "../../../apis/recording/RecordingApi"; +import { RecordingApi } from "../../../util/RecordingApi"; export const panZoomSchema = createSchema({ _panX: "number", @@ -965,6 +965,9 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(doc => doc instanceof Doc); diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 86358e838..1b17476f7 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -1,7 +1,7 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { AudioField, VideoField } from "../../../../fields/URLField"; +import { VideoField } from "../../../../fields/URLField"; import { Upload } from "../../../../server/SharedMediaTypes"; import { ViewBoxBaseComponent } from "../../DocComponent"; import { FieldView } from "../FieldView"; @@ -9,6 +9,8 @@ import { VideoBox } from "../VideoBox"; import { RecordingView } from './RecordingView'; import { DocumentType } from "../../../documents/DocumentTypes"; +import { RecordingApi } from "../../../util/RecordingApi"; + @observer export class RecordingBox extends ViewBoxBaseComponent() { @@ -31,17 +33,19 @@ export class RecordingBox extends ViewBoxBaseComponent() { @action setResult = (info: Upload.FileInformation) => { - console.log("Setting result to " + info) + // console.log("Setting result to " + info) this.result = info - console.log(this.result.accessPaths.agnostic.client) + // console.log(this.result.accessPaths.agnostic.client) this.dataDoc.type = DocumentType.VID; - console.log(this.videoDuration) + // console.log(this.videoDuration) this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration; this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey); // this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined; // this.layoutDoc._fitWidth = undefined; - this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.agnostic.client); + this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.agnostic.client); + // stringify the presenation and store it + this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(RecordingApi.Instance.clear()); } render() { diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index d99492095..870ef87d7 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -8,7 +8,7 @@ import { IconContext } from "react-icons"; import { Networking } from '../../../Network'; import { Upload } from '../../../../server/SharedMediaTypes'; -import { RecordingApi } from '../../../apis/recording/RecordingApi'; +import { RecordingApi } from '../../../util/RecordingApi'; enum RecordingStatus { @@ -57,11 +57,11 @@ export function RecordingView(props: IRecordingViewProps) { height: 720, }, // audio: true, - // audio: { - // echoCancellation: true, - // noiseSuppression: true, - // sampleRate: 44100 - // } + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100 + } } useEffect(() => { @@ -159,7 +159,10 @@ export function RecordingView(props: IRecordingViewProps) { // } videoRecorder.current.onstart = (event: any) => { - setRecording(true); + setRecording(true); + // TODO: update names + // RecordingApi.Instance.clear(); + RecordingApi.Instance.start(); } videoRecorder.current.onstop = () => { @@ -172,7 +175,8 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); - setFinished(true); + setFinished(true); + RecordingApi.Instance.pause(); } // recording paused @@ -182,12 +186,14 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] - setRecording(false); + setRecording(false); + RecordingApi.Instance.pause(); } videoRecorder.current.onresume = async (event: any) => { await startShowingStream(); - setRecording(true); + setRecording(true); + RecordingApi.Instance.resume(); } videoRecorder.current.start(200) diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index e57cb1abe..7364a64d9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -28,6 +28,7 @@ import { AnchorMenu } from "../pdf/AnchorMenu"; import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; +import { RecordingApi } from "../../util/RecordingApi"; const path = require('path'); @@ -117,6 +118,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + // if presentation isn't null, call followmovements on the recording api + if (this.presentation) { + const err = RecordingApi.Instance.followMovements(this.presentation); + if (err) console.log(err); + } + + this._playing = true; const eleTime = this.player?.currentTime || 0; if (this.timeline) { -- cgit v1.2.3-70-g09d2 From 4dc4b0939d4e4afbc9f6db999ff80d434ef4ccc6 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 02:00:55 -0400 Subject: Added the presentation mode back in. However, pres isn't being recreated smoothly after being stored in the db. --- src/client/util/RecordingApi.ts | 21 +++++++++++++++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 ++++-- .../views/nodes/RecordingBox/RecordingView.tsx | 3 --- 3 files changed, 21 insertions(+), 9 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ab7642a90..b53cba79d 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -1,6 +1,7 @@ import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; +import { Doc } from "../../fields/Doc"; type Movement = { time: number, @@ -42,6 +43,7 @@ export class RecordingApi { // used for tracking movements in the view frame this.disposeFunc = null; + this.recordingFFView = null; // for now, set playFFView this.playFFView = null; @@ -60,6 +62,9 @@ export class RecordingApi { return new Error('[recordingApi.ts] start()') } + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording' + // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp @@ -80,6 +85,9 @@ export class RecordingApi { return new Error('[recordingApi.ts] clear()') } + // update the presentation mode + Doc.UserDoc().presentationMode = 'none' + const presCopy = { ...this.currentPresentation } // clear presenation data @@ -133,8 +141,8 @@ export class RecordingApi { private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { - console.error('[recordingApi.ts] pause() failed: recording is paused()') - return new Error('[recordingApi.ts] pause()') + console.error('[recordingApi.ts] trackMovements() failed: recording is paused()') + return new Error('[recordingApi.ts] trackMovements()') } // get the time @@ -149,11 +157,12 @@ export class RecordingApi { // instance variable for the FFView private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; // set the FFView that will be used in a reaction to track the movements public setRecordingFFView = (view: CollectionFreeFormView): void => { // set the view to the current view - if (view === null || view === this.playFFView) return; + if (view === this.recordingFFView || view === null) return; //this.recordingFFView = view; // set the reaction to track the movements @@ -163,7 +172,7 @@ export class RecordingApi { ) // for now, set the most recent recordingFFView to the playFFView - this.playFFView = view; + this.recordingFFView = view; } // call on dispose function to stop tracking movements @@ -194,6 +203,10 @@ export class RecordingApi { // }) // } + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view + } + public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f74e526b6..8bcf6f46f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -965,8 +965,10 @@ export class CollectionFreeFormView extends CollectionSubView { setRecording(true); - // TODO: update names // RecordingApi.Instance.clear(); RecordingApi.Instance.start(); } @@ -176,7 +175,6 @@ export function RecordingView(props: IRecordingViewProps) { videoChunks = [] setRecording(false); setFinished(true); - console.log("finished recording") RecordingApi.Instance.pause(); } @@ -188,7 +186,6 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); - console.log("paused recording") RecordingApi.Instance.pause(); } -- cgit v1.2.3-70-g09d2 From a2173243c7a447527e2e86ec0a00998ed8b9cc40 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 03:30:07 -0400 Subject: Get play and pause to work. There is a bug with getting double called that was causing choppiness. --- src/client/util/RecordingApi.ts | 207 ++++++++++++++------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 10 +- 3 files changed, 148 insertions(+), 71 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index b53cba79d..dfe8e98ca 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -183,11 +183,10 @@ export class RecordingApi { // TODO: extract this into different class with pause and resume recording private playFFView: CollectionFreeFormView | null; - private timers: Timer[] | null; + private timers: NodeJS.Timeout[] | null; - // public followMovements = (presentation: Presentation): undefined | Error => { - // console.log(presentation) - // if (presentation.startDate === null || this.playFFView === null) { + // public loadPresentation = (presentation: Presentation): undefined | Error => { + // if (presentation.startTime === null || this.playFFView === null) { // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') // } @@ -195,48 +194,85 @@ export class RecordingApi { // const { movements } = presentation // this.timers = movements.map(movement => { // const { panX, panY, time } = movement - // return new Timer(() => { + + // const t = new Timer(() => { // document._panX = panX; // document._panY = panY; // // TODO: consider cleaning this array to null or some state - // }, time) + // }, time - presentation.startTime) + // t.pause() + // return t // }) + // console.log(this.timers) // } public setPlayFFView = (view: CollectionFreeFormView): void => { this.playFFView = view } + // public pauseMovements = (): undefined | Error => { + // console.log('[recordingApi.ts] pauseMovements()') + // if (this.playFFView === null) { + // return new Error('[recordingApi.ts] pauseMovements() failed: no view') + // } + // // TODO: set userdoc presentMode to browsing + // this.timers?.forEach(timer => timer.pause()) + // } + public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') } - // TODO: set userdoc presentMode to browsing - this.timers?.forEach(timer => timer.pause()) - } - public resumeMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] resumeMovements() failed: no view') + if (!this._isPlaying) { + return new Error('[recordingApi.ts] pauseMovements() failed: not playing') } - this.timers?.forEach(timer => timer.resume()) + // TODO: set userdoc presentMode to browsing + //console.log('cleared timers', this.timers) + console.log(this.timers?.map(timer => clearTimeout(timer))) + console.log('[recordingApi.ts] pauseMovements()') + + this._isPlaying = false } - public followMovements = (presentation: Presentation): undefined | Error => { + // public resumeMovements = (): undefined | Error => { + // if (this.playFFView === null) { + // return new Error('[recordingApi.ts] resumeMovements() failed: no view') + // } + // console.log('resume') + // this.timers?.forEach(timer => timer.resume()) + // } + + private _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { if (presentation.startTime === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } + if (this._isPlaying) { + return new Error('[recordingApi.ts] playMovements() failed: already playing') + } + this._isPlaying = true; + + console.log(timeViewed) + const document = this.playFFView.Document const { movements } = presentation - movements.forEach(movement => { + this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => { const { panX, panY, time } = movement + const absoluteTime = time - presentation.startTime - timeViewed*1000 + if (absoluteTime < 0) return arr; + // set the pan to what was stored - setTimeout(() => { + arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - }, time - presentation.startTime) - }) + }, absoluteTime)) + return arr; + }, []) + + console.log(this.timers.length) } // Unfinished code for tracing multiple free form views // export let pres: Map = new Map() @@ -258,58 +294,95 @@ export class RecordingApi { /** Represents the `setTimeout` with an ability to perform pause/resume actions * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout */ -export class Timer { - private _start: Date; - private _remaining: number; - private _durationTimeoutId?: NodeJS.Timeout; - private _callback: (...args: any[]) => void; - private _done = false; - get done () { - return this._done; +class Timer { + private timerId: NodeJS.Timeout | null; + private callback: (...args: any[]) => void + private start: number + private remaining: number; + + constructor(callback: (...args: any[]) => void, delay: number) { + this.callback = callback; + this.remaining = delay; + + this.start = Date.now(); + this.timerId = setTimeout(this.callback, this.remaining); } - public constructor(callback: (...args: any[]) => void, ms = 0) { - this._callback = () => { - callback(); - this._done = true; - }; - this._remaining = ms; - this.resume(); - } - - /** pauses the timer */ - public pause(): Timer { - if (this._durationTimeoutId && !this._done) { - this._clearTimeoutRef(); - this._remaining -= new Date().getTime() - this._start.getTime(); - } - return this; - } + public pause = () => { + console.log('[timer.ts] pause()') + this.timerId !== null && clearTimeout(this.timerId); + this.timerId = null; + this.remaining -= (Date.now() - this.start); + }; - /** resumes the timer */ - public resume(): Timer { - if (!this._durationTimeoutId && !this._done) { - this._start = new Date; - this._durationTimeoutId = setTimeout(this._callback, this._remaining); + public resume = () => { + if (this.timerId) { + return; } - return this; - } - /** - * clears the timeout and marks it as done. - * - * After called, the timeout will not resume - */ - public clearTimeout() { - this._clearTimeoutRef(); - this._done = true; - } + this.start = Date.now(); + this.timerId = setTimeout(this.callback, this.remaining); + }; - private _clearTimeoutRef() { - if (this._durationTimeoutId) { - clearTimeout(this._durationTimeoutId); - this._durationTimeoutId = undefined; - } - } - -} \ No newline at end of file + public clear = () => { + console.log('[timer.ts] clear()') + this.timerId !== null && clearTimeout(this.timerId); + // this.timerId = null; + // this.remaining -= (Date.now() - this.start); + }; +} +// class Timer { +// private _start: Date; +// private _remaining: number; +// private _durationTimeoutId?: NodeJS.Timeout; +// private _callback: (...args: any[]) => void; +// private _done = false; +// get done () { +// return this._done; +// } + +// public constructor(callback: (...args: any[]) => void, ms = 0) { +// this._callback = () => { +// callback(); +// this._done = true; +// }; +// this._remaining = ms; +// this.resume(); +// } + +// /** pauses the timer */ +// public pause(): Timer { +// if (this._durationTimeoutId && !this._done) { +// this._clearTimeoutRef(); +// this._remaining -= new Date().getTime() - this._start.getTime(); +// } +// return this; +// } + +// /** resumes the timer */ +// public resume(): Timer { +// if (!this._durationTimeoutId && !this._done) { +// this._start = new Date; +// this._durationTimeoutId = setTimeout(this._callback, this._remaining); +// } +// return this; +// } + +// /** +// * clears the timeout and marks it as done. +// * +// * After called, the timeout will not resume +// */ +// public clearTimeout() { +// this._clearTimeoutRef(); +// this._done = true; +// } + +// private _clearTimeoutRef() { +// if (this._durationTimeoutId) { +// clearTimeout(this._durationTimeoutId); +// this._durationTimeoutId = undefined; +// } +// } + +// } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8bcf6f46f..214d4bbdc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -966,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView { // if presentation isn't null, call followmovements on the recording api if (this.presentation) { - const err = RecordingApi.Instance.followMovements(this.presentation); - if (err) console.log(err); + const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0); + err && console.log(err) } - this._playing = true; const eleTime = this.player?.currentTime || 0; if (this.timeline) { @@ -198,6 +197,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + if (this.presentation) { + const err = RecordingApi.Instance.pauseMovements(); + err && console.log(err); + } + this._playing = false; this.removeCurrentlyPlaying(); try { -- cgit v1.2.3-70-g09d2 From 32f1fb8df81b5dfc12539090c3fe589099bf165b Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 04:43:22 -0400 Subject: Pause works with presentation now. --- src/client/util/RecordingApi.ts | 86 ++++++++++++---------- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +- src/client/views/nodes/VideoBox.tsx | 2 +- 3 files changed, 56 insertions(+), 39 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index e779a4ab1..ee427e5df 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -2,6 +2,7 @@ import { CollectionFreeFormView } from "../views/collections/collectionFreeForm" import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; import { Doc } from "../../fields/Doc"; +import { VideoBox } from "../views/nodes/VideoBox"; type Movement = { time: number, @@ -10,17 +11,15 @@ type Movement = { } type Presentation = { - movements: Array + movements: Array | null meta: Object, - startTime: number | null, } export class RecordingApi { private static NULL_PRESENTATION: Presentation = { - movements: [], + movements: null, meta: {}, - startTime: null, } // instance variables @@ -52,7 +51,7 @@ export class RecordingApi { // little helper :) private get isInitPresenation(): boolean { - return this.currentPresentation.startTime === null + return this.currentPresentation.movements === null } public start = (meta?: Object): Error | undefined => { @@ -68,12 +67,12 @@ export class RecordingApi { // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp - // this.absoluteStart = startDate.getTime() + this.absoluteStart = startDate.getTime() // (2) assign meta content if it exists this.currentPresentation.meta = meta || {} // (3) assign start date to currentPresenation - this.currentPresentation.startTime = startDate.getTime() + this.currentPresentation.movements = [] // (4) set isRecording true to allow trackMovements this.isRecording = true } @@ -110,39 +109,35 @@ export class RecordingApi { // don't allow track movments this.isRecording = false - // set relativeStart to the pausedTimestamp + // set adjust absoluteStart to add the time difference const timestamp = new Date().getTime() - this.absoluteStart = timestamp + this.absoluteStart = timestamp - this.absoluteStart } public resume = () => { - const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startTime - if (!startTimestamp) { - console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] pause()') - } - - // update absoluteStart to bridge the paused time - const absoluteTimePaused = timestamp - this.absoluteStart - this.absoluteStart = absoluteTimePaused + this.isRecording = true + // set absoluteStart to the pausedTimestamp + this.absoluteStart = new Date().getTime() - this.absoluteStart } private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { - console.error('[recordingApi.ts] trackMovements() failed: recording is paused()') return new Error('[recordingApi.ts] trackMovements()') } + // check to see if the presetation is init + if (this.isInitPresenation) { + return new Error('[recordingApi.ts] trackMovements(): no presentation') + } // get the time - const timestamp = new Date().getTime() - - // make new movement struct - const movement: Movement = { time: timestamp, panX, panY } + const time = new Date().getTime() - this.absoluteStart + // make new movement object + console.log(time) + const movement: Movement = { time, panX, panY } // add that movement to the current presentation data's movement array - this.currentPresentation.movements.push(movement) + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) } // instance variable for the FFView @@ -172,6 +167,7 @@ export class RecordingApi { } // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements private playFFView: CollectionFreeFormView | null; private timers: NodeJS.Timeout[] | null; @@ -185,42 +181,58 @@ export class RecordingApi { } if (!this._isPlaying) { - return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return } + this._isPlaying = false // TODO: set userdoc presentMode to browsing - //console.log('cleared timers', this.timers) - console.log(this.timers?.map(timer => clearTimeout(timer))) - console.log('[recordingApi.ts] pauseMovements()') + this.timers?.map(timer => clearTimeout(timer)) - this._isPlaying = false + this.videoBox = null; + } + + private videoBox: VideoBox | null = null; + + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause() + return this.videoBox === null } private _isPlaying = false; - public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { - if (presentation.startTime === null || this.playFFView === null) { + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } if (this._isPlaying) { - return new Error('[recordingApi.ts] playMovements() failed: already playing') + //return new Error('[recordingApi.ts] playMovements() failed: already playing') + return } this._isPlaying = true; + // Doc.UserDoc().presentationMode = 'watching'; - console.log(timeViewed) + // TODO: consider this bug at the end of the clip on seek + console.log(timeViewed, videoBox?.player?.currentTime) + this.videoBox = videoBox || null; const document = this.playFFView.Document const { movements } = presentation this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => { const { panX, panY, time } = movement - const absoluteTime = time - presentation.startTime - timeViewed*1000 - if (absoluteTime < 0) return arr; + + const timeDiff = time - timeViewed*1000 + if (timeDiff < 0) return arr; // set the pan to what was stored arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - }, absoluteTime)) + // if (movement === movements[movements.length - 1]) { + // this._isPlaying = false; + // } + }, timeDiff)) return arr; }, []) } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 214d4bbdc..9e57bca4f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -968,7 +968,12 @@ export class CollectionFreeFormView extends CollectionSubView { // if presentation isn't null, call followmovements on the recording api if (this.presentation) { - const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0); + const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0, this); err && console.log(err) } -- cgit v1.2.3-70-g09d2 From a9ff0c90656de71e37edafba68e946807d41403f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 13:36:13 -0400 Subject: fix infinite pause/play bug with a stopper, but still very buggy --- src/client/util/RecordingApi.ts | 3 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- .../views/nodes/RecordingBox/RecordingView.scss | 20 ++++++++++++-------- .../views/nodes/RecordingBox/RecordingView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 5 +++++ 5 files changed, 22 insertions(+), 12 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 8fb503b00..fec579486 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -188,7 +188,7 @@ export class RecordingApi { // TODO: set userdoc presentMode to browsing this.timers?.map(timer => clearTimeout(timer)) - this.videoBox = null; + // this.videoBox = null; } private videoBox: VideoBox | null = null; @@ -236,6 +236,7 @@ export class RecordingApi { }, timeDiff)) return arr; }, []) + } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9e57bca4f..aa2e0c417 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -970,9 +970,9 @@ export class CollectionFreeFormView extends CollectionSubView
; } -} -// this func communicates with PresBoxElement to send information of the doc -ScriptingGlobals.add(function lookupPresBoxField(presBoxDoc: Doc, field: string, presEleDoc: Doc) { - if (field === 'indexInPres') return DocListCast(presBoxDoc._viewType === CollectionViewType.Tree ? presBoxDoc.presentationLinearizedDocuments : presBoxDoc[StrCast(presBoxDoc.presentationFieldKey)]).indexOf(presEleDoc); - if (field === 'presCollapsedHeight') return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(presBoxDoc._viewType as any) ? 35 : 31; - if (field === 'presStatus') return presBoxDoc.presStatus; - if (field === '_itemIndex') return presBoxDoc._itemIndex; - if (field === 'presBox') return presBoxDoc; - return undefined; -}); \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 19435d22c..b9951eaae 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -34,12 +34,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { _heightDisposer: IReactionDisposer | undefined; @observable _dragging = false; - // these fields are conditionally computed fields on the layout document that take this document as a parameter - @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) - @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up - @computed get presStatus() { return StrCast(this.lookupField("presStatus")); } - @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); } - @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); } + @computed get indexInPres() { return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, "data")]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) + @computed get collapsedHeight() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31; } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up + @computed get presStatus() { return this.presBox.presStatus; } + @computed get presBox() { return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!; } @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; } componentDidMount() { -- cgit v1.2.3-70-g09d2 From f8840672a7bcb3f337d8e50098f374a1b2d441ce Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 01:17:24 -0400 Subject: added ability to make thumbnails of dashboards. started to cleanup dockingView/goldenlayout undo event handling. cleaned up tab doc list in docking view. made titles editable again in treeview. fixed overlay view to work with image docs etc by setting top/left in css --- src/client/documents/Documents.ts | 6 +-- src/client/goldenLayout.js | 2 +- src/client/util/CurrentUserUtils.ts | 26 ++++++++--- src/client/views/DocumentButtonBar.tsx | 53 ++++++++++------------ src/client/views/MainView.tsx | 12 +---- src/client/views/OverlayView.scss | 2 + src/client/views/OverlayView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 21 ++++++--- src/client/views/collections/TabDocView.tsx | 28 ++++-------- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 45 ++++++++++-------- src/client/views/nodes/WebBoxRenderer.js | 4 +- src/client/views/topbar/TopBar.tsx | 6 +-- 13 files changed, 108 insertions(+), 103 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 817478b2f..acc1e341a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -671,9 +671,9 @@ export namespace Docs { return viewDoc; } - export function ImageDocument(url: string, options: DocumentOptions = {}) { - const imgField = new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); + export function ImageDocument(url: string|ImageField, options: DocumentOptions = {}) { + const imgField = url instanceof ImageField ? url : new ImageField(url); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); } export function PresDocument(options: DocumentOptions = {}) { diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 82b10608d..4735f216f 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -1431,7 +1431,7 @@ this.root.callDownwards('_$init'); if (config.maximisedItemId === '__glMaximised') { - this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise(); + this.root.getItemsById(config.maximisedItemId)[0]?.toggleMaximise(); } }, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 51bfdbbd2..885679b64 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,7 +8,7 @@ import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -22,11 +22,10 @@ import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; -import { LabelBox } from "../views/nodes/LabelBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; -import { DragManager, dropActionType } from "./DragManager"; +import { DragManager } from "./DragManager"; import { makeTemplate, MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; @@ -1185,9 +1184,24 @@ export class CurrentUserUtils { } 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); + const docView = CollectionDockingView.Instance.props.DocumentView?.(); + const content = docView?.ContentDiv; + if (docView && content) { + const _width = Number(getComputedStyle(content).width.replace("px","")); + const _height = Number(getComputedStyle(content).height.replace("px","")); + CollectionFreeFormView.UpdateIcon( + docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + content, + _width, _height, + _width, _height, 0, + (iconFile, _nativeWidth, _nativeHeight) => { + const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); + const proto = Cast(img.proto, Doc, null)!; + proto["data-nativeWidth"] = _width; + proto["data-nativeHeight"] = _height; + docView.dataDoc.thumb = img; + }); + } } public static createNewDashboard = async (userDoc: Doc, id?: string) => { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c0645f0ab..103734b9b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -213,34 +213,31 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @undoBatch @action pinWithView = (targetDoc: Doc) => { - if (targetDoc) { - TabDocView.PinDoc(targetDoc); - setTimeout(() => { - const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; - const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); - if (scrollable) { - const scroll = targetDoc._scrollTop; - activeDoc.presPinView = true; - activeDoc.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeDoc.presPinView = true; - activeDoc.presStartTime = targetDoc._currentTimecode; - activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (pannable) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeDoc.presPinClipWidth = width; - activeDoc.presPinView = true; - } - }); + const activeDoc = targetDoc && TabDocView.PinDoc(targetDoc); + if (activeDoc) { + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; + const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); + if (scrollable) { + const scroll = targetDoc._scrollTop; + activeDoc.presPinView = true; + activeDoc.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeDoc.presPinView = true; + activeDoc.presStartTime = targetDoc._currentTimecode; + activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if (pannable) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeDoc.presPinClipWidth = width; + activeDoc.presPinView = true; + } } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index cdf85c905..a30db66e4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -226,17 +226,7 @@ export class MainView extends React.Component { if (received && !this.userDoc) { reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true }); } else { - if (received && CurrentUserUtils._urlState.sharing) { - reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized, - initialized => initialized && received && DocServer.GetRefField(received).then(docField => { - if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) { - CollectionDockingView.AddSplit(docField, "right"); - } - }), - ); - } - const activeDash = PromiseValue(this.userDoc.activeDashboard); - activeDash.then(dash => { + PromiseValue(this.userDoc.activeDashboard).then(dash => { if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash); else CurrentUserUtils.createNewDashboard(this.userDoc); }); diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 555f4298d..f22e68ff5 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -46,4 +46,6 @@ .overlayView-doc { z-index: 9002; //so that it appears above chroma position: absolute; + top: 0; + left: 0; } \ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index c3c103b50..f5e686473 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -200,7 +200,7 @@ export class OverlayView extends React.Component { ScreenToLocalTransform={this.docScreenToLocalXf(d)} renderDepth={1} isDocumentActive={returnTrue} - isContentActive={retu} + isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index de2106e4a..5af72b7d1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -26,6 +26,7 @@ import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); +import { SelectionManager } from '../../util/SelectionManager'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -50,9 +51,8 @@ export class CollectionDockingView extends CollectionSubView() { public _flush: UndoManager.Batch | undefined; private _ignoreStateChange = ""; public tabMap: Set = new Set(); - public get initialized() { return this._goldenLayout !== null; } public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; } - @observable private _goldenLayout: any = null; + private _goldenLayout: any = null; constructor(props: SubCollectionViewProps) { super(props); @@ -118,6 +118,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch public static OpenFullScreen(doc: Doc) { + SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") { return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); @@ -271,7 +272,7 @@ export class CollectionDockingView extends CollectionSubView() { return true; } - async setupGoldenLayout() { + setupGoldenLayout = async () => { const config = StrCast(this.props.Document.dockingConfig); if (config) { const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); @@ -291,7 +292,7 @@ export class CollectionDockingView extends CollectionSubView() { } this.tabMap.clear(); this._goldenLayout?.destroy(); - runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config))); + this._goldenLayout = new GoldenLayout(JSON.parse(config)); this._goldenLayout.on('tabCreated', this.tabCreated); this._goldenLayout.on('tabDestroyed', this.tabDestroyed); this._goldenLayout.on('stackCreated', this.stackCreated); @@ -322,7 +323,7 @@ export class CollectionDockingView extends CollectionSubView() { } this._ignoreStateChange = ""; }); - setTimeout(() => this.setupGoldenLayout(), 0); + setTimeout(this.setupGoldenLayout); //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } } @@ -413,19 +414,25 @@ export class CollectionDockingView extends CollectionSubView() { const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); const changesMade = this.props.Document.dockcingConfig !== json; if (changesMade && !this._flush) { - this.props.Document.dockingConfig = json; - this.props.Document.data = new List(docs); + UndoManager.RunInBatch(() => { + this.props.Document.dockingConfig = json; + this.props.Document.data = new List(docs); + }, "state changed"); } return changesMade; } tabDestroyed = (tab: any) => { + const dview = CollectionDockingView.Instance.props.Document; + const fieldKey = CollectionDockingView.Instance.props.fieldKey; + Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); this.stateChanged(); } tabCreated = (tab: any) => { + this.tabMap.add(tab); tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 0141724ea..75a8207dd 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -82,7 +82,6 @@ export class TabDocView extends React.Component { const iconWrap = document.createElement("div"); const closeWrap = document.createElement("div"); - titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; titleEle.onkeydown = (e: KeyboardEvent) => { @@ -185,14 +184,13 @@ export class TabDocView extends React.Component { UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); } - CollectionDockingView.Instance.tabMap.add(tab); } /** * Adds a document to the presentation view **/ @action - public static async PinDoc(doc: Doc, pinProps?: PinProps) { + public static PinDoc(doc: Doc, pinProps?: PinProps) { //add this new doc to props.Document // all docs will be added to the ActivePresentation as stored on CurrentUserUtils @@ -246,19 +244,14 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - const dview = CollectionDockingView.Instance.props.Document; - const fieldKey = CollectionDockingView.Instance.props.fieldKey; - const tabdocs = await DocListCastAsync(dview[fieldKey]); - runInAction(() => { - if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { - tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs - CollectionDockingView.AddSplit(curPres, "right"); - } - PresBox.Instance?._selectedArray.clear(); - pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); - batch.end(); - }); + if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { + CollectionDockingView.AddSplit(curPres, "right"); + setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + } + PresBox.Instance?._selectedArray.clear(); + pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs + return pinDoc; } } @@ -282,7 +275,6 @@ export class TabDocView extends React.Component { componentWillUnmount() { this._tabReaction?.(); this._view && DocumentManager.Instance.RemoveView(this._view); - this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab); this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged); } @@ -430,9 +422,7 @@ export class TabDocView extends React.Component { }} ref={ref => { if (this._mainCont = ref) { if (this._lastTab) { - console.log("DUP tab") this._view && DocumentManager.Instance.RemoveView(this._view); - CollectionDockingView.Instance.tabMap.delete(this._lastTab); } this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index befd2020c..09f05f69a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -609,10 +609,8 @@ export class TreeView extends React.Component { return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); } - // TODO: currently doc focus works, but can't seem to edit title - // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildClick = () => { - return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()); + return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 77cf0fc84..5f927a2a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1489,7 +1489,7 @@ export class CollectionFreeFormView extends CollectionSubView { + static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { if (oldDiv.childNodes && newDiv) { for (let i = 0; i < oldDiv.childNodes.length; i++) { this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); @@ -1508,32 +1508,39 @@ export class CollectionFreeFormView extends CollectionSubView { - const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + updateIcon = () => CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + this.props.docViewPath().lastElement().ContentDiv!, + this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), + this.props.PanelWidth(), this.props.PanelHeight(), 0, + (iconFile, nativeWidth, nativeHeight) => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }); + + public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number, + panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + newDiv.style.width = width.toString(); + newDiv.style.height = height.toString(); this.replaceCanvases(docViewContent, newDiv); - const htmlString = this._mainCont && new XMLSerializer().serializeToString(newDiv); - const nativeWidth = this.layoutDoc[WidthSym](); - const nativeHeight = this.layoutDoc[HeightSym](); - + const htmlString = new XMLSerializer().serializeToString(newDiv); + const nativeWidth = width; + const nativeHeight = height; CreateImage( - "", + Utils.prepend(""), document.styleSheets, htmlString, nativeWidth, - nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), - NumCast(this.layoutDoc._scrollTop) + nativeWidth * panelHeight / panelWidth, + scrollTop ).then ((data_url: any) => { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then( - returnedfilename => setTimeout(action(() => { - - this.dataDoc.icon = new ImageField(returnedfilename); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }), 500)); + VideoBox.convertDataUri(data_url, filename).then( + returnedfilename => setTimeout(() => { + cb(returnedfilename as string, nativeWidth, nativeHeight); + }, 500)); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index d9524dd6e..f3f1bcf5c 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -214,8 +214,8 @@ var ForeignHtmlRenderer = function (styleSheets) { .replace(/noscript/g, "div").replace(/
<\/div>/g, "") // when scripting isn't available (ie, rendering web pages here),
} placement="bottom"> @@ -57,12 +57,12 @@ export class TopBar extends React.Component { await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); batch.end(); }}> - {"Snapshot"} + Snapshot Browsing mode for directly navigating to documents} placement="bottom">
MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> - {"Explore"} + Explore
-- cgit v1.2.3-70-g09d2 From 7ab50d37d38a91d564cf3150be35a68d11bdcffe Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 01:45:05 -0400 Subject: undo/redo fixes for docking views (maximizing, closing stacks). making maximised tabs persist across refreshes. --- src/client/views/collections/CollectionDockingView.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5af72b7d1..745565ff5 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -298,13 +298,6 @@ export class CollectionDockingView extends CollectionSubView() { this._goldenLayout.on('stackCreated', this.stackCreated); this._goldenLayout.registerComponent('DocumentFrameRenderer', TabDocView); this._goldenLayout.container = this._containerRef.current; - if (this._goldenLayout.config.maximisedItemId === '__glMaximised') { - try { - this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise(); - } catch (e) { - this._goldenLayout.config.maximisedItemId = null; - } - } this._goldenLayout.init(); this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped); this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart); @@ -369,7 +362,7 @@ export class CollectionDockingView extends CollectionSubView() { const htmlTarget = e.target as HTMLElement; window.addEventListener("mouseup", this.onPointerUp); if (!htmlTarget.closest("*.lm_content") && (htmlTarget.closest("*.lm_tab") || htmlTarget.closest("*.lm_stack"))) { - if (htmlTarget.className !== "lm_close_tab") { + if (!htmlTarget.className.includes("lm_close") && !htmlTarget.className.includes("lm_maximise")) { this._flush = UndoManager.StartBatch("golden layout edit"); } } @@ -459,6 +452,9 @@ export class CollectionDockingView extends CollectionSubView() { alert('cant delete the last stack'); } })); + + stack.header?.controlsContainer.find('.lm_maximise') //get the close icon + .click(() => setTimeout(this.stateChanged)); stack.header?.controlsContainer.find('.lm_popout') //get the popout icon .off('click') //unbind the current click handler .click(action(() => { -- cgit v1.2.3-70-g09d2 From 58c2085ceb20cd2527ac69088efb7a46b20f8434 Mon Sep 17 00:00:00 2001 From: Jenny Yu Date: Wed, 8 Jun 2022 00:49:12 -0700 Subject: styling: pres element box --- src/client/views/DocumentDecorations.tsx | 44 +- src/client/views/collections/TabDocView.tsx | 904 +++++++++++----------- src/client/views/nodes/trails/PresElementBox.scss | 338 ++++---- src/client/views/nodes/trails/PresElementBox.tsx | 20 +- 4 files changed, 656 insertions(+), 650 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 0e9bf50fc..b666b5977 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -153,29 +153,29 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P _deleteAfterIconify = false; _iconifyBatch: UndoManager.Batch | undefined; onCloseClick = (forceDeleteOrIconify: boolean | undefined) => { - if (this.canDelete) { - const views = SelectionManager.Views().slice().filter(v => v); - if (forceDeleteOrIconify === false && this._iconifyBatch) return; - this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false; - if (!this._iconifyBatch) { - this._iconifyBatch = UndoManager.StartBatch("iconifying"); - } else { - forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes - } - var iconifyingCount = views.length; - const finished = action((force?: boolean) => { - if ((force || --iconifyingCount === 0) && this._iconifyBatch) { - if (this._deleteAfterIconify) { - views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); - SelectionManager.DeselectAll(); - } - this._iconifyBatch?.end(); - this._iconifyBatch = undefined; - } - }); - if (forceDeleteOrIconify) finished(forceDeleteOrIconify); - else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished)); + + const views = SelectionManager.Views().slice().filter(v => v); + if (forceDeleteOrIconify === false && this._iconifyBatch) return; + this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false; + if (!this._iconifyBatch) { + this._iconifyBatch = UndoManager.StartBatch("iconifying"); + } else { + forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes } + var iconifyingCount = views.length; + const finished = action((force?: boolean) => { + if ((force || --iconifyingCount === 0) && this._iconifyBatch) { + if (this._deleteAfterIconify) { + views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); + SelectionManager.DeselectAll(); + } + this._iconifyBatch?.end(); + this._iconifyBatch = undefined; + } + }); + if (forceDeleteOrIconify) finished(forceDeleteOrIconify); + else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished)); + } onMaximizeDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, () => { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 2b78b20ea..39add0534 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -38,490 +38,490 @@ import React = require("react"); const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { - documentId: FieldId; - glContainer: any; + documentId: FieldId; + glContainer: any; } @observer export class TabDocView extends React.Component { - _mainCont: HTMLDivElement | null = null; - _tabReaction: IReactionDisposer | undefined; - @observable _activated: boolean = false; - @observable _panelWidth = 0; - @observable _panelHeight = 0; - @observable _isActive: boolean = false; - @observable _document: Doc | undefined; - @observable _view: DocumentView | undefined; + _mainCont: HTMLDivElement | null = null; + _tabReaction: IReactionDisposer | undefined; + @observable _activated: boolean = false; + @observable _panelWidth = 0; + @observable _panelHeight = 0; + @observable _isActive: boolean = false; + @observable _document: Doc | undefined; + @observable _view: DocumentView | undefined; - @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } - @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); } - @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? "black" : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); } - // @computed get renderBounds() { - // const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0]; - // const xbounds = bounds[2] - bounds[0]; - // const ybounds = bounds[3] - bounds[1]; - // const dim = Math.max(xbounds, ybounds); - // return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim }; - // } + @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } + @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); } + @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? "black" : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); } + // @computed get renderBounds() { + // const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0]; + // const xbounds = bounds[2] - bounds[0]; + // const ybounds = bounds[3] - bounds[1]; + // const dim = Math.max(xbounds, ybounds); + // return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim }; + // } - get stack() { return (this.props as any).glContainer.parent.parent; } - get tab() { return (this.props as any).glContainer.tab; } - get view() { return this._view; } + get stack() { return (this.props as any).glContainer.parent.parent; } + get tab() { return (this.props as any).glContainer.tab; } + get view() { return this._view; } - @action - init = (tab: any, doc: Opt) => { - if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true; - if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { - tab._disposers = {} as { [name: string]: IReactionDisposer }; - tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true); - tab.DashDoc = doc; - CollectionDockingView.Instance.tabMap.add(tab); - const iconType: IconProp = Doc.toIcon(doc); - // setup the title element and set its size according to the # of chars in the title. Show the full title when clicked. - const titleEle = tab.titleElement[0]; - const iconWrap = document.createElement("div"); - const closeWrap = document.createElement("div"); + @action + init = (tab: any, doc: Opt) => { + if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true; + if (tab.DashDoc !== doc && doc && tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { + tab._disposers = {} as { [name: string]: IReactionDisposer }; + tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true); + tab.DashDoc = doc; + CollectionDockingView.Instance.tabMap.add(tab); + const iconType: IconProp = Doc.toIcon(doc); + // setup the title element and set its size according to the # of chars in the title. Show the full title when clicked. + const titleEle = tab.titleElement[0]; + const iconWrap = document.createElement("div"); + const closeWrap = document.createElement("div"); - titleEle.size = StrCast(doc.title).length + 3; - titleEle.value = doc.title; - titleEle.onkeydown = (e: KeyboardEvent) => { - e.stopPropagation(); - }; - titleEle.onchange = undoBatch(action((e: any) => { - titleEle.size = e.currentTarget.value.length + 3; - Doc.GetProto(doc).title = e.currentTarget.value; - })); + titleEle.size = StrCast(doc.title).length + 3; + titleEle.value = doc.title; + titleEle.onkeydown = (e: KeyboardEvent) => { + e.stopPropagation(); + }; + titleEle.onchange = undoBatch(action((e: any) => { + titleEle.size = e.currentTarget.value.length + 3; + Doc.GetProto(doc).title = e.currentTarget.value; + })); - const dragBtnDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction); - }; + const dragBtnDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction); + }; - if (tab.element[0].children[1].children.length === 1) { - iconWrap.className = "lm_iconWrap"; - iconWrap.id = "lm_iconWrap"; - closeWrap.className = "lm_iconWrap"; - closeWrap.id = "lm_closeWrap"; - closeWrap.onclick = (e: MouseEvent) => { - tab.header.parent.contentItem.remove(); - Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); - Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); - }; - const docIcon = ; - const closeIcon = ; - ReactDOM.render(docIcon, iconWrap); - ReactDOM.render(closeIcon, closeWrap); - // tab.element[0].append(closeWrap); - 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"; - iconWrap.style.color = textColor; - closeWrap.style.color = textColor; - moreInfoDrag.style.backgroundColor = textColor; - tab.element[0].style.background = !layer ? color : "dimgrey"; - }, { fireImmediately: true }); - } - // shifts the focus to this tab when another tab is dragged over it - tab.element[0].onmouseenter = (e: MouseEvent) => { - if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) { - tab.header.parent.setActiveContentItem(tab.contentItem); - tab.setActive(true); - } - }; - + if (tab.element[0].children[1].children.length === 1) { + iconWrap.className = "lm_iconWrap"; + iconWrap.id = "lm_iconWrap"; + closeWrap.className = "lm_iconWrap"; + closeWrap.id = "lm_closeWrap"; + closeWrap.onclick = (e: MouseEvent) => { + tab.header.parent.contentItem.remove(); + Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); + Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); + }; + const docIcon = ; + const closeIcon = ; + ReactDOM.render(docIcon, iconWrap); + ReactDOM.render(closeIcon, closeWrap); + // tab.element[0].append(closeWrap); + 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"; + iconWrap.style.color = textColor; + closeWrap.style.color = textColor; + moreInfoDrag.style.backgroundColor = textColor; + tab.element[0].style.background = !layer ? color : "dimgrey"; + }, { fireImmediately: true }); + } + // shifts the focus to this tab when another tab is dragged over it + tab.element[0].onmouseenter = (e: MouseEvent) => { + if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) { + tab.header.parent.setActiveContentItem(tab.contentItem); + tab.setActive(true); + } + }; - // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected - titleEle.onpointerdown = action((e: any) => { - if (e.target.className !== "lm_iconWrap") { - if (this.view) SelectionManager.SelectView(this.view, false); - else this._activated = true; - if (Date.now() - titleEle.lastClick < 1000) titleEle.select(); - titleEle.lastClick = Date.now(); - (document.activeElement !== titleEle) && titleEle.focus(); - } - }); - tab._disposers.selectionDisposer = reaction(() => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc), - action((selected) => { - if (selected) this._activated = true; - const toggle = tab.element[0].children[1].children[0] as HTMLInputElement; - selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && - UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), "tab switch"); - // toggle.style.fontWeight = selected ? "bold" : ""; - // toggle.style.textTransform = selected ? "uppercase" : ""; - })); - //attach the selection doc buttons menu to the drag handle - const stack: HTMLDivElement = tab.contentItem.parent; - const header: HTMLDivElement = tab; - stack.onscroll = action((e: any) => { - console.log('scrolling...'); - }); - const moreInfoDrag = document.createElement("div"); - moreInfoDrag.className = "lm_iconWrap"; - tab._disposers.buttonDisposer = reaction(() => this.view, view => - view && [ReactDOM.render( [view]} Stack={stack} />, moreInfoDrag), tab._disposers.buttonDisposer?.()], - { fireImmediately: true }); - // tab.reactComponents = [moreInfoDrag]; - // tab.element[0].children[3].before(moreInfoDrag); + // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected + titleEle.onpointerdown = action((e: any) => { + if (e.target.className !== "lm_iconWrap") { + if (this.view) SelectionManager.SelectView(this.view, false); + else this._activated = true; + if (Date.now() - titleEle.lastClick < 1000) titleEle.select(); + titleEle.lastClick = Date.now(); + (document.activeElement !== titleEle) && titleEle.focus(); + } + }); + tab._disposers.selectionDisposer = reaction(() => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc), + action((selected) => { + if (selected) this._activated = true; + const toggle = tab.element[0].children[1].children[0] as HTMLInputElement; + selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && + UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), "tab switch"); + // toggle.style.fontWeight = selected ? "bold" : ""; + // toggle.style.textTransform = selected ? "uppercase" : ""; + })); - // highlight the tab when the tab document is brushed in any part of the UI - tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { - //titleEle.value = title; - // titleEle.style.padding = degree ? 0 : 2; - // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`; - }, { fireImmediately: true }); + //attach the selection doc buttons menu to the drag handle + const stack: HTMLDivElement = tab.contentItem.parent; + const header: HTMLDivElement = tab; + stack.onscroll = action((e: any) => { + console.log('scrolling...'); + }); + const moreInfoDrag = document.createElement("div"); + moreInfoDrag.className = "lm_iconWrap"; + tab._disposers.buttonDisposer = reaction(() => this.view, view => + view && [ReactDOM.render( [view]} Stack={stack} />, moreInfoDrag), tab._disposers.buttonDisposer?.()], + { fireImmediately: true }); + // tab.reactComponents = [moreInfoDrag]; + // tab.element[0].children[3].before(moreInfoDrag); - // clean up the tab when it is closed - tab.closeElement.off('click') //unbind the current click handler - .click(function () { - Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - SelectionManager.DeselectAll(); - UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); - }); - } - } + // highlight the tab when the tab document is brushed in any part of the UI + tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { + //titleEle.value = title; + // titleEle.style.padding = degree ? 0 : 2; + // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`; + }, { fireImmediately: true }); - /** - * Adds a document to the presentation view - **/ - @action - public static async PinDoc(doc: Doc, pinProps?: PinProps) { - if (pinProps?.unpin) console.log('TODO: Remove UNPIN from this location'); - //add this new doc to props.Document - const curPres = CurrentUserUtils.ActivePresentation; - if (curPres) { - if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; } - const batch = UndoManager.StartBatch("pinning doc"); - const pinDoc = Doc.MakeAlias(doc); - pinDoc.presentationTargetDoc = doc; - pinDoc.title = doc.title + " - Slide"; - pinDoc.data = new List(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data - pinDoc.presMovement = PresMovement.Zoom; - pinDoc.groupWithUp = false; - pinDoc.context = curPres; - // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time - pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area - pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling. - pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc. - pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field - pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view - pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header - pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header - const presArray: Doc[] = PresBox.Instance?.sortArray(); - const size: number = PresBox.Instance?._selectedArray.size; - const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; - const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); - Doc.AddDocToList(curPres, "data", pinDoc, presSelected); - if (!pinProps?.audioRange && duration !== undefined) { - pinDoc.mediaStart = "manual"; - pinDoc.mediaStop = "manual"; - pinDoc.presStartTime = NumCast(doc.clipStart); - pinDoc.presEndTime = NumCast(doc.clipEnd, duration); + // clean up the tab when it is closed + tab.closeElement.off('click') //unbind the current click handler + .click(function () { + Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + SelectionManager.DeselectAll(); + UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); + }); } - //save position - if (pinProps?.setPosition || pinDoc.isInkMask) { - pinDoc.setPosition = true; - pinDoc.y = doc.y; - pinDoc.x = doc.x; - pinDoc.presHideAfter = true; - pinDoc.presHideBefore = true; - pinDoc.title = doc.title + " (move)"; - pinDoc.presMovement = PresMovement.None; - } - if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - const dview = CollectionDockingView.Instance.props.Document; - const fieldKey = CollectionDockingView.Instance.props.fieldKey; - const sublists = DocListCast(dview[fieldKey]); - const tabs = Cast(sublists[0], Doc, null); - const tabdocs = await DocListCastAsync(tabs?.data); - runInAction(() => { - if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { - tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs - CollectionDockingView.AddSplit(curPres, "right"); - } - PresBox.Instance?._selectedArray.clear(); - pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); - batch.end(); - }); - } - } + } - componentDidMount() { - new _global.ResizeObserver(action((entries: any) => { - for (const entry of entries) { - this._panelWidth = entry.contentRect.width; - this._panelHeight = entry.contentRect.height; + /** + * Adds a document to the presentation view + **/ + @action + public static async PinDoc(doc: Doc, pinProps?: PinProps) { + if (pinProps?.unpin) console.log('TODO: Remove UNPIN from this location'); + //add this new doc to props.Document + const curPres = CurrentUserUtils.ActivePresentation; + if (curPres) { + if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; } + const batch = UndoManager.StartBatch("pinning doc"); + const pinDoc = Doc.MakeAlias(doc); + pinDoc.presentationTargetDoc = doc; + pinDoc.title = doc.title; + pinDoc.data = new List(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data + pinDoc.presMovement = PresMovement.Zoom; + pinDoc.groupWithUp = false; + pinDoc.context = curPres; + // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time + pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area + pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling. + pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc. + pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field + pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view + pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header + pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header + const presArray: Doc[] = PresBox.Instance?.sortArray(); + const size: number = PresBox.Instance?._selectedArray.size; + const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; + const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); + Doc.AddDocToList(curPres, "data", pinDoc, presSelected); + if (!pinProps?.audioRange && duration !== undefined) { + pinDoc.mediaStart = "manual"; + pinDoc.mediaStop = "manual"; + pinDoc.presStartTime = NumCast(doc.clipStart); + pinDoc.presEndTime = NumCast(doc.clipEnd, duration); + } + //save position + if (pinProps?.setPosition || pinDoc.isInkMask) { + pinDoc.setPosition = true; + pinDoc.y = doc.y; + pinDoc.x = doc.x; + pinDoc.presHideAfter = true; + pinDoc.presHideBefore = true; + pinDoc.title = doc.title + " (move)"; + pinDoc.presMovement = PresMovement.None; + } + if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; + const dview = CollectionDockingView.Instance.props.Document; + const fieldKey = CollectionDockingView.Instance.props.fieldKey; + const sublists = DocListCast(dview[fieldKey]); + const tabs = Cast(sublists[0], Doc, null); + const tabdocs = await DocListCastAsync(tabs?.data); + runInAction(() => { + if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { + tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs + CollectionDockingView.AddSplit(curPres, "right"); + } + PresBox.Instance?._selectedArray.clear(); + pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array + DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); + batch.end(); + }); } - })).observe(this.props.glContainer._element[0]); - this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); - this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined); - // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }), - // ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""), - // { fireImmediately: true }); - } + } - componentWillUnmount() { - this._tabReaction?.(); - this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab); + componentDidMount() { + new _global.ResizeObserver(action((entries: any) => { + for (const entry of entries) { + this._panelWidth = entry.contentRect.width; + this._panelHeight = entry.contentRect.height; + } + })).observe(this.props.glContainer._element[0]); + this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); + this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined); + // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }), + // ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""), + // { fireImmediately: true }); + } - this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged); - } + componentWillUnmount() { + this._tabReaction?.(); + this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab); - @action.bound - private onActiveContentItemChanged(contentItem: any) { - if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { - this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; - !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. - } - } + this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged); + } + + @action.bound + private onActiveContentItemChanged(contentItem: any) { + if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { + this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; + !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. + } + } - // adds a tab to the layout based on the locaiton parameter which can be: - // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab, - // add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right - // replace[:{left,right,top,bottom,}] - e.g., "replace" will replace the current stack contents, - // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, - // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right - // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack - addDocTab = (doc: Doc, location: string) => { - SelectionManager.DeselectAll(); - const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); - const locationParams = locationFields.length > 1 ? locationFields[1] : ""; - switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); - case "close": return CollectionDockingView.CloseSplit(doc, locationParams); - case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); - case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - case "lightbox": return LightboxView.AddDocTab(doc, location); - case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); - case "inPlace": - case "add": - default: - return CollectionDockingView.AddSplit(doc, locationParams, this.stack); - } - } - remDocTab = (doc: Doc | Doc[]) => { - if (doc === this._document) { + // adds a tab to the layout based on the locaiton parameter which can be: + // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab, + // add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right + // replace[:{left,right,top,bottom,}] - e.g., "replace" will replace the current stack contents, + // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, + // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right + // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack + addDocTab = (doc: Doc, location: string) => { SelectionManager.DeselectAll(); - CollectionDockingView.CloseSplit(this._document); - return true; - } - return false; - } + const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); + const locationParams = locationFields.length > 1 ? locationFields[1] : ""; + switch (locationFields[0]) { + case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + case "close": return CollectionDockingView.CloseSplit(doc, locationParams); + case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); + case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); + case "lightbox": return LightboxView.AddDocTab(doc, location); + case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); + case "inPlace": + case "add": + default: + return CollectionDockingView.AddSplit(doc, locationParams, this.stack); + } + } + remDocTab = (doc: Doc | Doc[]) => { + if (doc === this._document) { + SelectionManager.DeselectAll(); + CollectionDockingView.CloseSplit(this._document); + return true; + } + return false; + } - getCurrentFrame = () => { - return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame); - } - @action - focusFunc = (doc: Doc, options?: DocFocusOptions) => { - const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; - if (shrinkwrap && this._document) { - const focusSpeed = 1000; - shrinkwrap(); - this._document._viewTransition = `transform ${focusSpeed}ms`; - setTimeout(action(() => { - this._document!._viewTransition = undefined; - options?.afterFocus?.(false); - }), focusSpeed); - } else { - options?.afterFocus?.(false); - } - if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { - this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) - } - } - active = () => this._isActive; - @observable _forceInvalidateScreenToLocal = 0; - ScreenToLocalTransform = () => { - this._forceInvalidateScreenToLocal; - const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); - return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY); - } - PanelWidth = () => this._panelWidth; - PanelHeight = () => this._panelHeight; - miniMapColor = () => this.tabColor; - tabView = () => this._view; - disableMinimap = () => !this._document || (this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform); - hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap); + getCurrentFrame = () => { + return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame); + } + @action + focusFunc = (doc: Doc, options?: DocFocusOptions) => { + const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; + if (shrinkwrap && this._document) { + const focusSpeed = 1000; + shrinkwrap(); + this._document._viewTransition = `transform ${focusSpeed}ms`; + setTimeout(action(() => { + this._document!._viewTransition = undefined; + options?.afterFocus?.(false); + }), focusSpeed); + } else { + options?.afterFocus?.(false); + } + if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { + this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) + } + } + active = () => this._isActive; + @observable _forceInvalidateScreenToLocal = 0; + ScreenToLocalTransform = () => { + this._forceInvalidateScreenToLocal; + const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); + return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY); + } + PanelWidth = () => this._panelWidth; + PanelHeight = () => this._panelHeight; + miniMapColor = () => this.tabColor; + tabView = () => this._view; + disableMinimap = () => !this._document || (this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform); + hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap); - @computed get docView() { - return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) : - <> this._view = r)} - renderDepth={0} - Document={this._document} - DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - onBrowseClick={MainView.Instance.exploreMode} - isContentActive={returnTrue} - PanelWidth={this.PanelWidth} - PanelHeight={this.PanelHeight} - styleProvider={DefaultStyleProvider} - docFilters={CollectionDockingView.Instance.childDocFilters} - docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} - searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} - addDocument={undefined} - removeDocument={this.remDocTab} - addDocTab={this.addDocTab} - ScreenToLocalTransform={this.ScreenToLocalTransform} - dontCenter={"y"} - rootSelected={returnTrue} - whenChildContentsActiveChanged={emptyFunction} - focus={this.focusFunc} - docViewPath={returnEmptyDoclist} - bringToFront={emptyFunction} - pinToPres={TabDocView.PinDoc} /> - - {this._document.hideMinimap ? "Open minimap" : "Close minimap"}}> -
e.stopPropagation()} - onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} > - -
-
- ; - } + @computed get docView() { + return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) : + <> this._view = r)} + renderDepth={0} + Document={this._document} + DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + onBrowseClick={MainView.Instance.exploreMode} + isContentActive={returnTrue} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + styleProvider={DefaultStyleProvider} + docFilters={CollectionDockingView.Instance.childDocFilters} + docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} + searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} + addDocument={undefined} + removeDocument={this.remDocTab} + addDocTab={this.addDocTab} + ScreenToLocalTransform={this.ScreenToLocalTransform} + dontCenter={"y"} + rootSelected={returnTrue} + whenChildContentsActiveChanged={emptyFunction} + focus={this.focusFunc} + docViewPath={returnEmptyDoclist} + bringToFront={emptyFunction} + pinToPres={TabDocView.PinDoc} /> + + {this._document.hideMinimap ? "Open minimap" : "Close minimap"}}> +
e.stopPropagation()} + onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} > + +
+
+ ; + } - render() { - return ( -
{ - if (this._mainCont = ref) { - (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); - DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document))); - new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); - } - }} > - {this.docView} -
- ); - } + render() { + return ( +
{ + if (this._mainCont = ref) { + (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); + DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document))); + new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); + } + }} > + {this.docView} +
+ ); + } } interface TabMinimapViewProps { - document: Doc; - hideMinimap: () => boolean; - tabView: () => DocumentView | undefined; - addDocTab: (doc: Doc, where: string) => boolean; - PanelWidth: () => number; - PanelHeight: () => number; - background: () => string; + document: Doc; + hideMinimap: () => boolean; + tabView: () => DocumentView | undefined; + addDocTab: (doc: Doc, where: string) => boolean; + PanelWidth: () => number; + PanelHeight: () => number; + background: () => string; } @observer export class TabMinimapView extends React.Component { - static miniStyleProvider = (doc: Opt, props: Opt, property: string): any => { - if (doc) { - switch (property.split(":")[0]) { - default: return DefaultStyleProvider(doc, props, property); - case StyleProp.PointerEvents: return "none"; - case StyleProp.DocContents: - const background = ((type: DocumentType) => { - switch (type) { - case DocumentType.PDF: return "pink"; - case DocumentType.AUDIO: return "lightgreen"; - case DocumentType.WEB: return "brown"; - case DocumentType.IMG: return "blue"; - case DocumentType.MAP: return "orange"; - case DocumentType.VID: return "purple"; - case DocumentType.RTF: return "yellow"; - case DocumentType.COL: return undefined; - default: return "gray"; - } - })(doc.type as DocumentType); - return !background ? - undefined : -
; + static miniStyleProvider = (doc: Opt, props: Opt, property: string): any => { + if (doc) { + switch (property.split(":")[0]) { + default: return DefaultStyleProvider(doc, props, property); + case StyleProp.PointerEvents: return "none"; + case StyleProp.DocContents: + const background = ((type: DocumentType) => { + switch (type) { + case DocumentType.PDF: return "pink"; + case DocumentType.AUDIO: return "lightgreen"; + case DocumentType.WEB: return "brown"; + case DocumentType.IMG: return "blue"; + case DocumentType.MAP: return "orange"; + case DocumentType.VID: return "purple"; + case DocumentType.RTF: return "yellow"; + case DocumentType.COL: return undefined; + default: return "gray"; + } + })(doc.type as DocumentType); + return !background ? + undefined : +
; + } } - } - } - @computed get renderBounds() { - const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView; - const bounds = compView?.freeformData?.(true)?.bounds; - if (!bounds) return undefined; - const xbounds = bounds.r - bounds.x; - const ybounds = bounds.b - bounds.y; - const dim = Math.max(xbounds, ybounds); - return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim }; - } - childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null); - returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150); - miniDown = (e: React.PointerEvent) => { - const doc = this.props.document; - const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 }; - const miniSize = this.returnMiniSize(); - doc && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - doc._panX = clamp(NumCast(doc._panX) + delta[0] / miniSize * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim); - doc._panY = clamp(NumCast(doc._panY) + delta[1] / miniSize * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim); - return false; - }), emptyFunction, emptyFunction); - } - render() { - if (!this.renderBounds) return (null); - const miniWidth = this.props.PanelWidth() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100; - const miniHeight = this.props.PanelHeight() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100; - const miniLeft = 50 + (NumCast(this.props.document._panX) - this.renderBounds.cx) / this.renderBounds.dim * 100 - miniWidth / 2; - const miniTop = 50 + (NumCast(this.props.document._panY) - this.renderBounds.cy) / this.renderBounds.dim * 100 - miniHeight / 2; - const miniSize = this.returnMiniSize(); - return this.props.hideMinimap() ? (null) : -
- -
-
-
-
; - } + } + @computed get renderBounds() { + const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView; + const bounds = compView?.freeformData?.(true)?.bounds; + if (!bounds) return undefined; + const xbounds = bounds.r - bounds.x; + const ybounds = bounds.b - bounds.y; + const dim = Math.max(xbounds, ybounds); + return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim }; + } + childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null); + returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150); + miniDown = (e: React.PointerEvent) => { + const doc = this.props.document; + const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 }; + const miniSize = this.returnMiniSize(); + doc && setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { + doc._panX = clamp(NumCast(doc._panX) + delta[0] / miniSize * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim); + doc._panY = clamp(NumCast(doc._panY) + delta[1] / miniSize * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim); + return false; + }), emptyFunction, emptyFunction); + } + render() { + if (!this.renderBounds) return (null); + const miniWidth = this.props.PanelWidth() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100; + const miniHeight = this.props.PanelHeight() / NumCast(this.props.document._viewScale, 1) / this.renderBounds.dim * 100; + const miniLeft = 50 + (NumCast(this.props.document._panX) - this.renderBounds.cx) / this.renderBounds.dim * 100 - miniWidth / 2; + const miniTop = 50 + (NumCast(this.props.document._panY) - this.renderBounds.cy) / this.renderBounds.dim * 100 - miniHeight / 2; + const miniSize = this.returnMiniSize(); + return this.props.hideMinimap() ? (null) : +
+ +
+
+
+
; + } } diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss index a178be910..1919071df 100644 --- a/src/client/views/nodes/trails/PresElementBox.scss +++ b/src/client/views/nodes/trails/PresElementBox.scss @@ -5,155 +5,157 @@ $slide-background: #d5dce2; $slide-active: #5B9FDD; .presItem-container { - cursor: grab; - display: grid; - grid-template-columns: 20px auto; - font-family: Roboto; - letter-spacing: normal; - position: relative; - pointer-events: all; - width: 100%; - height: 100%; - font-weight: 400; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - align-items: center; - - .presItem-number { - margin-top: 3.5px; - font-size: 12px; - font-weight: 700; - text-align: center; - justify-self: center; - align-self: flex-start; - position: relative; - display: inline-block; - overflow: hidden; - } + cursor: grab; + display: flex; + grid-template-columns: 20px auto; + font-family: Roboto; + letter-spacing: normal; + position: relative; + pointer-events: all; + width: 100%; + height: 100%; + font-weight: 400; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + align-items: center; + + // .presItem-number { + // margin-top: 3.5px; + // font-size: 12px; + // font-weight: 700; + // text-align: center; + // justify-self: center; + // align-self: flex-start; + // position: relative; + // display: inline-block; + // overflow: hidden; + // } } .presItem-slide { - position: relative; - background-color: #d5dce2; - border-radius: 5px; - height: calc(100% - 7px); - width: 100%; - display: grid; - grid-template-rows: 16px 10px auto; - grid-template-columns: max-content max-content max-content max-content auto; - - .presItem-name { - min-width: 20px; - z-index: 300; - top: 2px; - align-self: center; - font-size: 11px; - font-family: Roboto; - font-weight: 500; - position: relative; - padding-left: 10px; - padding-right: 10px; - letter-spacing: normal; - width: max-content; - text-overflow: ellipsis; - overflow: hidden; - white-space: pre; - } - - .presItem-docName { - min-width: 20px; - z-index: 300; - align-self: center; - font-size: 9px; - font-family: Roboto; - font-weight: 300; - position: relative; - padding-left: 10px; - padding-right: 10px; - letter-spacing: normal; - width: max-content; - text-overflow: ellipsis; - overflow: hidden; - white-space: pre; - grid-row: 2; - grid-column: 1/6; - } - - .presItem-time { - align-self: center; - position: relative; - padding-right: 10px; - top: 1px; - font-size: 10; - font-weight: 300; - font-family: Roboto; - z-index: 300; - letter-spacing: normal; - } - - .presItem-embedded { - overflow: hidden; - grid-row: 3; - grid-column: 1/8; - position: relative; - display: flex; - width: auto; - justify-content: center; - margin: auto; - margin-bottom: 2px; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - } - - .presItem-embeddedMask { - width: 100%; - height: 100%; - position: absolute; - border-radius: 3px; - top: 0; - left: 0; - z-index: 1; - overflow: hidden; - } - - - .presItem-slideButtons { - display: flex; - grid-column: 7; - grid-row: 1/3; - width: max-content; - justify-self: right; - justify-content: flex-end; - - .slideButton { - cursor: pointer; + position: relative; + height: 100%; + width: 100%; + border-bottom: .5px solid grey; + display: flex; + justify-content: space-between; + align-items: center; + grid-template-rows: 16px 10px auto; + grid-template-columns: max-content max-content max-content max-content auto; + + .presItem-name { + display: flex; + min-width: 20px; + z-index: 300; + top: 2px; + align-self: center; + font-size: 11px; + font-family: Roboto; + font-weight: 500; position: relative; - border-radius: 100px; + padding-left: 10px; + padding-right: 10px; + letter-spacing: normal; + width: max-content; + text-overflow: ellipsis; + overflow: hidden; + white-space: pre; + } + + .presItem-docName { + min-width: 20px; z-index: 300; - width: 18px; - height: 18px; - display: flex; - font-size: 12px; - justify-self: center; align-self: center; - background-color: rgba(0, 0, 0, 0.5); - color: white; + font-size: 9px; + font-family: Roboto; + font-weight: 300; + position: relative; + padding-left: 10px; + padding-right: 10px; + letter-spacing: normal; + width: max-content; + text-overflow: ellipsis; + overflow: hidden; + white-space: pre; + grid-row: 2; + grid-column: 1/6; + } + + .presItem-time { + align-self: center; + position: relative; + padding-right: 10px; + top: 1px; + font-size: 10; + font-weight: 300; + font-family: Roboto; + z-index: 300; + letter-spacing: normal; + } + + .presItem-embedded { + overflow: hidden; + grid-row: 3; + grid-column: 1/8; + position: relative; + display: flex; + width: auto; justify-content: center; - align-items: center; - transition: 0.2s; - margin-right: 3px; - } - - .slideButton:hover { - background-color: rgba(0, 0, 0, 1); - transform: scale(1.2); - } - } + margin: auto; + margin-bottom: 2px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } + + .presItem-embeddedMask { + width: 100%; + height: 100%; + position: absolute; + border-radius: 3px; + top: 0; + left: 0; + z-index: 1; + overflow: hidden; + } + + + .presItem-slideButtons { + display: flex; + grid-column: 7; + grid-row: 1/3; + width: max-content; + justify-self: right; + justify-content: flex-end; + + .slideButton { + cursor: pointer; + position: relative; + border-radius: 100px; + z-index: 300; + width: 18px; + height: 18px; + display: flex; + font-size: 12px; + justify-self: center; + align-self: center; + background-color: rgba(0, 0, 0, 0.5); + color: white; + justify-content: center; + align-items: center; + transition: 0.2s; + margin-right: 3px; + } + + .slideButton:hover { + background-color: rgba(0, 0, 0, 1); + transform: scale(1.2); + } + } } // .presItem-slide:hover { @@ -192,42 +194,42 @@ $slide-active: #5B9FDD; // } .presItem-slide.active { - box-shadow: 0 0 0px 1.5px $dark-blue; + box-shadow: 0 0 0px 1.5px $dark-blue; } .presItem-multiDrag { - font-family: Roboto; - font-weight: 600; - color: white; - text-align: center; - justify-content: center; - align-content: center; - width: 100px; - height: 30px; - position: absolute; - background-color: $dark-blue; - z-index: 4000; - border-radius: 10px; - box-shadow: black 0.4vw 0.4vw 0.8vw; - line-height: 30px; + font-family: Roboto; + font-weight: 600; + color: white; + text-align: center; + justify-content: center; + align-content: center; + width: 100px; + height: 30px; + position: absolute; + background-color: $dark-blue; + z-index: 4000; + border-radius: 10px; + box-shadow: black 0.4vw 0.4vw 0.8vw; + line-height: 30px; } .presItem-miniSlide { - font-weight: 700; - font-size: 12; - grid-column: 1/8; - align-self: center; - justify-self: center; - background-color: #d5dce2; - width: 26px; - text-align: center; - height: 26px; - line-height: 28px; - border-radius: 100%; + font-weight: 700; + font-size: 12; + grid-column: 1/8; + align-self: center; + justify-self: center; + background-color: #d5dce2; + width: 26px; + text-align: center; + height: 26px; + line-height: 28px; + border-radius: 100%; } .presItem-miniSlide.active { - box-shadow: 0 0 0px 1.5px $dark-blue; + box-shadow: 0 0 0px 1.5px $dark-blue; } // .presItem-slide:hover { diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index f1144b32d..acdf9c43f 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -329,7 +329,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { hideDocumentButtonBar: true, hideDecorationTitle: true, hideOpenButton: true, - hideDeleteButton: true, + // hideDeleteButton: true, cloneFieldFilter: new List(["system"]) }); @@ -387,7 +387,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { onPointerDown={this.headerDown} onPointerUp={this.headerUp} > - {miniView ? + {/* {miniView ? // when width is LESS than 110 px
{`${this.indexInPres + 1}.`} @@ -396,13 +396,17 @@ export class PresElementBox extends ViewBoxBaseComponent() { // when width is MORE than 110 px
{`${this.indexInPres + 1}.`} -
} +
} */} + {/*
+ {`${this.indexInPres + 1}.`} +
*/} {miniView ? (null) :
+
{`${this.indexInPres + 1}. `}
() { SetValue={this.onSetValue} />
-
{"Movement speed"}
}>
{this.transition}
-
{"Duration"}
}>
{this.duration}
+ {/*
{"Movement speed"}
}>
{this.transition}
*/} + {/*
{"Duration"}
}>
{this.duration}
*/}
{"Update view"}
}>
() { } - {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}> + {/* {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}>
activeItem.groupWithUp = !activeItem.groupWithUp} style={{ @@ -453,7 +457,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { e.stopPropagation()} />
-
} + } */} {/*
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> e.stopPropagation()} />
*/} @@ -463,7 +467,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { e.stopPropagation()} />
-
{activeItem.presPinView ? (<>View of {targetDoc.title}) : targetDoc.title}
+ {/*
{activeItem.presPinView ? (<>View of {targetDoc.title}) : targetDoc.title}
*/} {this.renderEmbeddedInline}
}
); -- cgit v1.2.3-70-g09d2 From 5157f0c500b01a635c373cc661a2588ef7506d79 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 12:44:36 -0400 Subject: added context menu back to tab's via the doc icon. made docicon draggable and clickable. removed collectiondockingviewmenu. --- src/client/goldenLayout.js | 5 ++- .../views/collections/CollectionDockingView.tsx | 3 +- .../collections/CollectionDockingViewMenu.scss | 34 --------------- .../collections/CollectionDockingViewMenu.tsx | 48 -------------------- src/client/views/collections/TabDocView.tsx | 51 +++++++++++----------- 5 files changed, 31 insertions(+), 110 deletions(-) delete mode 100644 src/client/views/collections/CollectionDockingViewMenu.scss delete mode 100644 src/client/views/collections/CollectionDockingViewMenu.tsx (limited to 'src/client/views/collections') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 4735f216f..012ee163c 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -458,8 +458,9 @@ if (this._bDragging === true) { this._bDragging = false; this.emit('dragStop', oEvent, this._nOriginalX + this._nX); - } else if (oEvent.target) { // make title receive pointer events to allow setting insertion position or selecting texst range - if (oEvent.target.className.includes("lm_title_wrap")) { + } else { // make title receive pointer events to allow setting insertion position or selecting texst range + const classname = typeof oEvent.target?.className === "string" ? oEvent.target.className : ""; + if (classname.includes("lm_title_wrap")) { oEvent.target.children[0].style.pointerEvents = "all"; oEvent.target.children[0].focus(); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 745565ff5..476ffaf81 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -362,7 +362,8 @@ export class CollectionDockingView extends CollectionSubView() { const htmlTarget = e.target as HTMLElement; window.addEventListener("mouseup", this.onPointerUp); if (!htmlTarget.closest("*.lm_content") && (htmlTarget.closest("*.lm_tab") || htmlTarget.closest("*.lm_stack"))) { - if (!htmlTarget.className.includes("lm_close") && !htmlTarget.className.includes("lm_maximise")) { + const className = typeof htmlTarget.className === "string" ? htmlTarget.className : ""; + if (!className.includes("lm_close") && !className.includes("lm_maximise")) { this._flush = UndoManager.StartBatch("golden layout edit"); } } diff --git a/src/client/views/collections/CollectionDockingViewMenu.scss b/src/client/views/collections/CollectionDockingViewMenu.scss deleted file mode 100644 index 4157f0f7e..000000000 --- a/src/client/views/collections/CollectionDockingViewMenu.scss +++ /dev/null @@ -1,34 +0,0 @@ - -.dockingViewButtonSelector { - div { - overflow: visible !important; - } - - display: inline-block; - width:100%; - height:100%; -} -.dockingViewButtonSelector-flyout { - position: relative; - z-index: 9999; - background-color: #eeeeee; - box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - color: black; - - padding: 10px; - border-radius: 3px; - display: inline-block; - height: 100%; - width: 100%; - border-radius: 3px; - - hr { - height: 1px; - margin: 0px; - background-color: gray; - border-top: 0px; - border-bottom: 0px; - border-right: 0px; - border-left: 0px; - } -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingViewMenu.tsx b/src/client/views/collections/CollectionDockingViewMenu.tsx deleted file mode 100644 index 1cab293a8..000000000 --- a/src/client/views/collections/CollectionDockingViewMenu.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { DocumentButtonBar } from "../DocumentButtonBar"; -import { DocumentView } from "../nodes/DocumentView"; -const higflyout = require("@hig/flyout"); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; - -@observer -export class CollectionDockingViewMenu extends React.Component<{ views: () => DocumentView[], Stack: any }> { - customStylesheet(styles: any) { - return { - ...styles, - panel: { - ...styles.panel, - minWidth: "100px" - }, - }; - } - _ref = React.createRef(); - - @computed get flyout() { - return ( -
- -
- ); - } - - @observable _tooltipOpen: boolean = false; - render() { - return this._tooltipOpen = false)} title={<>
Tap for toolbar, drag to create alias in another pane
} placement="bottom"> - !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} - onPointerDown={action(e => { - this.props.views()[0]?.select(false); - this._tooltipOpen = false; - })} > - - - - -
; - } -} diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 75a8207dd..91c6975ea 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -4,15 +4,15 @@ import { Tooltip } from '@material-ui/core'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; import { clamp } from 'lodash'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; -import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from "../../../fields/RefField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils"; +import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -27,10 +27,10 @@ import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MainView } from '../MainView'; import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; +import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionDockingViewMenu } from './CollectionDockingViewMenu'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; @@ -92,14 +92,28 @@ export class TabDocView extends React.Component { Doc.GetProto(doc).title = e.currentTarget.value; })); - const dragBtnDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction); - }; - - if (tab.element[0].children[1].children.length === 1) { - iconWrap.className = "lm_iconWrap"; - iconWrap.id = "lm_iconWrap"; + iconWrap.className = "lm_iconWrap lm_moreInfo"; + const dragBtnDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, action(e => { + if (this.view) { + SelectionManager.SelectView(this.view, false); + let child = this.view.ContentDiv!.children[0]; + while (child.children.length) { + const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string"); + if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; + if (next?.className?.toString().includes(DashFieldView.name)) break; + if (next) child = next; + else break; + } + simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + } + else { this._activated = true; + setTimeout(() =>this.view && SelectionManager.SelectView(this.view, false)); + } + })); + }; + closeWrap.className = "lm_iconWrap"; closeWrap.id = "lm_closeWrap"; closeWrap.onclick = (e: MouseEvent) => { @@ -111,6 +125,7 @@ export class TabDocView extends React.Component { const closeIcon = ; ReactDOM.render(docIcon, iconWrap); ReactDOM.render(closeIcon, closeWrap); + tab.reactComponents = [iconWrap, closeWrap]; // tab.element[0].append(closeWrap); tab.element[0].prepend(iconWrap); tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }), @@ -122,7 +137,6 @@ export class TabDocView extends React.Component { titleEle.style.backgroundColor = "transparent"; iconWrap.style.color = textColor; closeWrap.style.color = textColor; - moreInfoDrag.style.backgroundColor = textColor; tab.element[0].style.background = !layer ? color : "dimgrey"; }, { fireImmediately: true }); } @@ -155,19 +169,6 @@ export class TabDocView extends React.Component { // toggle.style.textTransform = selected ? "uppercase" : ""; })); - //attach the selection doc buttons menu to the drag handle - const stack: HTMLDivElement = tab.contentItem.parent; - const header: HTMLDivElement = tab; - stack.onscroll = action((e: any) => { - console.log('scrolling...'); - }); - const moreInfoDrag = document.createElement("div"); - moreInfoDrag.className = "lm_iconWrap"; - tab._disposers.buttonDisposer = reaction(() => this.view, view => - view && [ReactDOM.render( [view]} Stack={stack} />, moreInfoDrag), tab._disposers.buttonDisposer?.()], - { fireImmediately: true }); - // tab.reactComponents = [moreInfoDrag]; - // tab.element[0].children[3].before(moreInfoDrag); // highlight the tab when the tab document is brushed in any part of the UI tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => { -- cgit v1.2.3-70-g09d2 From 94975954fbd1ce9b68f6545b16a4eabe4b41cca0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 14:30:18 -0400 Subject: fixed bug with adding to pres when in miniview mode. --- .../views/collections/CollectionDockingView.tsx | 69 ++++++++++------------ src/client/views/collections/TabDocView.tsx | 3 + src/client/views/nodes/trails/PresBox.tsx | 4 +- 3 files changed, 37 insertions(+), 39 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 476ffaf81..a790a0475 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -172,12 +172,6 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { - if (document.type === DocumentType.PRES) { - const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); - if (docs.includes(document)) { - docs.splice(docs.indexOf(document), 1); - } - } if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document); const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); @@ -186,6 +180,7 @@ export class CollectionDockingView extends CollectionSubView() { return true; } const instance = CollectionDockingView.Instance; + const glayRoot = instance._goldenLayout.root; if (!instance) return false; const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName); @@ -194,29 +189,29 @@ export class CollectionDockingView extends CollectionSubView() { stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]); } else { const newItemStackConfig = { type: 'stack', content: [docContentConfig] }; - const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); - if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns - instance._goldenLayout.root.addChild(newContentItem); - } else if (instance._goldenLayout.root.contentItems[0].isStack) { - instance._goldenLayout.root.contentItems[0].addChild(docContentConfig); + const newContentItem = glayRoot.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); + if (glayRoot.contentItems.length === 0) { // if no rows / columns + glayRoot.addChild(newContentItem); + } else if (glayRoot.contentItems[0].isStack) { + glayRoot.contentItems[0].addChild(docContentConfig); } else if ( - instance._goldenLayout.root.contentItems.length === 1 && - instance._goldenLayout.root.contentItems[0].contentItems.length === 1 && - instance._goldenLayout.root.contentItems[0].contentItems[0].contentItems.length === 0) { - instance._goldenLayout.root.contentItems[0].contentItems[0].addChild(docContentConfig); + glayRoot.contentItems.length === 1 && + glayRoot.contentItems[0].contentItems.length === 1 && + glayRoot.contentItems[0].contentItems[0].contentItems.length === 0) { + glayRoot.contentItems[0].contentItems[0].addChild(docContentConfig); } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row switch (pullSide) { default: - case "right": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break; - case "left": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break; + case "right": glayRoot.contentItems[0].addChild(newContentItem); break; + case "left": glayRoot.contentItems[0].addChild(newContentItem, 0); break; case "top": case "bottom": // if not going in a row layout, must add already existing content into column - const rowlayout = instance._goldenLayout.root.contentItems[0]; + const rowlayout = glayRoot.contentItems[0]; const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout); - CollectionDockingView.Instance._goldenLayout.saveScrollTops(rowlayout.element); + instance._goldenLayout.saveScrollTops(rowlayout.element); rowlayout.parent.replaceChild(rowlayout, newColumn); if (pullSide === "top") { newColumn.addChild(rowlayout, undefined, true); @@ -225,23 +220,23 @@ export class CollectionDockingView extends CollectionSubView() { newColumn.addChild(newContentItem, undefined, true); newColumn.addChild(rowlayout, 0, true); } - CollectionDockingView.Instance._goldenLayout.restoreScrollTops(rowlayout.element); + instance._goldenLayout.restoreScrollTops(rowlayout.element); rowlayout.config.height = 50; newContentItem.config.height = 50; } } else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column switch (pullSide) { - case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break; - case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break; + case "top": glayRoot.contentItems[0].addChild(newContentItem, 0); break; + case "bottom": glayRoot.contentItems[0].addChild(newContentItem); break; case "left": case "right": default: // if not going in a row layout, must add already existing content into column - const collayout = instance._goldenLayout.root.contentItems[0]; + const collayout = glayRoot.contentItems[0]; const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout); - CollectionDockingView.Instance._goldenLayout.saveScrollTops(collayout.element); + instance._goldenLayout.saveScrollTops(collayout.element); collayout.parent.replaceChild(collayout, newRow); if (pullSide === "left") { newRow.addChild(collayout, undefined, true); @@ -250,7 +245,7 @@ export class CollectionDockingView extends CollectionSubView() { newRow.addChild(newContentItem, undefined, true); newRow.addChild(collayout, 0, true); } - CollectionDockingView.Instance._goldenLayout.restoreScrollTops(collayout.element); + instance._goldenLayout.restoreScrollTops(collayout.element); collayout.config.width = 50; newContentItem.config.width = 50; @@ -289,19 +284,19 @@ export class CollectionDockingView extends CollectionSubView() { this._goldenLayout.unbind('stackCreated', this.stackCreated); } catch (e) { } } + this.tabMap.clear(); + this._goldenLayout.destroy(); } - this.tabMap.clear(); - this._goldenLayout?.destroy(); - this._goldenLayout = new GoldenLayout(JSON.parse(config)); - this._goldenLayout.on('tabCreated', this.tabCreated); - this._goldenLayout.on('tabDestroyed', this.tabDestroyed); - this._goldenLayout.on('stackCreated', this.stackCreated); - this._goldenLayout.registerComponent('DocumentFrameRenderer', TabDocView); - this._goldenLayout.container = this._containerRef.current; - this._goldenLayout.init(); - this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped); - this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart); - this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged); + const glay = this._goldenLayout = new GoldenLayout(JSON.parse(config)); + glay.on('tabCreated', this.tabCreated); + glay.on('tabDestroyed', this.tabDestroyed); + glay.on('stackCreated', this.stackCreated); + glay.registerComponent('DocumentFrameRenderer', TabDocView); + glay.container = this._containerRef.current; + glay.init(); + glay.root.layoutManager.on('itemDropped', this.tabItemDropped); + glay.root.layoutManager.on('dragStart', this.tabDragStart); + glay.root.layoutManager.on('activeContentItemChanged', this.stateChanged); } } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 91c6975ea..34fe572e3 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -35,6 +35,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV import { CollectionView, CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; import React = require("react"); +import { listSpec } from '../../../fields/Schema'; const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { @@ -246,6 +247,8 @@ export class TabDocView extends React.Component { } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { + const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); + if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); CollectionDockingView.AddSplit(curPres, "right"); setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 1a4be8e0c..591480023 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -403,11 +403,11 @@ export class PresBox extends ViewBoxBaseComponent() { // openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); - await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. -- cgit v1.2.3-70-g09d2 From ec2e780141fb41d3f70aa86b0312211e0b30257c Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 10:39:14 -0400 Subject: fixed issues with pinWithView so that thumnails show the pinned area and navigating to the view shows the entire pinned area regardless of panel size. --- src/client/views/DocumentButtonBar.tsx | 29 +------------------ src/client/views/collections/TabDocView.tsx | 33 ++++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +++++-- src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 20 +++++++++++-- src/client/views/nodes/trails/PresElementBox.tsx | 8 +++--- 6 files changed, 64 insertions(+), 37 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 103734b9b..41224b1c5 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -212,34 +212,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @undoBatch @action - pinWithView = (targetDoc: Doc) => { - const activeDoc = targetDoc && TabDocView.PinDoc(targetDoc); - if (activeDoc) { - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; - const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); - if (scrollable) { - const scroll = targetDoc._scrollTop; - activeDoc.presPinView = true; - activeDoc.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeDoc.presPinView = true; - activeDoc.presStartTime = targetDoc._currentTimecode; - activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (pannable) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeDoc.presPinClipWidth = width; - activeDoc.presPinView = true; - } - } - } + pinWithView = (targetDoc: Doc) => targetDoc && TabDocView.PinDoc(targetDoc, {pinDocView: true}); @computed get pinWithViewButton() { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 34fe572e3..f6a75e2db 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -227,6 +227,39 @@ export class TabDocView extends React.Component { pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2; pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2; pinDoc.presPinViewScale = viewProps.scale; + pinDoc.contentBounds = new List([viewProps.bounds.left, viewProps.bounds.top, viewProps.bounds.left+viewProps.bounds.width, viewProps.bounds.top+viewProps.bounds.height]); + } + if (pinProps?.pinDocView) { + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(pinDoc.type as any) || pinDoc._viewType === CollectionViewType.Stacking; + const pannable: boolean = ((pinDoc.type === DocumentType.COL && doc._viewType === CollectionViewType.Freeform) || doc.type === DocumentType.IMG); + if (scrollable) { + const scroll = doc._scrollTop; + pinDoc.presPinView = true; + pinDoc.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(doc.type as any)) { + pinDoc.presPinView = true; + pinDoc.presStartTime = doc._currentTimecode; + pinDoc.presEndTime = NumCast(doc._currentTimecode) + 0.1; + } else if (pannable) { + pinDoc.presPinView = true; + pinDoc.presPinViewX = pinDoc._panX; + pinDoc.presPinViewY = pinDoc._panY; + pinDoc.presPinViewScale = pinDoc._viewScale; + const pw = NumCast(pinProps.panelWidth); + const ph = NumCast(pinProps.panelHeight); + const ps = NumCast(pinDoc._viewScale); + if (pw && ph && ps) { + pinDoc.contentBounds = new List([ + NumCast(pinDoc.panX)-pw/2/ps, + NumCast(pinDoc.panY)-ph/2/ps, + NumCast(pinDoc.panX)+pw/2/ps, + NumCast(pinDoc.panY)+ph/2/ps]); + } + } else if (doc.type === DocumentType.COMPARISON) { + const width = doc._clipWidth; + pinDoc.presPinClipWidth = width; + pinDoc.presPinView = true; + } } Doc.AddDocToList(curPres, "data", pinDoc, presSelected); if (!pinProps?.audioRange && duration !== undefined) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5f927a2a9..b70e6ff98 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -56,6 +56,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { FieldView, FieldViewProps } from "../../nodes/FieldView"; +import { TabDocView } from "../TabDocView"; export const panZoomSchema = createSchema({ _panX: "number", @@ -138,7 +139,11 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } + @computed get contentBounds() { + const cb = Cast(this.rootDoc.contentBounds, listSpec("number")); + return cb ? {x:cb[0], y:cb[1], r:cb[2], b: cb[3]} : + this.props.contentBounds?.() ?? aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); + } @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); } @computed get cachedCenteringShiftX(): number { @@ -1612,7 +1617,8 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); - appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); + appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); + //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 59a402e01..26dec3bd5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -112,6 +112,7 @@ export interface DocumentViewSharedProps { renderDepth: number; Document: Doc; DataDoc?: Doc; + contentBounds?: () => (undefined|{x:number, y:number, r:number, b:number}); fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 591480023..6de04bd31 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -35,6 +35,9 @@ export interface PinProps { setPosition?: boolean; hidePresBox?: boolean; pinWithView?: PinViewProps; + pinDocView?: boolean; + panelWidth?: number; + panelHeight?: number } export interface PinViewProps { @@ -339,10 +342,21 @@ export class PresBox extends ViewBoxBaseComponent() { } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { bestTarget._currentTimecode = activeItem.presStartTime; } else { + const contentBounds= Cast(activeItem.contentBounds, listSpec("number")); bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; + if (contentBounds) { + bestTarget._panX = (contentBounds[0] + contentBounds[2])/2; + bestTarget._panY = (contentBounds[1] + contentBounds[3])/2; + const dv = DocumentManager.Instance.getDocumentView(bestTarget); + if (dv) { + bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), + dv.props.PanelWidth() / (contentBounds[2]- contentBounds[0])); + }; + } else { + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; + } } this._navTimer = setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); }); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index b9951eaae..5eff47a86 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; @@ -13,6 +13,7 @@ import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; +import { CollectionViewType } from "../../collections/CollectionView"; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from "../../EditableView"; import { Colors } from "../../global/globalEnums"; @@ -23,7 +24,6 @@ import { PresBox } from "./PresBox"; import "./PresElementBox.scss"; import { PresMovement } from "./PresEnums"; import React = require("react"); -import { CollectionViewType } from "../../collections/CollectionView"; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -75,8 +75,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
Date: Thu, 9 Jun 2022 11:07:34 -0400 Subject: renamed fitToBox to fitContentsToBox. fixed pinning multiple documents at same time to not bring up presBox multiple times. --- src/client/documents/Documents.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 21 ++---------- src/client/views/LightboxView.tsx | 2 -- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/collections/CollectionMenu.tsx | 4 +-- .../views/collections/CollectionTimeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 25 +++++++-------- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 37 +++++++--------------- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../CollectionMulticolumnView.tsx | 2 +- .../CollectionMultirowView.tsx | 2 +- .../collectionSchema/CollectionSchemaView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +-- src/client/views/nodes/MapBox/MapBox.tsx | 6 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 ++-- src/client/views/nodes/trails/PresBox.tsx | 14 ++++---- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/fields/documentSchemas.ts | 6 ++-- src/mobile/AudioUpload.tsx | 4 +-- 22 files changed, 60 insertions(+), 92 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index acc1e341a..144a3d9cf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -113,7 +113,8 @@ export class DocumentOptions { _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", true); _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units", true); _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)", true); - _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _fitContentsToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 41224b1c5..0bbe473d7 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -204,27 +204,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
{SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}
}>
this.props.views().map(view => view && this.pinWithView(view.props.Document)))}> + onClick={(e => TabDocView.PinDoc(this.props.views().filter(v => v).map(dv => dv!.rootDoc), {pinDocView: true}))} + >
; } - @undoBatch - @action - pinWithView = (targetDoc: Doc) => targetDoc && TabDocView.PinDoc(targetDoc, {pinDocView: true}); - - @computed - get pinWithViewButton() { - const presPinWithViewIcon = ; - const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) :
{"Pin with current view"}
}> -
this.pinWithView(targetDoc)}> - {presPinWithViewIcon} -
-
; - } - @computed get shareButton() { const targetDoc = this.view0?.props.Document; @@ -349,9 +335,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
{this.pinButton}
- {/*
- {this.pinWithViewButton} -
*/} {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
{this.shareButton}
} diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index f67f37bfb..fcc4aea13 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -207,7 +207,6 @@ export class LightboxView extends React.Component { future = () => LightboxView._future; tourMap = () => LightboxView._tourMap; - fitToBox = () => LightboxView._docTarget === LightboxView.LightboxDoc; render() { let downx = 0, downy = 0; return !LightboxView.LightboxDoc ? (null) : @@ -241,7 +240,6 @@ export class LightboxView extends React.Component { DataDoc={undefined} LayoutTemplate={LightboxView.LightboxDocTemplate} addDocument={undefined} - // fitContentsToDoc={this.fitToBox} // bcz: why do we want this? when we initially open a colletion, we shrinkwrap it which allows for user navigation. if we later encounter a collection, it's not clear to me that we want to make it either shrinkwrap or fitContents... isDocumentActive={returnFalse} isContentActive={returnTrue} addDocTab={this.addDocTab} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 641178cd9..84a131a57 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -287,7 +287,7 @@ export class MainView extends React.Component { styleProvider={DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events) isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive ScreenToLocalTransform={this.headerBarScreenXf} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index f24ff09db..74055f057 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -71,7 +71,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { return this.propertyToggleBtn("Lock\xA0View", "_lockedTransform", on => `${on ? "Unlock" : "Lock"} panning of view`, on => "lock"); } @computed get fitContentButton() { - return this.propertyToggleBtn("View All", "_fitToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye"); + return this.propertyToggleBtn("View All", "_fitContentsToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye"); } @computed get fitWidthButton() { return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : "Do"} fit content to width of container`, on => "arrows-alt-h"); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index bcfd2dd56..16eb95cf4 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -300,7 +300,7 @@ export class PropertiesView extends React.Component { Document={layoutDoc} DataDoc={this.dataDoc} renderDepth={1} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} rootSelected={returnFalse} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 01e77f342..39f6466d6 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -252,8 +252,8 @@ export class CollectionViewBaseChrome extends React.Component this.target._fitToBox = !this.target._fitToBox), + script: "self.target._fitContentsToBox = !self.target._fitContentsToBox;", + immediate: undoBatch((source: Doc[]) => this.target._fitContentsToBox = !this.target._fitContentsToBox), initialize: emptyFunction }; _fitContentCommand = { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 4f6f45d2f..7573b938a 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -135,7 +135,7 @@ export class CollectionTimeView extends CollectionSubView() { onClick={this.contentsDown}> { * Adds a document to the presentation view **/ @action - public static PinDoc(doc: Doc, pinProps?: PinProps) { - //add this new doc to props.Document + public static PinDoc(docs: Doc|Doc[], pinProps?: PinProps) { + const docList = ((docs instanceof Doc) ? [docs]: docs); + const batch = UndoManager.StartBatch("pinning doc"); // all docs will be added to the ActivePresentation as stored on CurrentUserUtils const curPres = CurrentUserUtils.ActivePresentation; - if (curPres) { + curPres && docList.forEach(doc => { // Edge Case 1: Cannot pin document to itself if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; } - const batch = UndoManager.StartBatch("pinning doc"); const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; pinDoc.title = doc.title + " - Slide"; @@ -279,17 +279,16 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { - const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); - if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); - CollectionDockingView.AddSplit(curPres, "right"); - setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things - } PresBox.Instance?._selectedArray.clear(); pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs - return pinDoc; + }); + if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { + const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); + if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); + CollectionDockingView.AddSplit(curPres, "right"); + setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things } + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } componentDidMount() { @@ -572,7 +571,7 @@ export class TabMinimapView extends React.Component { docFilters={CollectionDockingView.Instance.childDocFilters} docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 09f05f69a..8824750a3 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -796,7 +796,7 @@ export class TreeView extends React.Component { isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} hideTitle={asText} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} onClick={this.onChildClick} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b70e6ff98..ec3cf1fe8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,7 +9,7 @@ import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fiel import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; -import { createSchema, listSpec } from "../../../../fields/Schema"; +import { listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { ImageField } from "../../../../fields/URLField"; @@ -41,6 +41,7 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn import { LightboxView } from "../../LightboxView"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; +import { FieldViewProps } from "../../nodes/FieldView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { VideoBox } from "../../nodes/VideoBox"; @@ -50,27 +51,13 @@ import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; import { TreeViewType } from "../CollectionTreeView"; import { CollectionViewType } from "../CollectionView"; +import { TabDocView } from "../TabDocView"; import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { FieldView, FieldViewProps } from "../../nodes/FieldView"; -import { TabDocView } from "../TabDocView"; -export const panZoomSchema = createSchema({ - _panX: "number", - _panY: "number", - _currentTimecode: "number", - _timecodeToShow: "number", - _currentFrame: "number", - _useClusters: "boolean", - _viewTransition: "string", - _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _fitToBox: "boolean", - scrollHeight: "number" // this will be set when the collection is an annotation overlay for a PDF/Webpage -}); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -138,20 +125,20 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } - @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } - @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); } + @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document); } + @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document); } @computed get cachedCenteringShiftX(): number { - const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling; + const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { - const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling; + const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections } @computed get cachedGetLocalTransform(): Transform { @@ -176,8 +163,8 @@ export class CollectionFreeFormView extends CollectionSubView !this._firstRender && (this.fitToContent || force) ? this.fitToContentVals : undefined; - reverseNativeScaling = () => this.fitToContent ? true : false; + freeformData = (force?: boolean) => !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined; + reverseNativeScaling = () => this.fitContentsToBox ? true : false; panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY); zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); @@ -1272,7 +1259,7 @@ export class CollectionFreeFormView extends CollectionSubView; } addDocTab = action((doc: Doc, where: string) => { @@ -1616,7 +1603,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3fac3350a..6df2bb102 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -516,7 +516,7 @@ export class MarqueeView extends React.Component Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt; // returns the duration of the focus setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. + reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected. isAnyChildContentActive?: () => boolean; // is any child content of the document active @@ -113,7 +113,7 @@ export interface DocumentViewSharedProps { Document: Doc; DataDoc?: Doc; contentBounds?: () => (undefined|{x:number, y:number, r:number, b:number}); - fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document + fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitContentsToBox property on a Document ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; suppressSetHeight?: boolean; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 9f1c019fe..5f4c17ee6 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -259,7 +259,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this._loadPending && this._map.getBounds()) { this._loadPending = false; - this.layoutDoc.fitToBox && this.fitBounds(this._map); + this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); } this.dataDoc.mapLat = this._map.getCenter()?.lat(); this.dataDoc.mapLng = this._map.getCenter()?.lng(); @@ -284,7 +284,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this._loadPending && this._map.getBounds()) { this._loadPending = false; - this.layoutDoc.fitToBox && this.fitBounds(this._map); + this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); } this.dataDoc.mapZoom = this._map.getZoom(); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 7d0302b26..4e431e7bd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -495,7 +495,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // embed document when dragg marked as embed } else if (de.embedKey) { const target = dragData.droppedDocuments[0]; - target._fitToBox = true; + target._fitContentsToBox = true; const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), @@ -1557,7 +1557,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - fitToBox = () => this.props.Document._fitToBox; + fitContentsToBox = () => this.props.Document._fitContentsToBox; sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); @@ -1632,7 +1632,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ScreenToLocalTransform={this.sidebarScreenToLocal} renderDepth={this.props.renderDepth + 1} setHeight={this.setSidebarHeight} - fitContentsToDoc={this.fitToBox} + fitContentsToBox={this.fitContentsToBox} noSidebar={true} fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />; }; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 6de04bd31..cebf9487a 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -35,8 +35,8 @@ export interface PinProps { setPosition?: boolean; hidePresBox?: boolean; pinWithView?: PinViewProps; - pinDocView?: boolean; - panelWidth?: number; + pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document + panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) panelHeight?: number } @@ -997,7 +997,7 @@ export class PresBox extends ViewBoxBaseComponent() { /** * Method called for viewing paths which adds a single line with * points at the center of each document added. - * Design choice: When this is called it sets _fitToBox as true so the + * Design choice: When this is called it sets _fitContentsToBox as true so the * user can have an overview of all of the documents in the collection. * (Design needed for when documents in presentation trail are in another * collection) @@ -1800,16 +1800,16 @@ export class PresBox extends ViewBoxBaseComponent() { doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y }); break; case 'title': - doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'header': - doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'content': - doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'twoColumns': - doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; default: break; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 5eff47a86..ba0193e4b 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -85,7 +85,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { isContentActive={this.props.isContentActive} addDocTab={returnFalse} pinToPres={returnFalse} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} PanelWidth={this.embedWidth} PanelHeight={this.embedHeight} ScreenToLocalTransform={Transform.Identity} diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index e532becb5..be39e0709 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -36,8 +36,8 @@ export const documentSchema = createSchema({ _nativeHeight: "number", // " _width: "number", // width of document in its container's coordinate system _height: "number", // " - _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set + _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set + _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set _xMargin: "number", // margin added on left/right of most documents to add separation from their container _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container _overflow: "string", // sets overflow behvavior for CollectionFreeForm views @@ -59,7 +59,7 @@ export const documentSchema = createSchema({ borderRounding: "string", // border radius rounding of document boxShadow: "string", // the amount of shadow around the perimeter of a document color: "string", // foreground color of document - fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view + fitContentsToBox: "boolean",// whether freeform view contents should be zoomed/panned to fill the area of the document view box fontSize: "string", hidden: "boolean", // whether a document should not be displayed isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through) diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx index 418464f0e..64baa351c 100644 --- a/src/mobile/AudioUpload.tsx +++ b/src/mobile/AudioUpload.tsx @@ -19,7 +19,7 @@ import React = require('react'); @observer export class AudioUpload extends React.Component { - @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc; + @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc; /** * Handles the onclick functionality for the 'Restart' button @@ -36,7 +36,7 @@ export class AudioUpload extends React.Component { title: "mobile audio", _width: 500, _height: 100 - }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc; + }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc; } /** -- cgit v1.2.3-70-g09d2 From 53bc43a9817f6a80b9dc4e0adad11b555c4236b6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 11:20:14 -0400 Subject: pinned docs now have click func to navigate to target. restored embedded views within presBox --- src/client/views/collections/TabDocView.tsx | 2 ++ src/client/views/nodes/trails/PresElementBox.tsx | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 5cc92a305..5883f0496 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -36,6 +36,7 @@ import { CollectionView, CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; import React = require("react"); import { listSpec } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { @@ -260,6 +261,7 @@ export class TabDocView extends React.Component { pinDoc.presPinClipWidth = width; pinDoc.presPinView = true; } + pinDoc.onClick = ScriptField.MakeFunction("DocFocusOrOpen(self.presentationTargetDoc)"); } Doc.AddDocToList(curPres, "data", pinDoc, presSelected); if (!pinProps?.audioRange && duration !== undefined) { diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index ef70f8e36..41a583580 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -582,9 +582,9 @@ export class PresElementBox extends ViewBoxBaseComponent() {
} - {/*
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> +
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> e.stopPropagation()} /> -
*/} +
{"Remove from presentation"}
}>
-- cgit v1.2.3-70-g09d2 From 012a4681c824baaef60370de5904e9cc35b03f13 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 11:32:01 -0400 Subject: fixed pin doc navigation when not in presBox (eg. headerBar) --- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 61 +-- src/client/views/nodes/trails/PresElementBox.tsx | 490 +++++++++-------------- 3 files changed, 219 insertions(+), 334 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 5883f0496..142013d1a 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -261,8 +261,8 @@ export class TabDocView extends React.Component { pinDoc.presPinClipWidth = width; pinDoc.presPinView = true; } - pinDoc.onClick = ScriptField.MakeFunction("DocFocusOrOpen(self.presentationTargetDoc)"); } + pinDoc.onClick = ScriptField.MakeFunction("navigateToDoc(self.presentationTargetDoc, self)"); Doc.AddDocToList(curPres, "data", pinDoc, presSelected); if (!pinProps?.audioRange && duration !== undefined) { pinDoc.mediaStart = "manual"; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index cebf9487a..db2f58be7 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -29,6 +29,7 @@ import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import "./PresBox.scss"; import { PresEffect, PresMovement, PresStatus } from "./PresEnums"; +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; export interface PinProps { audioRange?: boolean; @@ -332,34 +333,36 @@ export class PresBox extends ViewBoxBaseComponent() { const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; if (bestTarget) console.log(bestTarget.title, bestTarget.type); else console.log("no best target"); - bestTarget && runInAction(() => { - console.log(bestTarget.title, bestTarget.type); - if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget._scrollTop = activeItem.presPinViewScroll; - } else if (bestTarget.type === DocumentType.COMPARISON) { - bestTarget._clipWidth = activeItem.presPinClipWidth; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { - bestTarget._currentTimecode = activeItem.presStartTime; + if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem); + } + + @action + static navigateToDoc(bestTarget:Doc, activeItem:Doc) { + if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + bestTarget._scrollTop = activeItem.presPinViewScroll; + } else if (bestTarget.type === DocumentType.COMPARISON) { + bestTarget._clipWidth = activeItem.presPinClipWidth; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { + bestTarget._currentTimecode = activeItem.presStartTime; + } else { + const contentBounds= Cast(activeItem.contentBounds, listSpec("number")); + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + if (contentBounds) { + bestTarget._panX = (contentBounds[0] + contentBounds[2])/2; + bestTarget._panY = (contentBounds[1] + contentBounds[3])/2; + const dv = DocumentManager.Instance.getDocumentView(bestTarget); + if (dv) { + bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), + dv.props.PanelWidth() / (contentBounds[2]- contentBounds[0])); + }; } else { - const contentBounds= Cast(activeItem.contentBounds, listSpec("number")); - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - if (contentBounds) { - bestTarget._panX = (contentBounds[0] + contentBounds[2])/2; - bestTarget._panY = (contentBounds[1] + contentBounds[3])/2; - const dv = DocumentManager.Instance.getDocumentView(bestTarget); - if (dv) { - bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), - dv.props.PanelWidth() / (contentBounds[2]- contentBounds[0])); - }; - } else { - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; - } + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; } - this._navTimer = setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); - }); + } + return setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); } /** @@ -2570,4 +2573,8 @@ export class PresBox extends ViewBoxBaseComponent() {
; } -} \ No newline at end of file +} + +ScriptingGlobals.add(function navigateToDoc(bestTarget:Doc, activeItem:Doc) { + PresBox.navigateToDoc(bestTarget, activeItem); +}); \ No newline at end of file diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 41a583580..ba0193e4b 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -4,10 +4,9 @@ import { action, computed, IReactionDisposer, observable, reaction } from "mobx" import { observer } from "mobx-react"; import { Doc, DocListCast, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; -import { List } from "../../../../fields/List"; -import { Cast, NumCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; -import { Docs, DocUtils } from "../../../documents/Documents"; +import { DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -31,8 +30,8 @@ import React = require("react"); */ @observer export class PresElementBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } - _heightDisposer: IReactionDisposer | undefined; + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } + _heightDisposer: IReactionDisposer | undefined; @observable _dragging = false; @computed get indexInPres() { return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, "data")]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) @@ -182,11 +181,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { e.preventDefault(); } - - /** - * Function to drag and drop the pres element to a diferent location - */ - startDrag = (e: PointerEvent) => { + /** + * Function to drag and drop the pres element to a diferent location + */ + startDrag = (e: PointerEvent) => { const miniView: boolean = this.toolbarWidth <= 100; const activeItem = this.rootDoc; const dragArray = PresBox.Instance._dragArray; @@ -197,32 +195,27 @@ export class PresElementBox extends ViewBoxBaseComponent() { dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument; const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { - const doc = this._itemRef.current || dragArray[0]; - doc.className = miniView ? "presItem-miniSlide" : "presItem-slide"; - dragItem.push(doc); + const doc = this._itemRef.current || dragArray[0]; + doc.className = miniView ? "presItem-miniSlide" : "presItem-slide"; + dragItem.push(doc); } else if (dragArray.length >= 1) { - const doc = document.createElement('div'); - doc.className = "presItem-multiDrag"; - doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides"; - doc.style.position = 'absolute'; - doc.style.top = (e.clientY) + 'px'; - doc.style.left = (e.clientX - 50) + 'px'; - dragItem.push(doc); + const doc = document.createElement('div'); + doc.className = "presItem-multiDrag"; + doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides"; + doc.style.position = 'absolute'; + doc.style.top = (e.clientY) + 'px'; + doc.style.left = (e.clientX - 50) + 'px'; + dragItem.push(doc); } // const dropEvent = () => runInAction(() => this._dragging = false); if (activeItem) { - DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined); - // runInAction(() => this._dragging = true); - return true; + DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined); + // runInAction(() => this._dragging = true); + return true; } return false; - } - - /** - * Returns a local transformed coordinate array for given coordinates. - */ - ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; + } onPointerOver = (e: any) => { document.removeEventListener("pointermove", this.onPointerMove); @@ -321,283 +314,168 @@ export class PresElementBox extends ViewBoxBaseComponent() { return width; } - onPointerOver = (e: any) => { - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - } - - onPointerMove = (e: PointerEvent) => { - const slide = this._itemRef.current!; - let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false; - for (const doc of DragManager.docsBeingDragged) { - if (!doc.presentationTargetDoc) dragIsPresItem = false; - } - if (slide && dragIsPresItem) { - const rect = slide.getBoundingClientRect(); - const y = e.clientY - rect.top; //y position within the element. - const height = slide.clientHeight; - const halfLine = height / 2; - if (y <= halfLine) { - slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; - slide.style.borderBottom = "0px"; - } else if (y > halfLine) { - slide.style.borderTop = "0px"; - slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; - } - } - document.removeEventListener("pointermove", this.onPointerMove); - } - - onPointerLeave = (e: any) => { - this._itemRef.current!.style.borderTop = "0px"; - this._itemRef.current!.style.borderBottom = "0px"; - document.removeEventListener("pointermove", this.onPointerMove); - } - - @action - toggleProperties = () => { - if (CurrentUserUtils.propertiesWidth < 5) { - action(() => (CurrentUserUtils.propertiesWidth = 250)); - } - } - - @undoBatch - removeItem = action((e: React.MouseEvent) => { - this.props.removeDocument?.(this.rootDoc); - if (PresBox.Instance._selectedArray.has(this.rootDoc)) { - PresBox.Instance._selectedArray.delete(this.rootDoc); - } - this.hideRecording(); - e.stopPropagation(); - }); - - // set the value/title of the individual pres element - @undoBatch - @action - onSetValue = (value: string) => { - this.rootDoc.title = !value.trim().length ? "-untitled-" : value; - return true; - } - - /** - * Method called for updating the view of the currently selected document - * - * @param targetDoc - * @param activeItem - */ - @undoBatch - @action - updateView = (targetDoc: Doc, activeItem: Doc) => { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { - const scroll = targetDoc._scrollTop; - activeItem.presPinViewScroll = scroll; - } else if (targetDoc.type === DocumentType.VID) { - activeItem.presPinTimecode = targetDoc._currentTimecode; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const clipWidth = targetDoc._clipWidth; - activeItem.presPinClipWidth = clipWidth; - } else { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } - } - - @computed get recordingIsInOverlay() { - let isInOverlay = false - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { - if (doc.slides === this.rootDoc) { - isInOverlay = true - return - } - }) - return isInOverlay - } - - @undoBatch - @action - hideRecording = () => { - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { - if (doc.slides === this.rootDoc) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); - } - }) - } - - @undoBatch - @action - showRecording = (activeItem: Doc) => { - // Remove every recording that already exists in overlay view - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { - if (doc.slides !== null) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); - } - }) - if (activeItem.recording) { - // if we already have an existing recording - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null)); - - } - } - - @undoBatch - @action - startRecording = (activeItem: Doc) => { - // Remove every recording that already exists in overlay view - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { - if (doc.slides !== null) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); - } - }) - - if (activeItem.recording) { - // if we already have an existing recording - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null)); - - } else { - // if we dont have any recording - const recording = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", cloneFieldFilter: new List(["system"]) }); - - // attach the recording to the slide, and attach the slide to the recording - recording.slides = activeItem - activeItem.recording = recording - - // TODO: Figure out exactly where we want the video to appear - const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - recording.x = pt[0]; - recording.y = pt[1]; - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording); - } - } - - @computed - get toolbarWidth(): number { - const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox); - let width: number = NumCast(this.presBox._width); - if (presBoxDocView) width = presBoxDocView.props.PanelWidth(); - if (width === 0) width = 300; - return width; - } - - @computed get mainItem() { - const isSelected: boolean = PresBox.Instance?._selectedArray.has(this.rootDoc); - const toolbarWidth: number = this.toolbarWidth; - const showMore: boolean = this.toolbarWidth >= 300; - const miniView: boolean = this.toolbarWidth <= 110; - const presBox: Doc = this.presBox; //presBox - const presBoxColor: string = StrCast(presBox._backgroundColor); - const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false; - const targetDoc: Doc = this.targetDoc; - const activeItem: Doc = this.rootDoc; - return ( -
= 300; + const miniView: boolean = this.toolbarWidth <= 110; + const presBox: Doc = this.presBox; //presBox + const presBoxColor: string = StrCast(presBox._backgroundColor); + const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false; + const targetDoc: Doc = this.targetDoc; + const activeItem: Doc = this.rootDoc; + const isGroup: boolean = BoolCast(targetDoc._isGroup); + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + }} + onDoubleClick={action(e => { + if (isGroup) { + this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup; + } else { + this.toggleProperties(); + PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); + } + })} + onPointerOver={this.onPointerOver} + onPointerLeave={this.onPointerLeave} + onPointerDown={this.headerDown} + onPointerUp={this.headerUp} + > + {miniView ? + // when width is LESS than 110 px +
+ {`${this.indexInPres + 1}.`} +
+ : + // when width is MORE than 110 px +
+ {`${this.indexInPres + 1}.`} +
} + {isGroup ? +
{ - e.stopPropagation(); - e.preventDefault(); - PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); - this.showRecording(activeItem); - }} - onDoubleClick={action(e => { - this.toggleProperties(); - PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); - })} - onPointerOver={this.onPointerOver} - onPointerLeave={this.onPointerLeave} - onPointerDown={this.headerDown} - onPointerUp={this.headerUp} - > - {miniView ? - // when width is LESS than 110 px -
- {`${this.indexInPres + 1}.`} -
- : - // when width is MORE than 110 px -
- {`${this.indexInPres + 1}.`} -
} - {miniView ? (null) :
-
- StrCast(activeItem.title)} - SetValue={this.onSetValue} - /> -
-
{"Movement speed"}
}>
{this.transition}
-
{"Duration"}
}>
{this.duration}
-
-
{"Update view"}
}> -
this.updateView(targetDoc, activeItem)} - style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V
-
- - {this.recordingIsInOverlay ? -
{"Hide Recording"}
}> -
- e.stopPropagation()} /> -
-
: -
{"Start recording"}
}> -
this.startRecording(activeItem)} - style={{ fontWeight: 700 }}> - e.stopPropagation()} /> -
-
- } - - - {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}> -
activeItem.groupWithUp = !activeItem.groupWithUp} - style={{ - zIndex: 1000 - this.indexInPres, - fontWeight: 700, - backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined, - height: activeItem.groupWithUp ? 53 : 18, - transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined - }}> -
- e.stopPropagation()} /> -
-
-
} -
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> - e.stopPropagation()} /> -
-
{"Remove from presentation"}
}>
- e.stopPropagation()} /> -
-
-
{activeItem.presPinView ? (<>View of {targetDoc.title}) : targetDoc.title}
- {this.renderEmbeddedInline} -
} -
); - } + backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), + boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined + }}> +
+ StrCast(activeItem.title)} + SetValue={this.onSetValue} + /> +
+
+
{"Update view"}
}> +
this.updateView(targetDoc, activeItem)} + style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V
+
+ {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}> +
activeItem.groupWithUp = !activeItem.groupWithUp} + style={{ + zIndex: 1000 - this.indexInPres, + fontWeight: 700, + backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined, + height: activeItem.groupWithUp ? 53 : 18, + transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined + }}> +
+ e.stopPropagation()} /> +
+
+
} +
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> + e.stopPropagation()} /> +
+
{"Remove from presentation"}
}>
+ e.stopPropagation()} /> +
+
+
+
+ {this.rootDoc.presExpandGroup ? this.renderGroupSlides : (null)} +
+
{activeItem.presPinView ? (<>View of {targetDoc.title}) : targetDoc.title}
+
{ + if (isGroup) { + this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup; + } + }} + > + +
+
+ : (null)} + {miniView || isGroup ? (null) :
+
+ StrCast(activeItem.title)} + SetValue={this.onSetValue} + /> +
+
{"Movement speed"}
}>
{this.transition}
+
{"Duration"}
}>
{this.duration}
+
+
{"Update view"}
}> +
this.updateView(targetDoc, activeItem)} + style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V
+
+ {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}> +
activeItem.groupWithUp = !activeItem.groupWithUp} + style={{ + zIndex: 1000 - this.indexInPres, + fontWeight: 700, + backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined, + height: activeItem.groupWithUp ? 53 : 18, + transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined + }}> +
+ e.stopPropagation()} /> +
+
+
} +
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> + e.stopPropagation()} /> +
+
{"Remove from presentation"}
}>
+ e.stopPropagation()} /> +
+
+
{activeItem.presPinView ? (<>View of {targetDoc.title}) : targetDoc.title}
+ {this.renderEmbeddedInline} +
} +
); + } - render() { - return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : this.mainItem; - } + render() { + return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : this.mainItem; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 331cb0bec8089c4435126542ed3181fe227eff51 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 11:53:33 -0400 Subject: fixes to navigating to slide document targets in header bar --- src/client/views/MainView.tsx | 1 + .../collectionMulticolumn/CollectionMulticolumnView.tsx | 1 + .../collectionMulticolumn/CollectionMultirowView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 11 ++++++++--- 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index adcfd82dc..9516ea017 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -292,6 +292,7 @@ export class MainView extends React.Component { isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive ScreenToLocalTransform={this.headerBarScreenXf} childHideResizeHandles={returnTrue} + dontRegisterView={true} hideResizeHandles={true} PanelWidth={this.headerBarDocWidth} PanelHeight={this.headerBarDocHeight} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 7147ae485..b7ba94940 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -272,6 +272,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { searchFilterDocs={this.searchFilterDocs} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} + dontRegisterView={this.props.dontRegisterView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 3bda56c33..338639a83 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -271,6 +271,7 @@ export class CollectionMultirowView extends CollectionSubView() { searchFilterDocs={this.searchFilterDocs} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} + dontRegisterView={this.props.dontRegisterView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index db2f58be7..da65e7919 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -333,11 +333,16 @@ export class PresBox extends ViewBoxBaseComponent() { const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; if (bestTarget) console.log(bestTarget.title, bestTarget.type); else console.log("no best target"); - if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem); + if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); } @action - static navigateToDoc(bestTarget:Doc, activeItem:Doc) { + static navigateToDoc(bestTarget:Doc, activeItem:Doc, jumpToDoc:boolean) { + if (jumpToDoc) { + const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null); + const openInTab = (doc: Doc, finished?: () => void) => CollectionDockingView.AddSplit(doc, "right"); + DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined,undefined, undefined, true, NumCast(activeItem.presZoom)); // documents open in new tab instead of on right + } if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; bestTarget._scrollTop = activeItem.presPinViewScroll; @@ -2576,5 +2581,5 @@ export class PresBox extends ViewBoxBaseComponent() { } ScriptingGlobals.add(function navigateToDoc(bestTarget:Doc, activeItem:Doc) { - PresBox.navigateToDoc(bestTarget, activeItem); + PresBox.navigateToDoc(bestTarget, activeItem, true); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 140e2f643f97057ba9c89c502cff7843bd738cd6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 16:59:09 -0400 Subject: fixed issues with taking dashboard snapshots to update dashboard view --- src/client/util/CurrentUserUtils.ts | 9 ++- src/client/views/DashboardView.tsx | 12 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 ++-- src/client/views/topbar/TopBar.tsx | 64 +--------------------- 4 files changed, 19 insertions(+), 76 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b1329e349..2d2dda2b8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1183,13 +1183,13 @@ export class CurrentUserUtils { input.click(); } - public static async snapshotDashboard(userDoc: Doc) { + public static CaptureDashboardThumbnail() { const docView = CollectionDockingView.Instance.props.DocumentView?.(); const content = docView?.ContentDiv; if (docView && content) { const _width = Number(getComputedStyle(content).width.replace("px","")); const _height = Number(getComputedStyle(content).height.replace("px","")); - CollectionFreeFormView.UpdateIcon( + return CollectionFreeFormView.UpdateIcon( docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), content, _width, _height, @@ -1199,10 +1199,13 @@ export class CurrentUserUtils { const proto = Cast(img.proto, Doc, null)!; proto["data-nativeWidth"] = _width; proto["data-nativeHeight"] = _height; - docView.dataDoc.thumb = img; + Doc.GetProto(CurrentUserUtils.ActiveDashboard).thumb = img; }); } + } + + 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); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 5fd9b550d..3cfece970 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,4 +1,5 @@ import { observable } from "mobx"; +import { extname } from 'path'; import { observer } from "mobx-react"; import * as React from 'react'; import { Doc, DocListCast } from "../../fields/Doc"; @@ -33,13 +34,16 @@ export class DashboardView extends React.Component {
All Dashboards
- {myDashboards.map((dashboard) => -
{ this.clickDashboard(e, dashboard) }}> - + {myDashboards.map((dashboard) => { + const url = ImageCast((dashboard.thumb as Doc)?.data)?.url; + const ext = url ? extname(url.href):""; + const href = url?.href.replace(ext, "_m"+ ext); // need to choose which resolution image to show. options are _s, _m, _l, _o (small/medium/large/original) + return
this.clickDashboard(e, dashboard)}> +
{StrCast(dashboard.title)}
- )} + })} {myDashboards.map((dashboard) => { console.log(dashboard.thumb) })} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f4d2d55d5..ef3a896e1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1532,7 +1532,7 @@ export class CollectionFreeFormView extends CollectionSubView { - VideoBox.convertDataUri(data_url, filename).then( - returnedfilename => setTimeout(() => { - cb(returnedfilename as string, nativeWidth, nativeHeight); - }, 500)); + (async (data_url: any) => { + const returnedFilename = await VideoBox.convertDataUri(data_url, filename); + cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index fcc72b73d..21ef807ff 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -21,7 +21,7 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { - Doc.UserDoc().activeDashboard = undefined + CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activeDashboard = undefined); } render() { const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); @@ -61,68 +61,6 @@ export class TopBar extends React.Component {
- - - {/*
-
- {`${Doc.CurrentUserEmail}`} -
-
window.location.assign(Utils.prepend("/logout"))}> - {"Log out"} -
-
*/} - {/*
-
-
- {activeDashboard ? StrCast(activeDashboard.title) : "Dash"} -
*/} - - {/* */} - {/*
*/ } - {/*
- Create a new dashboard
} placement="bottom">
{ - const batch = UndoManager.StartBatch("new dash"); - await CurrentUserUtils.createNewDashboard(Doc.UserDoc()); - batch.end(); - }}> - New -
- - Work on a copy of the dashboard layout
} placement="bottom"> -
{ - const batch = UndoManager.StartBatch("snapshot"); - await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); - batch.end(); - }}> - Snapshot -
- - Browsing mode for directly navigating to documents} placement="bottom"> -
MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> - Explore -
-
- */} - {/* */ } - {/*
*/ } - {/*
window.open( - "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> - -
-
SettingsManager.Instance.open()}> - -
*/} - {/*
- -
-
SettingsManager.Instance.open()}> - -
-
*/} ); } -- cgit v1.2.3-70-g09d2 From d1f268c1788f54c62c0779aba224b5b7d30edb01 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 17:13:49 -0400 Subject: changed tab numbering to be relative to dashboard. --- src/client/util/CurrentUserUtils.ts | 5 ++--- src/client/views/collections/CollectionDockingView.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2d2dda2b8..3107650d9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1215,8 +1215,6 @@ export class CurrentUserUtils { 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, @@ -1224,7 +1222,7 @@ export class CurrentUserUtils { _height: 1000, _fitWidth: true, _backgroundGridShow: true, - title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, + title: `Untitled Tab 1`, }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); @@ -1233,6 +1231,7 @@ export class CurrentUserUtils { // switching the tabs from the datadoc to the regular doc const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); dashboardDoc.data = new List(dashboardTabs); + dashboardDoc["pane-count"] = 1; userDoc.activePresentation = presentation; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a790a0475..87149a4b8 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -428,10 +428,10 @@ export class CollectionDockingView extends CollectionSubView() { stackCreated = (stack: any) => { stack.header?.element.on('mousedown', (e: any) => { if (e.target === stack.header?.element[0] && e.button === 2) { - const emptyPane = CurrentUserUtils.EmptyPane; - emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; + const dashboard= CurrentUserUtils.ActiveDashboard; + dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, + _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`, }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); CollectionDockingView.AddSplit(docToAdd, "", stack); @@ -455,10 +455,10 @@ export class CollectionDockingView extends CollectionSubView() { .off('click') //unbind the current click handler .click(action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size - const emptyPane = CurrentUserUtils.EmptyPane; - emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; + const dashboard = CurrentUserUtils.ActiveDashboard; + dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}` + _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}` }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); CollectionDockingView.AddSplit(docToAdd, "", stack); -- cgit v1.2.3-70-g09d2 From ab5e48a2340f06628fc22d1267d081de9dbc572f Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 23:24:34 -0400 Subject: fixed up creating dashboard icons to work with pdfs in freeformviews with a grid. no idea why having a grid breaks thrings. --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/DashboardView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 53 ++++++++++++++-------- src/client/views/nodes/PDFBox.tsx | 44 +++++++----------- 4 files changed, 53 insertions(+), 50 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3b1009532..9a01f3e5d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1194,7 +1194,7 @@ export class CurrentUserUtils { docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), content, _width, _height, - _width, _height, 0, + _width, _height, 0, 1, true, docView.layoutDoc[Id] + "-icon", (iconFile, _nativeWidth, _nativeHeight) => { const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); const proto = Cast(img.proto, Doc, null)!; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index b08151c0f..e003968d6 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -36,9 +36,7 @@ export class DashboardView extends React.Component {
{myDashboards.map((dashboard) => { - const url = ImageCast((dashboard.thumb as Doc)?.data)?.url; - const ext = url ? extname(url.href):""; - const href = url?.href.replace(ext, "_m"+ ext); // need to choose which resolution image to show. options are _s, _m, _l, _o (small/medium/large/original) + const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; return
this.clickDashboard(e, dashboard)}>
{StrCast(dashboard.title)}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ef3a896e1..99f74b8a2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1500,15 +1500,22 @@ export class CollectionFreeFormView extends CollectionSubView { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; }); - public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number, - panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) { + public static UpdateIcon( + filename:string, docViewContent:HTMLElement, + width: number, height: number, + panelWidth:number, panelHeight: number, + scrollTop:number, + realNativeHeight: number, + noSuffix: boolean, + replaceRootFilename: string| undefined, + cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) + { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; newDiv.style.width = width.toString(); newDiv.style.height = height.toString(); @@ -1538,10 +1553,10 @@ export class CollectionFreeFormView extends CollectionSubView { - const returnedFilename = await VideoBox.convertDataUri(data_url, filename); + const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { @@ -1771,7 +1786,7 @@ export class CollectionFreeFormView extends CollectionSubView
{this.layoutDoc._backgroundGridShow ? - : (null)} + />
: (null)} () { @@ -147,34 +148,23 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - return; // currently we render pdf icons as text labels + // currently we render pdf icons as text labels const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; - const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); - this.replaceCanvases(docViewContent, newDiv); - const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); - const nativeWidth = this.layoutDoc[WidthSym](); - const nativeHeight = this.layoutDoc[HeightSym](); - - CreateImage( - "", - document.styleSheets, - htmlString, - nativeWidth, - nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), - NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"]) - ).then - ((data_url: any) => { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then( - returnedfilename => setTimeout(action(() => { - this.dataDoc.icon = new ImageField(returnedfilename); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }), 500)); - }) - .catch(function (error: any) { - console.error('oops, something went wrong!', error); + const filename = this.layoutDoc[Id] + "-icon" + (new Date()).getTime(); + this._pdfViewer?._mainCont.current && CollectionFreeFormView.UpdateIcon( + filename, docViewContent, + this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), + this.props.PanelWidth(), this.props.PanelHeight(), + NumCast(this.layoutDoc._scrollTop), + NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"], 1), + true, + this.layoutDoc[Id] + "-icon", + (iconFile:string, nativeWidth:number, nativeHeight:number) => { + setTimeout(() => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }, 500); }); } -- cgit v1.2.3-70-g09d2 From 5089c14ca27a52326d3f38e7f3ef572548f72d2a Mon Sep 17 00:00:00 2001 From: Jenny Yu Date: Thu, 9 Jun 2022 20:59:22 -0700 Subject: feat: opening up sharing manager on dashboard --- src/client/views/collections/CollectionStackingView.scss | 5 ++--- src/client/views/topbar/TopBar.tsx | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 2f002736d..73572299a 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -30,11 +30,10 @@ width: 90%; margin: 5px; font-size: 11px; - background-color: $light-blue; color: $medium-blue; padding: 10px; - border-radius: 10px; - border: solid 2px $medium-blue; + border-radius: 5px; + border: solid .5px $medium-blue; } } diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 258891099..79d48e13c 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -9,6 +9,7 @@ import { Cast, StrCast } from '../../../fields/Types'; import { Utils } from '../../../Utils'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { SettingsManager } from "../../util/SettingsManager"; +import { SharingManager } from "../../util/SharingManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { Borders, Colors } from "../global/globalEnums"; import { MainView } from "../MainView"; @@ -57,7 +58,7 @@ export class TopBar extends React.Component {
{/* TODO: if this is my dashboard, display share if this is a shared dashboard, display "view original or view annotated" */} -
Share
+
{SharingManager.Instance.open(undefined, activeDashboard)}}>Share
-- cgit v1.2.3-70-g09d2 From 1ab1ff51b5b5769079db1922d5013ec4680a1aa1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 09:10:54 -0400 Subject: fixed closing tabs to move to headerBar --- src/client/views/collections/CollectionDockingView.tsx | 2 ++ src/client/views/collections/TabDocView.tsx | 12 ++---------- 2 files changed, 4 insertions(+), 10 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a790a0475..e657bd502 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -412,6 +412,8 @@ export class CollectionDockingView extends CollectionSubView() { } tabDestroyed = (tab: any) => { + Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); + Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); const dview = CollectionDockingView.Instance.props.Document; const fieldKey = CollectionDockingView.Instance.props.fieldKey; Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 142013d1a..6c4437aed 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -115,20 +115,12 @@ export class TabDocView extends React.Component { } })); }; - - closeWrap.className = "lm_iconWrap"; - closeWrap.id = "lm_closeWrap"; - closeWrap.onclick = (e: MouseEvent) => { - tab.header.parent.contentItem.remove(); - Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); - Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); - }; + const docIcon = ; - const closeIcon = ; + const closeIcon = ; ReactDOM.render(docIcon, iconWrap); ReactDOM.render(closeIcon, closeWrap); tab.reactComponents = [iconWrap, closeWrap]; - // tab.element[0].append(closeWrap); tab.element[0].prepend(iconWrap); tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }), ({ layer, color }) => { -- cgit v1.2.3-70-g09d2 From bc662b4e7eaaa1f33f41f64ce64d60579939b971 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 10:18:05 -0400 Subject: fixed some problems with dragging doc onto empty tab bar. made topBar be just one line to save vertical space. added a context menu to Dashboard --- src/client/util/CurrentUserUtils.ts | 14 ++++--- src/client/util/DragManager.ts | 2 +- .../collectionLinear/CollectionLinearView.tsx | 1 + src/client/views/global/globalCssVariables.scss | 2 +- src/client/views/nodes/DocumentView.tsx | 11 +++--- src/client/views/topbar/TopBar.scss | 6 +-- src/client/views/topbar/TopBar.tsx | 45 ++++++++++++---------- 7 files changed, 46 insertions(+), 35 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e46bb3b3e..60bfc165b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -381,6 +381,7 @@ export class CurrentUserUtils { dontUndo: true, title, target, + dontRegisterView: true, hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), @@ -399,6 +400,7 @@ export class CurrentUserUtils { _chromeHidden: true, backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", + dontRegisterView: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), ignoreClick: true, _gridGap: 0, @@ -719,7 +721,7 @@ export class CurrentUserUtils { ]; doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, - linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true + childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() })))); } } @@ -833,12 +835,13 @@ export class CurrentUserUtils { }); if (doc.contextMenuBtns === undefined) { doc.contextMenuBtns = CurrentUserUtils.linearButtonList( - { title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, + { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, CurrentUserUtils.contextMenuTools(doc).map(params => !params.subMenu ? btnFunc(params) : CurrentUserUtils.linearButtonList({ title: params.title, + childDontRegisterViews: true, linearViewSubMenu: true, flexGap: 0, ignoreClick: true, linearViewExpandable: true, icon: params.title, _height: 30, linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, @@ -854,6 +857,7 @@ export class CurrentUserUtils { btnFunc(params) : CurrentUserUtils.linearButtonList({ title: params.title, + childDontRegisterViews: true, linearViewSubMenu: true, flexGap: 0, ignoreClick: true, linearViewExpandable: true, icon: params.title, _height: 30, linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, @@ -919,7 +923,7 @@ export class CurrentUserUtils { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, _chromeHidden: true, boxShadow: "0 0", - explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }, sharingDocumentId + "outer", sharingDocumentId); (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; } @@ -943,7 +947,7 @@ export class CurrentUserUtils { doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, - explainer: "This is where documents that are Imported into Dash will go." + dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." })); } } @@ -952,7 +956,7 @@ export class CurrentUserUtils { static setupSearchSidebar(doc: Doc) { if (doc.mySearchPanel === undefined) { doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({ - backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, + dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true })) as any as Doc; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9f8c49081..c3c6c22df 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -455,7 +455,7 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; } - if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { + if (((e.target as any)?.className === "lm_tabs" || ((e.target as any)?.className === "lm_header" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { startWindowDragTimer = setTimeout(async () => { startWindowDragTimer = undefined; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index c0a33a5e0..9b1cb1601 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -153,6 +153,7 @@ export class CollectionLinearView extends CollectionSubView() { PanelWidth={nested ? doc[WidthSym] : this.dimension} PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} renderDepth={this.props.renderDepth + 1} + dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} focus={emptyFunction} styleProvider={this.props.styleProvider} docViewPath={returnEmptyDoclist} diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index ce9cc05d6..a14634bdc 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -38,7 +38,7 @@ $small-text: 9px; // misc values $border-radius: 0.3em; $search-thumnail-size: 130; -$topbar-height: 55px; +$topbar-height: 32px; $antimodemenu-height: 36px; // dragged items diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1591840e6..60d16f044 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -764,7 +764,7 @@ export class DocumentViewInternal extends DocComponent this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" }); !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" }); - if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { + if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; @@ -834,10 +834,10 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); - !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); - !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); + helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); + !Doc.UserDoc().noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); + !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); } @@ -1204,6 +1204,7 @@ export class DocumentView extends React.Component { @observable public docView: DocumentViewInternal | undefined | null; + showContextMenu(pageX:number, pageY:number) { return this.docView?.onContextMenu(undefined, pageX, pageY); } get Document() { return this.props.Document; } get topMost() { return this.props.renderDepth === 0; } get rootDoc() { return this.docView?.rootDoc || this.Document; } diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss index 6662386d4..c5b340514 100644 --- a/src/client/views/topbar/TopBar.scss +++ b/src/client/views/topbar/TopBar.scss @@ -22,9 +22,9 @@ grid-auto-columns: 33.3% 33.3% 33.3%; align-items: center; - &:first-child { - height: 20px; - } + // &:first-child { + // height: 20px; + // } } .topbar-button-text { diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 57ceac2dd..5bee58ccb 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -3,14 +3,18 @@ import { Tooltip } from "@material-ui/core"; import { action } from "mobx"; import { observer } from "mobx-react"; import * as React from 'react'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { AclAdmin, Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, StrCast } from '../../../fields/Types'; +import { GetEffectiveAcl } from "../../../fields/util"; import { Utils } from '../../../Utils'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DocumentManager } from "../../util/DocumentManager"; +import { SelectionManager } from "../../util/SelectionManager"; import { SettingsManager } from "../../util/SettingsManager"; import { SharingManager } from "../../util/SharingManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; import { Borders, Colors } from "../global/globalEnums"; import { MainView } from "../MainView"; import "./TopBar.scss"; @@ -22,22 +26,38 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { - CurrentUserUtils.closeActiveDashboard(); - CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activePage = "home"); + CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { + Doc.UserDoc().activePage = "home"; + CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use + }); } render() { - const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); const activeDashboard = Cast(Doc.UserDoc().activeDashboard, Doc, null) return ( //TODO:glr Add support for light / dark mode
-
Home
+ {activeDashboard ?
{Doc.CurrentUserEmail} (Placeholder)
: (null)}
+
SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> + {activeDashboard ? StrCast(activeDashboard.title) : "Dash"} +
+
{ + const dashView = DocumentManager.Instance.getDocumentView(activeDashboard); + ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" }); + dashView?.showContextMenu(e.clientX+20, e.clientY+30); + }}> + +
+
{SharingManager.Instance.open(undefined, activeDashboard)}}> + {/* TODO: if this is my dashboard, display share + if this is a shared dashboard, display "view original or view annotated" */} + { Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original" } +
window.open( "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> @@ -47,21 +67,6 @@ export class TopBar extends React.Component {
-
-
- {activeDashboard ?
Freeform View (Placeholder)
: (null)} -
-
-
- {activeDashboard ? StrCast(activeDashboard.title) : "Dash"} -
-
-
- {/* TODO: if this is my dashboard, display share - if this is a shared dashboard, display "view original or view annotated" */} -
{SharingManager.Instance.open(undefined, activeDashboard)}}>Share
-
-
); -- cgit v1.2.3-70-g09d2 From 04fd9f20ccdc204dad5724008b076cbf9ea7b874 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 13:02:49 -0400 Subject: fixed some initialization problems with tabs --- src/client/util/History.ts | 6 ++-- .../views/collections/CollectionDockingView.tsx | 32 ++++++++++++---------- 2 files changed, 22 insertions(+), 16 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/History.ts b/src/client/util/History.ts index e6f75a7db..632348306 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -58,8 +58,10 @@ export namespace HistoryUtil { export function getState(): ParsedUrl { const state = copyState(history.state); - state.initializers = state.initializers || {}; - return state; + if (state) { + state.initializers = state.initializers || {}; + } + return state ?? {initializers:{}}; } // export function addHandler(handler: (state: ParsedUrl | null) => void) { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 4119fb5ba..d2687df17 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -188,10 +188,13 @@ export class CollectionDockingView extends CollectionSubView() { stack.addChild(docContentConfig, undefined); stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]); } else { - const newItemStackConfig = { type: 'stack', content: [docContentConfig] }; - const newContentItem = glayRoot.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); + const newContentItem = () => { + const newItem = glayRoot.layoutManager.createContentItem({ type: 'stack', content: [docContentConfig] }, instance._goldenLayout); + newItem.callDownwards('_$init'); + return newItem; + } if (glayRoot.contentItems.length === 0) { // if no rows / columns - glayRoot.addChild(newContentItem); + glayRoot.addChild(newContentItem()); } else if (glayRoot.contentItems[0].isStack) { glayRoot.contentItems[0].addChild(docContentConfig); } else if ( @@ -203,32 +206,33 @@ export class CollectionDockingView extends CollectionSubView() { else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row switch (pullSide) { default: - case "right": glayRoot.contentItems[0].addChild(newContentItem); break; - case "left": glayRoot.contentItems[0].addChild(newContentItem, 0); break; + case "right": glayRoot.contentItems[0].addChild(newContentItem()); break; + case "left": glayRoot.contentItems[0].addChild(newContentItem(), 0); break; case "top": case "bottom": // if not going in a row layout, must add already existing content into column const rowlayout = glayRoot.contentItems[0]; const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout); + const newItem = newContentItem(); instance._goldenLayout.saveScrollTops(rowlayout.element); rowlayout.parent.replaceChild(rowlayout, newColumn); if (pullSide === "top") { newColumn.addChild(rowlayout, undefined, true); - newColumn.addChild(newContentItem, 0, true); + newColumn.addChild(newItem, 0, true); } else if (pullSide === "bottom") { - newColumn.addChild(newContentItem, undefined, true); + newColumn.addChild(newItem, undefined, true); newColumn.addChild(rowlayout, 0, true); } instance._goldenLayout.restoreScrollTops(rowlayout.element); rowlayout.config.height = 50; - newContentItem.config.height = 50; + newItem.config.height = 50; } } else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column switch (pullSide) { - case "top": glayRoot.contentItems[0].addChild(newContentItem, 0); break; - case "bottom": glayRoot.contentItems[0].addChild(newContentItem); break; + case "top": glayRoot.contentItems[0].addChild(newContentItem(), 0); break; + case "bottom": glayRoot.contentItems[0].addChild(newContentItem()); break; case "left": case "right": default: @@ -236,23 +240,23 @@ export class CollectionDockingView extends CollectionSubView() { const collayout = glayRoot.contentItems[0]; const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout); + const newItem = newContentItem(); instance._goldenLayout.saveScrollTops(collayout.element); collayout.parent.replaceChild(collayout, newRow); if (pullSide === "left") { newRow.addChild(collayout, undefined, true); - newRow.addChild(newContentItem, 0, true); + newRow.addChild(newItem, 0, true); } else { - newRow.addChild(newContentItem, undefined, true); + newRow.addChild(newItem, undefined, true); newRow.addChild(collayout, 0, true); } instance._goldenLayout.restoreScrollTops(collayout.element); collayout.config.width = 50; - newContentItem.config.width = 50; + newItem.config.width = 50; } } instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig()); - newContentItem.callDownwards('_$init'); } return instance.layoutChanged(); -- cgit v1.2.3-70-g09d2 From 2cc83f81afa317c693aa45d3a2e1497ef7b2d477 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 15 Jun 2022 20:55:45 -0400 Subject: fixed iconifying cropped images to show the correct image. --- src/client/views/GestureOverlay.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 317f5f5d7..6afe64868 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -847,7 +847,7 @@ export class GestureOverlay extends Touchable { @computed get elements() { const selView = SelectionManager.Views().lastElement(); - const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc.viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1); + const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1); const rect = this._overlayRef.current?.getBoundingClientRect(); const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true); B.left = B.left - width / 2; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 99f74b8a2..52e99f26b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -133,8 +133,8 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } - @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document); } - @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document); } + @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } + @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } @computed get cachedCenteringShiftX(): number { const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections @@ -167,9 +167,11 @@ export class CollectionFreeFormView extends CollectionSubView !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined; reverseNativeScaling = () => this.fitContentsToBox ? true : false; - panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX); - panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY); - zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); + // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document. + // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image + panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1)); + panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1)); + zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; getTransform = () => this.cachedGetTransform.copy(); getLocalTransform = () => this.cachedGetLocalTransform.copy(); @@ -1414,9 +1416,9 @@ export class CollectionFreeFormView extends CollectionSubView Date: Fri, 17 Jun 2022 09:33:01 -0400 Subject: updated fontIconBadge to use 'viewed' list to decrement value when items are viewed. cleaned up how sharedDoc is setup in currentUserUtils to allow updates to not require rebuilding DB --- src/client/documents/Documents.ts | 10 ++- src/client/util/CurrentUserUtils.ts | 75 +++++++++++++--------- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/button/FontIconBadge.tsx | 32 ++++----- src/client/views/nodes/button/FontIconBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/fields/Doc.ts | 1 + src/fields/Types.ts | 4 ++ 8 files changed, 77 insertions(+), 53 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b290b2d58..5f009573e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,6 +98,7 @@ export class DocumentOptions { allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); + userColor?: string; // color associated with a Dash user (seen in header fields of shared documents) color?: string; // foreground color data doc backgroundColor?: STRt = new StrInfo("background color for data doc"); _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); @@ -190,6 +191,10 @@ export class DocumentOptions { childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children + childContextMenuFilters?: List; + childContextMenuScripts?: List; + childContextMenuLabels?: List; + childContextMenuIcons?: List; hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; @@ -198,7 +203,6 @@ export class DocumentOptions { hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; - watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) @@ -250,7 +254,8 @@ export class DocumentOptions { numBtnType?: string; numBtnMax?: number; numBtnMin?: number; - switchToggle?: boolean; + switchToggle?: boolean; + badgeValue?: ScriptField; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded @@ -294,6 +299,7 @@ export class DocumentOptions { treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically + treeViewChildDoubleClick?: ScriptField; // // Action Button buttonMenu?: boolean; // whether a action button should be displayed diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 60bfc165b..b4e18a8bb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -7,7 +7,7 @@ import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; @@ -354,6 +354,7 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc) { + const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -361,7 +362,7 @@ export class CurrentUserUtils { { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue}, { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, ]; @@ -369,8 +370,9 @@ export class CurrentUserUtils { 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 = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) => + await this.setupLinkDocs(doc, linkDatabaseId); + await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => Docs.Create.FontIconDocument({ icon, btnType: ButtonType.MenuButton, @@ -387,7 +389,7 @@ export class CurrentUserUtils { _removeDropProperties: new List(["dropAction", "_stayInCollection"]), _width: 60, _height: 60, - watchedDocuments, + badgeValue, onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) }) ); @@ -900,10 +902,7 @@ export class CurrentUserUtils { } // Sharing sidebar is where shared documents are contained - static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.myPublishedDocs === undefined) { - doc.myPublishedDocs = new List(); - } + static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { @@ -915,28 +914,41 @@ export class CurrentUserUtils { } doc.myLinkDatabase = new PrefetchProxy(linkDocs); } - // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document - if (doc.mySharedDocs === undefined) { - let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); - if (!sharedDocs) { - sharedDocs = Docs.Create.TreeDocument([], { - title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - _chromeHidden: true, boxShadow: "0 0", - dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" - }, sharingDocumentId + "outer", sharingDocumentId); - (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; - } - if (sharedDocs instanceof Doc) { - Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; - const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); - const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); - sharedDocs.childContextMenuFilters = new List([dashboardFilter!,]); - sharedDocs.childContextMenuScripts = new List([addToDashboards!,]); - sharedDocs.childContextMenuLabels = new List(["Add to Dashboards",]); - sharedDocs.childContextMenuIcons = new List(["user-plus",]); - } - doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc); + } + // A user's sharing document is where all documents that are shared to that user are placed. + // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field + // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents + static async setupSharedDocs(doc: Doc, sharingDocumentId: string) { + const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); + const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); + const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) + + const sharedDocOpts:DocumentOptions = { + title: "My Shared Docs", + userColor: "rgb(202, 202, 202)", + childContextMenuFilters: new List([dashboardFilter!,]), + childContextMenuScripts: new List([addToDashboards!,]), + childContextMenuLabels: new List(["Add to Dashboards",]), + childContextMenuIcons: new List(["user-plus",]), + treeViewChildDoubleClick: dblClkScript, + }; + const sharedRequiredDocOpts:DocumentOptions = { + "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar + _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, + explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + }; + + const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); + if (!(sharedDocs instanceof Doc)) { + doc.mySharedDocs = new PrefetchProxy( + Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); + } else { + Object.entries(sharedRequiredDocOpts).forEach(pair => { + const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc); + targetDoc[pair[0]] = pair[1]; + }); } } @@ -1044,6 +1056,7 @@ export class CurrentUserUtils { doc.savedFilters = new List(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; + doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); 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 diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8824750a3..59dc5671b 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -613,7 +613,7 @@ export class TreeView extends React.Component { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } - onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index cf86b5e07..df17d603f 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -6,31 +6,31 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils import { DragManager } from "../../../util/DragManager"; import "./FontIconBadge.scss"; -interface FontIconBadgeProps { - collection: Doc | undefined; +interface FontIconBadgeProps { + value: string | 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); - } + // 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 + if (this.props.value === undefined) return (null); return
-
0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} +
+ {this.props.value}
; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index a1b9023f3..97e6eddfe 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
{this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
); break; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f29b879b3..16a523b40 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -382,11 +382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - // creates links between terms in a document and documents which have a matching Id + // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6abc27b23..7b72787d1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 7e2aa5681..bf40a0d7b 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -76,6 +76,10 @@ export function Cast(field: FieldResult, ctor: T, defaultVal return defaultVal === null ? undefined : defaultVal; } +export function DocCast(field: FieldResult, defaultVal?: Doc) { + return Cast(field, Doc, null) ?? defaultVal; +} + export function NumCast(field: FieldResult, defaultVal: number | null = 0) { return Cast(field, "number", defaultVal); } -- cgit v1.2.3-70-g09d2