From 4f5f4b4da24dc0965f87dd33ff79279c6bfc2d7c Mon Sep 17 00:00:00 2001 From: jameshu111 Date: Thu, 16 Feb 2023 21:43:06 -0500 Subject: Modified Formatted Text Box and Document View Context Menu Icons Imported fa-ambulance, fa-highlighter, fa-remove-format, and fa-hand-point-up --- src/client/views/MainView.tsx | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 895ed9bda..0174e345b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -252,6 +252,7 @@ export class MainView extends React.Component { fa.faTaxi, fa.faDownload, fa.faExpandArrowsAlt, + fa.faAmbulance, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, @@ -477,6 +478,9 @@ export class MainView extends React.Component { fa.faSquareRootAlt, fa.faVolumeMute, fa.faUserCircle, + fa.faHighlighter, + fa.faRemoveFormat, + fa.faHandPointUp, ] ); this.initAuthenticationRouters(); -- cgit v1.2.3-70-g09d2 From af2a2c83868c87812e9ae54c8e3cced81374619a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Feb 2023 15:08:45 -0500 Subject: restructured getAnchor()/scrollFocus to be more consistent. added setterscript for computedFields. restructed getFieldsImpl to avoid making multiple requests for the same document due to timing issues by 'locking' a document cache with a promise before sending the server request. added rotation and fill color as animatable fields. fixed image cropping for --- src/client/DocServer.ts | 143 +++++++------- src/client/documents/Documents.ts | 23 ++- src/client/util/DocumentManager.ts | 4 +- src/client/util/LinkManager.ts | 2 +- src/client/util/SharingManager.tsx | 30 ++- src/client/views/DocumentButtonBar.tsx | 10 +- src/client/views/InkTangentHandles.tsx | 9 +- src/client/views/InkingStroke.tsx | 32 +++- src/client/views/MainView.tsx | 1 - src/client/views/MarqueeAnnotator.tsx | 35 ++-- src/client/views/StyleProvider.tsx | 3 + .../collections/CollectionStackedTimeline.tsx | 10 +- .../views/collections/CollectionTimeView.tsx | 37 +--- src/client/views/collections/TabDocView.tsx | 8 +- .../CollectionFreeFormLayoutEngines.tsx | 2 + .../collectionFreeForm/CollectionFreeFormView.tsx | 49 ++--- src/client/views/nodes/AudioBox.tsx | 12 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 23 ++- src/client/views/nodes/DocumentLinksButton.tsx | 20 +- src/client/views/nodes/DocumentView.tsx | 15 +- src/client/views/nodes/FunctionPlotBox.tsx | 6 +- src/client/views/nodes/ImageBox.tsx | 34 +--- src/client/views/nodes/LabelBox.tsx | 2 - src/client/views/nodes/PDFBox.tsx | 27 +-- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/ScriptingBox.tsx | 5 +- src/client/views/nodes/VideoBox.tsx | 23 +-- src/client/views/nodes/WebBox.tsx | 40 ++-- src/client/views/nodes/button/FontIconBox.tsx | 28 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/nodes/trails/PresBox.tsx | 211 ++++++++++++++------- src/client/views/pdf/AnchorMenu.tsx | 7 +- src/client/views/pdf/PDFViewer.tsx | 9 +- src/fields/Doc.ts | 28 ++- src/fields/ScriptField.ts | 6 +- src/server/DashUploadUtils.ts | 2 +- src/server/websocket.ts | 2 +- 37 files changed, 517 insertions(+), 387 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index cab90138f..8f79ebb03 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -234,7 +234,7 @@ export namespace DocServer { * the server if the document has not been cached. * @param id the id of the requested document */ - const _GetRefFieldImpl = async (id: string, force: boolean = false): Promise> => { + const _GetRefFieldImpl = (id: string, force: boolean = false): Promise> => { // an initial pass through the cache to determine whether the document needs to be fetched, // is already in the process of being fetched or already exists in the // cache @@ -309,8 +309,7 @@ export namespace DocServer { } export async function getYoutubeChannels() { - const apiKey = await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels }); - return apiKey; + return await Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels }); } export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) { @@ -329,10 +328,11 @@ export namespace DocServer { */ const _GetRefFieldsImpl = async (ids: string[]): Promise<{ [id: string]: Opt }> => { const requestedIds: string[] = []; - const waitingIds: string[] = []; - const promises: Promise>[] = []; - const map: { [id: string]: Opt } = {}; + const promises: Promise[] = []; + let defaultRes: any = undefined; + const defaultPromise = new Promise(res => (defaultRes = res)); + let defaultPromises: { p: Promise; id: string }[] = []; // 1) an initial pass through the cache to determine // i) which documents need to be fetched // ii) which are already in the process of being fetched @@ -340,6 +340,13 @@ export namespace DocServer { for (const id of ids) { const cached = _cache[id]; if (cached === undefined) { + defaultPromises.push({ + id, + p: (_cache[id] = new Promise(async res => { + await defaultPromise; + res(_cache[id]); + })), + }); // NOT CACHED => we'll have to send a request to the server requestedIds.push(id); } else if (cached instanceof Promise) { @@ -347,10 +354,10 @@ export namespace DocServer { // and requested one of the documents I'm looking for. Shouldn't fetch again, just // wait until this promise is resolved (see 7) promises.push(cached); - waitingIds.push(id); + // waitingIds.push(id); } else { // CACHED => great, let's just add it to the field map - map[id] = cached; + // map[id] = cached; } } @@ -358,74 +365,65 @@ export namespace DocServer { // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of // the fields have been returned from the server - console.log('Requesting ' + requestedIds.length + ' fields'); + console.log('Requesting ' + requestedIds.length); FieldLoader.active && runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)); - const getSerializedFields: Promise = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); + const serializedFields = await Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects. // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. - - let retrieved = 0; - const fields: { [id: string]: RefField } = {}; - await getSerializedFields.then(async fieldvals => { - console.log('deserializing ' + fieldvals.length + ' fields'); - const proms: Promise[] = []; - await runInAction(async () => { - for (const field of fieldvals) { - const cached = _cache[field.id]; - if (!cached) { - retrieved++; - if (FieldLoader.active && retrieved % 150 === 0) { - runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = retrieved)); - await new Promise(res => setTimeout(res)); + let processed = 0; + console.log('deserializing ' + serializedFields.length + ' fields'); + for (const field of serializedFields) { + processed++; + if (FieldLoader.active && processed % 150 === 0) { + runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = processed)); + await new Promise(res => setTimeout(res)); // force loading to yield to splash screen rendering to update progress + } + const cached = _cache[field.id]; + if (!cached || (cached instanceof Promise && defaultPromises.some(dp => dp.p === cached))) { + // deserialize + // adds to a list of promises that will be awaited asynchronously + promises.push( + (_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => { + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; + } else { + delete _cache[field.id]; } - // deserialize - const prom = SerializationHelper.Deserialize(field).then(async deserialized => { - fields[field.id] = deserialized; - - //overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (deserialized !== undefined) { - _cache[field.id] = deserialized; - } else { - delete _cache[field.id]; - } - return deserialized; - }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - _cache[field.id] = prom; - - // adds to a list of promises that will be awaited asynchronously - proms.push(prom); - } else if (cached instanceof Promise) { - console.log('.'); - proms.push(cached as any); - cached.then((f: any) => (fields[field.id] = f)); - } else if (field) { - console.log('-'); - proms.push(cached as any); - fields[field.id] = DocServer.GetCachedRefField(field.id) || field; - } - } - }); - return Promise.all(proms); - }); + const promInd = defaultPromises.findIndex(dp => dp.id === field.id); + promInd !== -1 && defaultPromises.splice(promInd, 1); + return deserialized; + })) + ); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + } else if (cached instanceof Promise) { + console.log('.'); + //promises.push(cached); + } else if (field) { + // console.log('-'); + } + } + } - // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose - // prototype documents, if any, have also been fetched and cached. - console.log('Deserialized ' + Object.keys(fields).length + ' fields'); + await Promise.all(promises); + defaultPromises.forEach(df => delete _cache[df.id]); + defaultRes(); - // 6) with this confidence, we can now go through and update the cache at the ids of the fields that - // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given - // id to the soon-to-be-returned field mapping. - requestedIds.forEach(id => (map[id] = fields[id])); - } + // 5) at this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose + // prototype documents, if any, have also been fetched and cached. + console.log('Deserialized ' + (requestedIds.length - defaultPromises.length) + ' fields'); + // 6) with this confidence, we can now go through and update the cache at the ids of the fields that + // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given + // id to the soon-to-be-returned field mapping. + //ids.forEach(id => (map[id] = _cache[id] as any)); // 7) those promises we encountered in the else if of 1), which represent // other callers having already submitted a request to the server for (a) document(s) @@ -434,16 +432,19 @@ export namespace DocServer { // // fortunately, those other callers will also hit their own version of 6) and clean up // the shared cache when these promises resolve, so all we have to do is... - const otherCallersFetching = await Promise.all(promises); + //const otherCallersFetching = await Promise.all(promises); // ...extract the RefFields returned from the resolution of those promises and add them to our // own map. - waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index])); + // waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index])); // now, we return our completed mapping from all of the ids that were passed into the method // to their actual RefField | undefined values. This return value either becomes the input // argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...)) // or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])). - return map; + return ids.reduce((map, id) => { + map[id] = _cache[id] as any; + return map; + }, {} as { [id: string]: Opt }); }; let _GetRefFields: (ids: string[]) => Promise<{ [id: string]: Opt }> = errorFunc; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index debb11066..d2ab67849 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -263,6 +263,7 @@ export class DocumentOptions { viewTransitionTime?: number; // transition duration for view parameters presTransition?: number; //the time taken for the transition TO a document presDuration?: number; //the duration of the slide in presentation view + presZoomText?: boolean; // whether text anchors should shown in a larger box when following links to make them stand out borderRounding?: string; boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow data?: any; @@ -1043,6 +1044,9 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), url, options, id); } + export function CollectionAnchorDocument(options: DocumentOptions = {}, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); + } export function TextanchorDocument(options: DocumentOptions = {}, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); } @@ -1051,6 +1055,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); } + export function InkAnchorDocument(options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); + } + export function HTMLAnchorDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id); } @@ -1407,12 +1415,15 @@ export namespace DocUtils { } }); funcs && - Object.keys(funcs).map(key => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { - doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }) : undefined; - } - }); + Object.keys(funcs) + .filter(key => key.endsWith('-setter')) + .map(key => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { + const setFunc = Cast(funcs[key + '-setter'], 'string', null); + doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined; + } + }); return doc; } export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index e4d48d4de..d9273c2c9 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -199,7 +199,7 @@ export class DocumentManager { static GetContextPath(doc: Opt, includeExistingViews?: boolean) { if (!doc) return []; const srcContext = Cast(doc.context, Doc, null) ?? Cast(Cast(doc.annotationOn, Doc, null)?.context, Doc, null); - var containerDocContext = srcContext ? [srcContext] : []; + var containerDocContext = srcContext ? [srcContext, doc] : [doc]; while ( containerDocContext.length && containerDocContext[0]?.context && @@ -286,7 +286,7 @@ export class DocumentManager { return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, originalTarget, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30); } if (!docView && targetDoc.type !== DocumentType.MARKER) { - annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below + annoContainerView.focus(targetDoc, options); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below } } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 7da16ca78..46d44fea4 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -48,7 +48,7 @@ export class LinkManager { const a2 = DocCast(linkdata[1]); a1 && a2 && - Promise.all([a1.proto, a2.proto]).then( + Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then( action(protos => { (protos[0] as Doc)?.[DirectLinksSym].add(link); (protos[1] as Doc)?.[DirectLinksSym].add(link); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 00ae85d12..36095700c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -130,29 +130,21 @@ export class SharingManager extends React.Component<{}> { if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') { this.populating = true; const userList = await RequestPromise.get(Utils.prepend('/getUsers')); - const raw = JSON.parse(userList) as User[]; - const sharingDocs: ValidatedUser[] = []; - const evaluating = raw.map(async user => { - const isCandidate = user.email !== Doc.CurrentUserEmail; - if (isCandidate) { - const sharingDoc = await DocServer.GetRefField(user.sharingDocumentId); - const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId); + const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail); + const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[])); + raw.map( + action((newUser: User) => { + const sharingDoc = docs[newUser.sharingDocumentId]; + const linkDatabase = docs[newUser.linkDatabaseId]; if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { - sharingDocs.push({ user, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) }); - } - } - }); - return Promise.all(evaluating).then(() => { - runInAction(async () => { - for (const sharer of sharingDocs) { - if (!this.users.find(user => user.user.email === sharer.user.email)) { - this.users.push(sharer); + if (!this.users.find(user => user.user.email === newUser.email)) { + this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) }); // LinkManager.addLinkDB(sharer.linkDatabase); } } - }); - this.populating = false; - }); + }) + ); + this.populating = false; } }; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index f61d147cf..9298c881c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -315,7 +315,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null) }); + TabDocView.PinDoc(docs, { + pinAudioPlay: true, + pinDocLayout, + pinData: { dataview: true }, + activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), + currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null), + }); e.stopPropagation(); }} /> @@ -496,7 +502,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const rootView = this.props.views()[0]; const rootDoc = rootView?.rootDoc; if (rootDoc) { - const anchor = rootView.ComponentView?.getAnchor?.(true) ?? rootDoc; + const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc; const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true); if (trail !== anchor.presTrail) { DocUtils.MakeLink({ doc: anchor }, { doc: trail }, 'link trail'); diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index c4a2f603e..71e0ff63c 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -38,18 +38,19 @@ export class InkTangentHandles extends React.Component { setupMoveUpEvents( this, e, - (e: PointerEvent, down: number[], delta: number[]) => { + action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('DocDecs move tangent'); if (e.altKey) this.onBreakTangent(controlIndex); const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] }); const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 }); InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex); return false; - }, - () => { + }), + action(() => { this.props.inkView.controlUndo?.end(); + this.props.inkView.controlUndo = undefined; UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); - }, + }), emptyFunction ); }; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 76c1aa80a..17cf6a678 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -25,7 +25,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, HeightSym, WidthSym } from '../../fields/Doc'; import { InkData, InkField } from '../../fields/InkField'; -import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { DashColor, OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils'; import { CognitiveServices } from '../cognitive_services/CognitiveServices'; @@ -41,11 +41,13 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; import './InkStroke.scss'; import { InkStrokeProperties } from './InkStrokeProperties'; import { InkTangentHandles } from './InkTangentHandles'; -import { DocComponentView, DocFocusOptions } from './nodes/DocumentView'; +import { DocComponentView, DocFocusOptions, DocumentView } from './nodes/DocumentView'; import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { StyleProp } from './StyleProvider'; import Color = require('color'); +import { Docs } from '../documents/Documents'; +import { PinProps, PresBox } from './nodes/trails'; @observer export class InkingStroke extends ViewBoxBaseComponent() { @@ -78,12 +80,28 @@ export class InkingStroke extends ViewBoxBaseComponent() { // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1); - getAnchor = (addAsAnnotation: boolean) => { - return this._subContentView?.getAnchor?.(addAsAnnotation) || this.rootDoc; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + if (this._subContentView) { + return this._subContentView.getAnchor?.(addAsAnnotation) || this.rootDoc; + } + + if (!addAsAnnotation && !pinProps) return this.rootDoc; + + const anchor = Docs.Create.InkAnchorDocument({ title: 'Ink anchor:' + this.rootDoc.title, presDuration: 1100, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); + if (anchor) { + anchor.backgroundColor = 'transparent'; + // /* addAsAnnotation &&*/ this.addDocument(anchor); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, inkable: true } }, this.rootDoc); + return anchor; + } + return this.rootDoc; }; - scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => { - return this._subContentView?.scrollFocus?.(textAnchor, options); + scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { + if (this._subContentView) return this._subContentView?.scrollFocus?.(docView, anchor, options); + + const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; + return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; }; /** * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); @@ -349,7 +367,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1); const closed = InkingStroke.IsClosed(inkData); const isInkMask = BoolCast(this.layoutDoc.isInkMask); - const fillColor = isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : StrCast(this.layoutDoc.fillColor, 'transparent'); + const fillColor = isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FillColor) ?? 'transparent'; const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color); // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be. diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 625dc2748..64f116a09 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -145,7 +145,6 @@ export class MainView extends React.Component { if (ele && prog) { // remove from DOM setTimeout(() => { - clearTimeout(); prog.style.transition = '1s'; prog.style.width = '100%'; }, 0); diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 3bdf65d01..30867a774 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -8,7 +8,7 @@ import { GetEffectiveAcl } from '../../fields/util'; import { unimplementedFunction, Utils } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import { DragManager } from '../util/DragManager'; -import { undoBatch } from '../util/UndoManager'; +import { undoBatch, UndoManager } from '../util/UndoManager'; import './MarqueeAnnotator.scss'; import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @@ -48,7 +48,11 @@ export class MarqueeAnnotator extends React.Component { constructor(props: any) { super(props); - AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true); + AnchorMenu.Instance.OnCrop = (e: PointerEvent) => { + if (this.props.anchorMenuCrop) { + UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping'); + } + }; AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)); AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; @@ -87,11 +91,8 @@ export class MarqueeAnnotator extends React.Component { AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - const sourceAnchorCreator = () => { - const annoDoc = this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true); // hyperlink color - annoDoc && this.props.addDocument(annoDoc); - return annoDoc; - }; + const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color + const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow'); FormattedTextBox.SelectOnLoad = target[Id]; @@ -116,11 +117,7 @@ export class MarqueeAnnotator extends React.Component { e.preventDefault(); e.stopPropagation(); var cropRegion: Doc | undefined; - const sourceAnchorCreator = () => { - cropRegion = this.highlight('', true); // hyperlink color - cropRegion && this.props.addDocument(cropRegion); - return cropRegion; - }; + const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: e => { @@ -159,7 +156,15 @@ export class MarqueeAnnotator extends React.Component { return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, text: this.props.selectionText(), backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { + annotationOn: this.props.rootDoc, + text: this.props.selectionText(), + backgroundColor: 'transparent', + presDuration: 2100, + presTransition: 500, + presZoomText: true, + title: 'Selection on ' + this.props.rootDoc.title, + }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; @@ -262,10 +267,6 @@ export class MarqueeAnnotator extends React.Component { AnchorMenu.Instance.jumpTo(cliX, cliY); - if (AnchorMenu.Instance.Highlighting) { - // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up - this.highlight('rgba(245, 230, 95, 0.75)', false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) - } this.props.finishMarquee(undefined, undefined, e); runInAction(() => (this._width = this._height = 0)); } else { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5f87f0697..153c30052 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -31,6 +31,7 @@ export enum StyleProp { BorderRounding = 'borderRounding', // border radius of the document view Color = 'color', // foreground color of Document view items BackgroundColor = 'backgroundColor', // background color of a document view + FillColor = 'fillColor', // fill color of an ink stroke or shape WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note HideLinkButton = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button LinkSource = 'linkSource', // source document of a link -- used by LinkAnchorBox @@ -158,6 +159,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 28f08b6ce..eecab9d86 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -190,7 +190,7 @@ export class CollectionStackedTimeline extends CollectionSubView 15 && !this.IsTrimming) { - const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); @@ -273,7 +273,7 @@ export class CollectionStackedTimeline extends CollectionSubView { if (shiftKey) { - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime, undefined, undefined, true); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true); } else { !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } @@ -388,8 +388,10 @@ export class CollectionStackedTimeline extends CollectionSubView, anchorEndTime: Opt, docAnchor: Opt, addAsAnnotation: boolean) { + static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt, anchorEndTime: Opt, docAnchor: Opt, addAsAnnotation: boolean) { if (anchorStartTime === undefined) return rootDoc; + const startTag = '_timecodeToShow'; + const endTag = '_timecodeToHide'; const anchor = docAnchor ?? Docs.Create.LabelDocument({ diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index e6f29ec37..437b22040 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -21,7 +21,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; import React = require('react'); -import { DocFocusOptions } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; import { PresBox } from '../nodes/trails'; @observer @@ -52,8 +52,7 @@ export class CollectionTimeView extends CollectionSubView() { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, annotationOn: this.rootDoc, }); - anchor.presPinPivotField = this.pivotField; // should be captured in pinDocView below - PresBox.pinDocView(anchor, { pinData: { viewType: true, filters: true } }, this.rootDoc); + PresBox.pinDocView(anchor, { pinData: { viewType: true, pivot: true, filters: true } }, this.rootDoc); if (addAsAnnotation) { // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered @@ -67,32 +66,12 @@ export class CollectionTimeView extends CollectionSubView() { }; @action - scrollFocus = (anchor: Doc, options: DocFocusOptions) => { - if (options.preview) { - // if in preview, then override document's fields with view spec - this._focusFilters = StrListCast(anchor.presPinDocFilters); - this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); - this._focusPivotField = StrCast(anchor.presPinPivotField); - return undefined; - } - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - - // should be part of restoreTargetDocView - this.layoutDoc._prevFilterIndex = 1; - this.layoutDoc._pivotField = anchor.presPinPivotField; - - return PresBox.restoreTargetDocView( - this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinLayout) }, - anchor, - focusSpeed, - { - viewType: anchor.presPinViewType ? true : false, - filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false, - } - ) - ? focusSpeed - : undefined; + scrollPreview = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { + // if in preview, then override document's fields with view spec + this._focusFilters = StrListCast(anchor.presPinDocFilters); + this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); + this._focusPivotField = StrCast(anchor.presPinPivotField); + return undefined; }; layoutEngine = () => this._layoutEngine; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index f93810deb..b75f315ca 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -247,13 +247,13 @@ export class TabDocView extends React.Component { alert('Cannot pin presentation document to itself'); return; } - const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false); - const pinDoc = anchorDoc && anchorDoc !== doc ? Doc.MakeDelegate(anchorDoc) : Doc.MakeDelegate(doc); + const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); + const pinDoc = Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc); pinDoc.presentationTargetDoc = anchorDoc ?? 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 = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; - pinDoc.presDuration = 2000; + pinDoc.presDuration = pinDoc.presDuration ?? 1000; 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 @@ -274,7 +274,7 @@ export class TabDocView extends React.Component { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc); + //PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, anchorDoc && anchorDoc !== doc && !anchorDoc.unrendered ? anchorDoc : doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 7dd9cdb8b..fa0695fb2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -14,6 +14,7 @@ export interface ViewDefBounds { x: number; y: number; z?: number; + rotation?: number; text?: string; zIndex?: number; width?: number; @@ -31,6 +32,7 @@ export interface PoolData { x: number; y: number; z?: number; + rotation?: number; zIndex?: number; width?: number; height?: number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7d176f426..0477c6a16 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,6 @@ import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { ObjectField } from '../../../../fields/ObjectField'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -41,7 +40,7 @@ import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDo import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../../nodes/trails/PresBox'; +import { PinProps, PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; @@ -523,6 +522,10 @@ export class CollectionFreeFormView extends CollectionSubView ); @@ -1425,17 +1428,21 @@ export class CollectionFreeFormView extends CollectionSubView { + scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; if (options.preview) { this._focusFilters = StrListCast(anchor.presPinDocFilters); this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); return undefined; } - return PresBox.restoreTargetDocView( - this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinLayout) }, - anchor, - focusSpeed, - { - dataview: anchor.presData ? true : false, - pannable: anchor.presPinData ? true : false, - viewType: anchor.presPinViewType ? true : false, - filters: anchor.presPinDocFilters || anchor.presPinDocRangeFilters ? true : false, - } - ) - ? focusSpeed - : undefined; + return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - getAnchor = (addAsAnnotation: boolean) => { - if (this.props.Document.annotationOn) { - return this.rootDoc; - } - + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) - const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc }); - PresBox.pinDocView(anchor, { pinData: { pannable: true, viewType: true, filters: true } }, this.rootDoc); + const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, viewType: true, filters: true } }, this.rootDoc); if (addAsAnnotation) { if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 1d59d3356..81dc3aafd 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -18,6 +18,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import './AudioBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; +import { PinProps, PresBox } from './trails'; /** * AudioBox @@ -136,20 +137,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - return ( + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + const anchor = CollectionStackedTimeline.createAnchor( this.rootDoc, this.dataDoc, this.annotationKey, - '_timecodeToShow' /* audioStart */, - '_timecodeToHide' /* audioEnd */, this._ele?.currentTime || Cast(this.props.Document._currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined), undefined, undefined, addAsAnnotation - ) || this.rootDoc - ); + ) || this.rootDoc; + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc); + return anchor; }; // updates timecode and shows it in timeline, follows links at time diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ba510e1dc..9a32556f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,7 +1,6 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; -import { InkField } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; @@ -20,11 +19,10 @@ import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; + dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; - rotation: number; dataTransition?: string; replica: string; CollectionFreeFormView: CollectionFreeFormView; @@ -46,7 +44,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; @@ -56,7 +54,7 @@ export class CollectionFreeFormDocumentView extends DocComponent, property: string) => { - if (doc === this.layoutDoc) + if (doc === this.layoutDoc) { // prettier-ignore switch (property) { case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children case StyleProp.BackgroundColor: return this.BackgroundColor; case StyleProp.Color: return this.Color; } + } return this.props.styleProvider?.(doc, props, property); }; @@ -108,6 +110,15 @@ export class CollectionFreeFormDocumentView extends DocComponent }); } + public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt }) { + const timecode = Math.round(time); + Object.keys(vals).forEach(val => { + const findexed = Cast(d[`${val}-indexed`], listSpec('string'), []).slice(); + findexed[timecode] = vals[val] as any as string; + d[`${val}-indexed`] = new List(findexed); + }); + } + public static setValues(time: number, d: Doc, vals: { [val: string]: Opt }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 2432d4c37..5783d0e57 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -16,6 +16,7 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { TaskCompletionBox } from './TaskCompletedBox'; import React = require('react'); import _ = require('lodash'); +import { PinProps } from './trails'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; @@ -175,7 +176,7 @@ export class DocumentLinksButton extends React.Component { + action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) => { if (startLink === endLink) { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; @@ -183,7 +184,7 @@ export class DocumentLinksButton extends React.Component DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}> + onClick={e => + DocumentLinksButton.StartLink && + DocumentLinksButton.finishLinkClick( + e.clientX, + e.clientY, + DocumentLinksButton.StartLink, + this.props.View.props.Document, + true, + this.props.View, + (e.shiftKey ? { pinDocLayout: true, pinDocContent: true, pinData: { poslayoutview: true, dataannos: true, dataview: true } } : {}) as PinProps + ) + }> ) : null} @@ -292,7 +304,7 @@ export class DocumentLinksButton extends React.Component void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document - getAnchor?: (addAsAnnotation: boolean) => 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, options: DocFocusOptions) => Opt; // returns the duration of the focus + getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => 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) + scrollPreview?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus + scrollFocus?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; 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 @@ -585,7 +586,13 @@ export class DocumentViewInternal extends DocComponent (focusSpeed => (PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined))(options.instant ? 0 : options.zoomTime ?? 500)); + const focusSpeed = targetMatch && scrollFocus?.(this.props.DocumentView(), presItem?.proto === anchor ? presItem : anchor, options); // FOCUS: navigate through the display hierarchy making sure the target is in view const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 5c0005dae..8fa01c97a 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -13,7 +13,7 @@ import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import { DocFocusOptions } from './DocumentView'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; const EquationSchema = createSchema({}); @@ -42,14 +42,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent ); } getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); anchor.xRange = new List(Array.from(this._plot.options.xAxis.domain)); anchor.yRange = new List(Array.from(this._plot.options.yAxis.domain)); if (addAsAnnotation) this.addDocument(anchor); return anchor; }; @action - scrollFocus = (doc: Doc, smooth: DocFocusOptions) => { + scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => { this.dataDoc.xRange = new List(Array.from(Cast(doc.xRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10])))); this.dataDoc.yRange = new List(Array.from(Cast(doc.yRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9])))); return 0; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 0ba576e55..363cd1d94 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -26,11 +26,11 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, OpenWhere } from './DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; -import { PresBox } from './trails'; +import { PinProps, PresBox } from './trails'; import React = require('react'); import Color = require('color'); import { LinkDocPreview } from './LinkDocPreview'; @@ -71,34 +71,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - return PresBox.restoreTargetDocView( - this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinLayout) }, - anchor, - focusSpeed, - !anchor.presPinData - ? {} - : { - pannable: true, - dataannos: anchor.presAnnotations !== undefined, - dataview: true, - } - ) - ? focusSpeed - : undefined; - }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor Docs.Create.ImageanchorDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; /* addAsAnnotation &&*/ this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true } }, this.rootDoc); return anchor; } return this.rootDoc; @@ -177,9 +157,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.rootDoc; - getTitle() { return this.rootDoc['title-custom'] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === 'string' ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 88d134bba..8d7ab2f0d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -5,7 +5,7 @@ import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -22,13 +22,13 @@ import { LightboxView } from '../LightboxView'; import { CreateImage } from '../nodes/WebBoxRenderer'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; +import { PinProps, PresBox } from './trails'; import { VideoBox } from './VideoBox'; import React = require('react'); -import { PresBox } from './trails'; -import { DocFocusOptions } from './DocumentView'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() { @@ -200,18 +200,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdfViewer?.brushView(view); - scrollFocus = (doc: Doc, options: DocFocusOptions) => { + scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { let didToggle = false; - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { this.toggleSidebar(options.preview); didToggle = true; } - if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; - this._initialScrollTarget = doc; - PresBox.restoreTargetDocView(this.props.DocumentView?.(), {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false }); - return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined); + if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1; + this._initialScrollTarget = anchor; + PresBox.restoreTargetDocView(docView, anchor, options.zoomTime ?? 500); + return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.presPinViewScroll, NumCast(anchor.y)), options) ?? (didToggle ? 1 : undefined); }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt = undefined; if (this._pdfViewer?.selectionContent()) { ele = document.createElement('div'); @@ -223,11 +223,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent { this._pdfViewer = pdfViewer; - if (this._initialScrollTarget) { - this.scrollFocus(this._initialScrollTarget, { instant: true }); + const docView = this.props.DocumentView?.(); + if (this._initialScrollTarget && docView) { + this.scrollFocus(docView, this._initialScrollTarget, { instant: true }); this._initialScrollTarget = undefined; } }; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index f94996c66..61e4894f0 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -130,7 +130,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { const startTime = Cast(this.layoutDoc._currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc; }; videoLoad = () => { diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 281967a21..fa2021642 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -20,6 +20,7 @@ import { EditableView } from '../EditableView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { OverlayView } from '../OverlayView'; import { DocumentIconContainer } from './DocumentIcon'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import './ScriptingBox.scss'; const _global = (window /* browser */ || global) /* node */ as any; @@ -481,10 +482,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { - return undefined; - }; - getSuggestedParams(pos: number) { const firstScript = this.rawText.slice(0, pos); const indexP = firstScript.lastIndexOf('.'); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1dfa55c64..c26562e7c 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -7,6 +7,7 @@ import * as rp from 'request-promise'; import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; +import { ObjectField } from '../../../fields/ObjectField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; import { emptyFunction, formatTime, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -27,11 +28,11 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; +import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; +import { PinProps, PresBox } from './trails'; import './VideoBox.scss'; -import { ObjectField } from '../../../fields/ObjectField'; -import { DocFocusOptions, OpenWhere } from './DocumentView'; const path = require('path'); /** @@ -390,13 +391,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, 'move', true)); }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const timecode = Cast(this.layoutDoc._currentTimecode, 'number', null); const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation); - return ( - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow' /* videoStart */, '_timecodeToHide' /* videoEnd */, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || - this.rootDoc - ); + if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent'; + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc; + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc); + return anchor; }; // sets video info on load @@ -953,14 +954,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (doc !== this.rootDoc) { - const showTime = Cast(doc._timecodeToShow, 'number', null); - showTime !== undefined && setTimeout(() => this.Seek(showTime), 100); - return 0.1; - } - }; - // renders CollectionStackedTimeline @computed get renderTimeline() { return ( diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 4c9bead9d..caab18e0d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; @@ -11,8 +11,9 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; +import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -21,7 +22,6 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; -import { DocumentDecorations } from '../DocumentDecorations'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; @@ -29,13 +29,13 @@ import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentView, DocumentViewInternal, DocumentViewProps } from './DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from './LinkDocPreview'; import { VideoBox } from './VideoBox'; import './WebBox.scss'; import React = require('react'); -import { DragManager } from '../../util/DragManager'; +import { PinProps, PresBox } from './trails'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -189,6 +189,7 @@ export class WebBox extends ViewBoxAnnotatableComponent void) => (this._setBrushViewer = func); brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view); - scrollFocus = (doc: Doc, options: DocFocusOptions) => { - if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.preview); - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + focus = (doc: Doc, options: DocFocusOptions) => { + !doc.unrendered && this.props.DocumentView?.() && this.scrollFocus(this.props.DocumentView?.(), doc, {}); + this.props.focus(doc, options); + }; + scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { + if (this._url && StrCast(anchor.webUrl) !== this._url) this.submitURL(StrCast(anchor.webUrl), options.preview); + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { this.toggleSidebar(options.preview); } - if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; - if (doc !== this.rootDoc && this._outerRef.current) { + if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1; + if (anchor !== this.rootDoc && this._outerRef.current) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this._scrollHeight)); + const scrollTo = !anchor[HeightSym]() + ? NumCast(anchor.y) + : Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight)); + const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; if (scrollTo !== undefined && this._initialScroll === undefined) { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; this.goTo(scrollTo, focusSpeed, options.easeFunc); + PresBox.restoreTargetDocView(docView, anchor, focusSpeed); return focusSpeed; } else if (!this._webPageHasBeenRendered || !this._scrollHeight || this._initialScroll !== undefined) { this._initialScroll = scrollTo; + return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; } } return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt = undefined; try { const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents(); @@ -330,9 +339,11 @@ export class WebBox extends ViewBoxAnnotatableComponent { + const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed + if (contentFrameNumber !== undefined) { + CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color }); + } else { + dv.rootDoc._backgroundColor = color; + } + }); + } else { + const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : []; + if (checkResult) { + return selected.lastElement()?._backgroundColor ?? 'transparent'; + } + selected.forEach(doc => (doc._backgroundColor = color)); } - if (selected) selected._backgroundColor = color; }); // toggle: Set overlay status of selected document diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 788de7af9..269438a7c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; -import { DocFocusOptions, DocumentViewInternal, OpenWhere } from '../DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; import { DashDocCommentView } from './DashDocCommentView'; @@ -932,7 +932,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + scrollFocus = (docView: DocumentView, textAnchor: Doc, options: DocFocusOptions) => { let didToggle = false; if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) { this.toggleSidebar(options.preview); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 13cbd87eb..45d386436 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -4,9 +4,9 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; -import { InkTool } from '../../../../fields/InkField'; +import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; @@ -34,8 +34,23 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; +import { SerializationHelper } from '../../../util/SerializationHelper'; const { Howl } = require('howler'); +export interface pinDataTypes { + scrollable?: boolean; + pannable?: boolean; + viewType?: boolean; + inkable?: boolean; + filters?: boolean; + pivot?: boolean; + temporal?: boolean; + clippable?: boolean; + dataview?: boolean; + textview?: boolean; + poslayoutview?: boolean; + dataannos?: boolean; +} export interface PinProps { audioRange?: boolean; activeFrame?: number; @@ -45,18 +60,7 @@ export interface PinProps { pinDocLayout?: boolean; // pin layout info (width/height/x/y) pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) pinAudioPlay?: boolean; // pin audio annotation - pinData?: { - scrollable?: boolean | undefined; - pannable?: boolean | undefined; - viewType?: boolean | undefined; - filters?: boolean | undefined; - temporal?: boolean | undefined; - clippable?: boolean | undefined; - dataview?: boolean | undefined; - textview?: boolean | undefined; - poslayoutview?: boolean | undefined; - dataannos?: boolean | undefined; - }; + pinData?: pinDataTypes; } @observer @@ -318,19 +322,9 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - static pinDataTypes(target?: Doc): { - scrollable?: boolean; - pannable?: boolean; - viewType?: boolean; - filters?: boolean; - temporal?: boolean; - clippable?: boolean; - dataview?: boolean; - textview?: boolean; - poslayoutview?: boolean; - dataannos?: boolean; - } { + static pinDataTypes(target?: Doc): pinDataTypes { const targetType = target?.type as any; + const inkable = [DocumentType.INK].includes(targetType); const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._viewType === CollectionViewType.Stacking; const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType); @@ -340,18 +334,19 @@ export class PresBox extends ViewBoxBaseComponent() { const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const viewType = targetType === DocumentType.COL; const filters = true; + const pivot = true; const dataannos = false; - return { scrollable, pannable, viewType, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos }; } @action playAnnotation = (anno: AudioField) => {}; @action - static restoreTargetDocView(bestTargetView: Opt, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTargetView?.rootDoc)) { - if (!bestTargetView) return; - const bestTarget = bestTargetView.rootDoc; + static restoreTargetDocView(bestTargetView: Opt, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.presPinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) { + const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); + if (!bestTarget) return; let changed = false; - if (pinProps?.pinDocLayout) { + if (pinDocLayout) { if ( bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) || bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) || @@ -369,33 +364,51 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } - if (pinDataTypes.clippable) { + if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presPinClipWidth !== undefined)) { if (bestTarget._clipWidth !== activeItem.presPinClipWidth) { bestTarget._clipWidth = activeItem.presPinClipWidth; changed = true; } } - if (pinDataTypes.temporal) { + if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.presStartTime !== undefined)) { if (bestTarget._currentTimecode !== activeItem.presStartTime) { bestTarget._currentTimecode = activeItem.presStartTime; changed = true; } } - if (pinDataTypes.viewType && activeItem.presPinViewType !== undefined) { + if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.presFillColor !== undefined || activeItem.color !== undefined))) { + if (bestTarget.fillColor !== activeItem.presFillColor) { + Doc.GetProto(bestTarget).fillColor = activeItem.presFillColor; + changed = true; + } + if (bestTarget.color !== activeItem.color) { + Doc.GetProto(bestTarget).color = activeItem.color; + changed = true; + } + } + if ((pinDataTypes?.viewType && activeItem.presPinViewType !== undefined) || (!pinDataTypes && activeItem.presPinViewType !== undefined)) { if (bestTarget._viewType !== activeItem.presPinViewType) { bestTarget._viewType = activeItem.presPinViewType; changed = true; } } - if (pinDataTypes.filters && activeItem.presPinDocFilters !== undefined) { + if ((pinDataTypes?.filters && activeItem.presPinDocFilters !== undefined) || (!pinDataTypes && activeItem.presPinDocFilters !== undefined)) { if (bestTarget.docFilters !== activeItem.presPinDocFilters) { bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presPinDocFilters as ObjectField) || new List([]); changed = true; } } - if (pinDataTypes.scrollable) { + if ((pinDataTypes?.pivot && activeItem.presPinPvitoField !== undefined) || (!pinDataTypes && activeItem.presPinPivotField !== undefined)) { + if (bestTarget.pivotField !== activeItem.presPinPivotField) { + bestTarget.pivotField = activeItem.presPinPivotField; + bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView + changed = true; + } + } + + if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presPinViewScroll !== undefined)) { if (bestTarget._scrollTop !== activeItem.presPinViewScroll) { bestTarget._scrollTop = activeItem.presPinViewScroll; changed = true; @@ -406,39 +419,59 @@ export class PresBox extends ViewBoxBaseComponent() { } } } - if (pinDataTypes.dataannos) { + if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.presAnnotations !== undefined)) { const fkey = Doc.LayoutFieldKey(bestTarget); - Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List([...DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered), ...DocListCast(activeItem.presAnnotations)]); + const oldItems = DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered); + const newItems = DocListCast(activeItem.presAnnotations).map(doc => { + doc.hidden = false; + return doc; + }); + const hiddenItems = DocListCast(bestTarget[fkey + '-annotations']) + .filter(doc => !doc.unrendered && !newItems.includes(doc)) + .map(doc => { + doc.hidden = true; + return doc; + }); + const newList = new List([...oldItems, ...hiddenItems, ...newItems]); + Doc.GetProto(bestTarget)[fkey + '-annotations'] = newList; } - if (pinDataTypes.dataview && activeItem.presData !== undefined) { + if ((pinDataTypes?.dataview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { bestTarget._dataTransition = `all ${transTime}ms`; const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); } - if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - if (pinDataTypes.poslayoutview) { + if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { + Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + } + if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) { changed = true; + const layoutField = Doc.LayoutFieldKey(bestTarget); + const transitioned = new Set(); StrListCast(activeItem.presPinLayoutData) - .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) - .forEach(data => { - const doc = DocServer.GetCachedRefField(data.id) as Doc; - doc._dataTransition = `all ${transTime}ms`; - doc.x = data.x; - doc.y = data.y; - doc._width = data.w; - doc._height = data.h; + .map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string }) + .forEach(async data => { + const doc = DocCast(DocServer.GetCachedRefField(data.id)); + if (doc) { + transitioned.add(doc); + const field = !data.data ? undefined : await SerializationHelper.Deserialize(data.data); + const tfield = !data.text ? undefined : await SerializationHelper.Deserialize(data.text); + doc._dataTransition = `all ${transTime}ms`; + doc.x = data.x; + doc.y = data.y; + data.back && (doc._backgroundColor = data.back); + data.fill && (doc._fillColor = data.fill); + doc._width = data.w; + doc._height = data.h; + data.data && (Doc.GetProto(doc).data = field); + data.text && (Doc.GetProto(doc).text = tfield); + Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc); + } }); - setTimeout( - () => - StrListCast(activeItem.presPinLayoutData) - .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) - .forEach(action(data => ((DocServer.GetCachedRefField(data.id) as Doc)._dataTransition = undefined))), - transTime + 10 - ); + setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10); } - if (pinDataTypes.pannable) { + if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPinViewX !== undefined || activeItem.presPinViewScale !== undefined))) { const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; @@ -460,7 +493,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } if (changed) { - return bestTargetView.setViewTransition('all', transTime); + return bestTargetView?.setViewTransition('all', transTime); } } @@ -499,12 +532,31 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presAnnotations = new List(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']).filter(doc => !doc.unrendered)); } if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; + if (pinProps.pinData.inkable) { + pinDoc.presFillColor = targetDoc.fillColor; + pinDoc.presColor = targetDoc.color; + } if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop; if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth; if (pinProps.pinData.poslayoutview) - pinDoc.presPinLayoutData = new List(DocListCast(targetDoc[fkey] as ObjectField).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); + pinDoc.presPinLayoutData = new List( + DocListCast(targetDoc[fkey] as ObjectField).map(d => + JSON.stringify({ + id: d[Id], + x: NumCast(d.x), + y: NumCast(d.y), + w: NumCast(d._width), + h: NumCast(d._height), + fill: StrCast(d._fillColor), + back: StrCast(d._backgroundColor), + data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''), + text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''), + }) + ) + ); if (pinProps.pinData.viewType) pinDoc.presPinViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presPinDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); + if (pinProps.pinData.pivot) pinDoc.presPinPivotField = targetDoc._pivotField; if (pinProps.pinData.pannable) { pinDoc.presPinViewX = NumCast(targetDoc._panX); pinDoc.presPinViewY = NumCast(targetDoc._panY); @@ -586,7 +638,7 @@ export class PresBox extends ViewBoxBaseComponent() { const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { // targetDoc may or may not be displayed. so get the first available document (or alias) view that matches targetDoc and use it - PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); + // PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500), undefined, targetDoc); } }; const finishAndRestoreLayout = () => { @@ -660,16 +712,43 @@ export class PresBox extends ViewBoxBaseComponent() { _exitTrail: Opt<() => void>; PlayTrail = (docs: Doc[]) => { - const savedStates = docs.map(doc => (doc._viewType !== CollectionViewType.Freeform ? undefined : { c: doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) })); + const savedStates = docs.map(doc => { + switch (doc.type) { + case DocumentType.COL: + if (doc._viewType === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) }; + break; + case DocumentType.INK: + if (doc.data instanceof InkField) { + return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) }; + } + } + return undefined; + }); this.startPresentation(0); this._exitTrail = () => { savedStates .filter(savedState => savedState) .map(savedState => { - const { x, y, s, c } = savedState!; - c._panX = x; - c._panY = y; - c._viewScale = s; + switch (savedState?.type) { + case CollectionViewType.Freeform: + { + const { x, y, s, doc } = savedState!; + doc._panX = x; + doc._panY = y; + doc._viewScale = s; + } + break; + case DocumentType.INK: + { + const { data, fillColor, color, x, y, doc } = savedState!; + doc.x = x; + doc.y = y; + doc.data = data; + doc.fillColor = fillColor; + doc.color = color; + } + break; + } }); LightboxView.SetLightboxDoc(undefined); Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); @@ -1869,7 +1948,7 @@ export class PresBox extends ViewBoxBaseComponent() { ); } - scrollFocus = () => { + scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => { // this.gotoDocument(0); // this.startOrPause(false); return undefined; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 4a7f5d038..fc8f1da49 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -41,7 +41,6 @@ export class AnchorMenu extends AntimodeMenu { @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)'; @observable private _showLinkPopup: boolean = false; - @observable public Highlighting: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search @@ -121,9 +120,7 @@ export class AnchorMenu extends AntimodeMenu { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight(this.highlightColor, false, undefined, true) && this.Pinned) { - this.Highlighting = !this.Highlighting; - } + this.Highlight(this.highlightColor, false, undefined, true); AnchorMenu.Instance.fadeOut(true); }; @@ -138,7 +135,7 @@ export class AnchorMenu extends AntimodeMenu { const button = ( diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 2db44788f..9877690f8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. -pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.14.305/build/pdf.worker.js'; +pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js'; interface IViewerProps extends FieldViewProps { Document: Doc; @@ -179,7 +179,7 @@ export class PDFViewer extends React.Component { let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, this._scrollHeight); + const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc }; else if (!options.instant && !options.preview) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); @@ -518,6 +518,10 @@ export class PDFViewer extends React.Component { return this.props.styleProvider?.(doc, props, property); }; + focus = (doc: Doc, options: DocFocusOptions) => { + !doc.unrendered && this.props.DocumentView?.() && this.scrollToAnnotation(doc); + this.props.focus(doc, options); + }; renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
{ PanelWidth={this.panelWidth} ScreenToLocalTransform={this.overlayTransform} dropAction={'alias'} + focus={this.focus} docFilters={docFilters} select={emptyFunction} bringToFront={emptyFunction} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index df49c32f0..ed5eaa756 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -722,13 +722,22 @@ export namespace Doc { return bestAlias ?? Doc.MakeAlias(doc); } - export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise { + export async function makeClone( + doc: Doc, + cloneMap: Map, + linkMap: Map, + rtfs: { copy: Doc; key: string; field: RichTextField }[], + exclusions: string[], + topLevelExclusions: string[], + dontCreate: boolean, + asBranch: boolean + ): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true); cloneMap.set(doc[Id], copy); const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions; - const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + const filter = [...fieldExclusions, ...topLevelExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; await Promise.all( Object.keys(doc).map(async key => { if (filter.includes(key)) return; @@ -739,10 +748,10 @@ export namespace Doc { const list = await Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch))); !dontCreate && assignKey(new List(clones)); } else if (doc[key] instanceof Doc) { - assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields + assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields } else { !dontCreate && assignKey(ObjectField.MakeCopy(field)); if (field instanceof RichTextField) { @@ -754,17 +763,18 @@ export namespace Doc { }; if (key === 'proto') { if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); } } else if (key === 'anchor1' || key === 'anchor2') { if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch)); + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], true, asBranch)); } } else { if (field instanceof RefField) { assignKey(field); } else if (cfield instanceof ComputedField) { - !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); + !dontCreate && assignKey(cfield[Copy]()); + // ComputedField.MakeFunction(cfield.script.originalScript)); } else if (field instanceof ObjectField) { await copyObjectField(field); } else if (field instanceof Promise) { @@ -776,7 +786,7 @@ export namespace Doc { }) ); for (const link of Array.from(doc[DirectLinksSym])) { - const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); + const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch); linkMap.set(link, linkClone); } if (!dontCreate) { @@ -793,7 +803,7 @@ export namespace Doc { export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { const linkMap = new Map(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], ['context'], dontCreate, asBranch); Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index b23732b45..5f02bd73d 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -185,9 +185,11 @@ export class ComputedField extends ScriptField { const compiled = ScriptField.CompileScript(script, params, false); return compiled.compiled ? new ComputedField(compiled) : undefined; } - public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); - return compiled.compiled ? new ComputedField(compiled) : undefined; + const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; + const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined; + return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined; } public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt) { if (!doc[`${fieldKey}-indexed`]) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 33809824f..f461cf3fa 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -501,7 +501,7 @@ export namespace DashUploadUtils { const parseExifData = async (source: string) => { const image = await request.get(source, { encoding: null }); - const { data, error } = await new Promise(resolve => { + const { data, error } = await new Promise<{ data: any; error: any }>(resolve => { new ExifImage({ image }, (error, data) => { let reason: Opt = undefined; if (error) { diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 9b91a35a6..68b003496 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -199,7 +199,7 @@ export namespace WebSocket { return Database.Instance.getDocument(id, callback); } function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { - process.stdout.write(`.`); + process.stdout.write(`+`); GetRefFieldLocal([id, callback]); } -- cgit v1.2.3-70-g09d2 From 9c63f3833f15cc995c2255b83923384686127f3e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 1 Mar 2023 11:57:05 -0500 Subject: changed inPlace link following to be lightbox. allowed collections to be labeled as lightboxes. lightbox collections display link targets in an overlay that hides the rest of their content --- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 28 ++++++---- src/client/util/LinkFollower.ts | 10 ++-- src/client/views/DocumentDecorations.tsx | 13 ++--- src/client/views/MainView.tsx | 1 - src/client/views/PropertiesButtons.tsx | 62 +++++++++++----------- src/client/views/PropertiesView.tsx | 1 - src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 22 ++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 17 +++--- src/client/views/nodes/MapBox/MapBox.tsx | 1 - src/fields/documentSchemas.ts | 4 +- 13 files changed, 88 insertions(+), 78 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d2ab67849..70c57e8fa 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -160,6 +160,7 @@ export class DocumentOptions { _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) + _isLightbox?: boolean; // whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs _noAutoscroll?: boolean; // whether collections autoscroll when this item is dragged diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index d9273c2c9..0d10bed43 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,7 +10,7 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { CollectionView } from '../views/collections/CollectionView'; import { LightboxView } from '../views/LightboxView'; -import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; @@ -307,16 +307,22 @@ export class DocumentManager { } //} if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); - const doFocus = (forceDidFocus: boolean) => - focusView.focus(originalTarget, { - ...options, - originalTarget, - afterFocus: (didFocus: boolean) => - new Promise(res => { - focusAndFinish(forceDidFocus || didFocus); - res(ViewAdjustment.doNothing); - }), - }); + const doFocus = (forceDidFocus: boolean) => { + if (options.openInLightbox || (!options.originatingDoc?.followLinkLocation && DocCast(focusView.rootDoc.context)?._isLightbox)) { + focusView.props.addDocTab(targetDoc, OpenWhere.lightbox); + focusAndFinish(true); + } else { + focusView.focus(originalTarget, { + ...options, + originalTarget, + afterFocus: (didFocus: boolean) => + new Promise(res => { + focusAndFinish(forceDidFocus || didFocus); + res(ViewAdjustment.doNothing); + }), + }); + } + }; if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) { focusView.iconify(() => doFocus(true)); } else { diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 50bc89ed2..e86e68c21 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -3,6 +3,7 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { List } from '../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView'; @@ -36,7 +37,9 @@ export class LinkFollower { const createTabForTarget = (didFocus: boolean) => new Promise(res => { const where = LightboxView.LightboxDoc ? OpenWhere.lightbox : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere); - docViewProps.addDocTab(doc, where); + const lightbox = where === OpenWhere.lightbox && DocumentManager.GetContextPath(doc).find(container => container.isLightbox && DocumentManager.Instance.getDocumentView(container)); + const addDocTab = lightbox ? DocumentManager.Instance.getDocumentView(lightbox)?.ComponentView?.addDocTab : undefined; + (addDocTab ?? docViewProps.addDocTab)(doc, where); setTimeout(() => { const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. if (targDocView) { @@ -51,7 +54,7 @@ export class LinkFollower { }); } else { finished?.(); - res(where !== OpenWhere.inPlace || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target + res(BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); } }, 100); }); @@ -109,6 +112,7 @@ export class LinkFollower { zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500), zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, + openInLightbox: sourceDoc.followLinkLocation === OpenWhere.lightbox, effect: sourceDoc, originatingDoc: sourceDoc, zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText), @@ -123,7 +127,7 @@ export class LinkFollower { } else { const containerDocContext = DocumentManager.GetContextPath(target); const targetContexts = !sourceDoc.followLinkToOuterContext && containerDocContext.length ? [containerDocContext.lastElement()] : containerDocContext; - DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished); + DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.lightbox), finished), targetContexts, allFinished); } }; let movedTarget = false; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4e51f10a8..eeef01d17 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; -import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); @@ -283,11 +283,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); Doc.deiconifyView(openDoc); } - LightboxView.SetLightboxDoc( - openDoc, - undefined, - selectedDocs.slice(1).map(view => view.props.Document) - ); + selectedDocs[0].props.addDocTab(openDoc, OpenWhere.lightbox); + // LightboxView.SetLightboxDoc( + // openDoc, + // undefined, + // selectedDocs.slice(1).map(view => view.props.Document) + // ); } } SelectionManager.DeselectAll(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1e9220e1e..95c0f3755 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -692,7 +692,6 @@ export class MainView extends React.Component { if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { - case OpenWhere.inPlace: // fall through to lightbox case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location); case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 7e33ee495..a5c01490f 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -7,7 +7,7 @@ import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; import { ScriptField } from '../../fields/ScriptField'; -import { BoolCast, StrCast } from '../../fields/Types'; +import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; @@ -20,6 +20,7 @@ import { VideoBox } from './nodes/VideoBox'; import { pasteImageBitmap } from './nodes/WebBoxRenderer'; import './PropertiesButtons.scss'; import React = require('react'); +import { LinkManager } from '../util/LinkManager'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -118,14 +119,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { on => 'object-group' ); } - // this implments a container pattern by marking the targetDoc (collection) as an inPlace container, - // and then making the contained collection be a "menu" such that when any of its contents are clicked, - // they will open their targets in the outer container. To get back to the "menu", you click on the main container. - @computed get inPlaceContainerButton() { + // this implments a container pattern by marking the targetDoc (collection) as a lightbox + // that always fits its contents to its container and that hides all other documents when + // a link is followed that targets a 'lightbox' destination + @computed get isLightboxButton() { return this.propertyToggleBtn( - 'In Place', - 'isInPlaceContainer', - on => `${on ? 'Make' : 'Remove'} in place container flag`, + 'Lightbox', + 'isLightbox', + on => `${on ? 'Set' : 'Remove'} lightbox flag`, on => 'window-restore', onClick => { SelectionManager.Views().forEach(dv => { @@ -134,16 +135,12 @@ export class PropertiesButtons extends React.Component<{}, {}> { // containerDoc.noShadow = // containerDoc.noHighlighting = // containerDoc._forceActive = - containerDoc._fitContentsToBox = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer; - containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined; + containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox; + containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); containerContents.forEach(doc => { - DocListCast(doc.links).forEach(link => { - doc.isLinkButton = true; - //doc.followLinkLocation = OpenWhere.inPlace; - link.linkDisplay = false; - }); + DocListCast(doc.links).forEach(link => (link.linkDisplay = false)); }); }); } @@ -290,7 +287,6 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action handleOptionChange = (onClick: string) => { - this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick); SelectionManager.Views() .filter(dv => dv.docView) .map(dv => dv.docView!) @@ -305,7 +301,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { break; case 'linkInPlace': docView.toggleFollowLink(false, false); - docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.inPlace : undefined; + docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.lightbox : undefined; break; case 'linkOnRight': docView.toggleFollowLink(false, false); @@ -327,24 +323,26 @@ export class PropertiesButtons extends React.Component<{}, {}> { ['nothing', 'Select Document'], ['enterPortal', 'Enter Portal'], ['toggleDetail', 'Toggle Detail'], - ['linkInPlace', 'Open in Place'], + ['linkInPlace', 'Open Link in Lightbox'], ['linkOnRight', 'Open Link on Right'], ]; - const currentSelection = this.selectedDoc.onClickBehavior; - // Get items to place into the list - const list = buttonList.map(value => { - const click = () => { - this.handleOptionChange(value[0]); - }; + const click = () => this.handleOptionChange(value[0]); + const linkButton = BoolCast(this.selectedDoc._isLinkButton); + const followLoc = this.selectedDoc._followLinkLocation; + const linkedToLightboxView = () => DocListCast(this.selectedDoc.links).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); + + let active = false; + // prettier-ignore + switch (value[0]) { + case 'linkInPlace': active = linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView(); break; + case 'linkOnRight': active = linkButton && followLoc === OpenWhere.addRight; break; + case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break; + case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break; + case 'nothing': active = !linkButton && this.selectedDoc.onClick === undefined;break; + } return ( -
+
{value[1]}
); @@ -413,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.freezeThumb)} {toggle(this.forceActiveButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} - {toggle(this.inPlaceContainerButton, { display: !isFreeForm && !isMap ? 'none' : '' })} + {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} {toggle(this.maskButton, { display: !isInk ? 'none' : '' })} {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index e9d1433c3..0fe165783 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1626,7 +1626,6 @@ export class PropertiesView extends React.Component { - {LinkManager.currentLink?.linksToAnnotation ? : null}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 90406ed3c..316f1e4e9 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -191,7 +191,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); + !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' }); if (!Doc.noviceMode && false) { optionItems.push({ diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 01872df84..042c2b71b 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -350,7 +350,7 @@ export class TabDocView extends React.Component { // 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 + // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); @@ -359,17 +359,15 @@ export class TabDocView extends React.Component { // prettier-ignore switch (whereFields[0]) { case undefined: - case OpenWhere.inPlace: { - if (this.layoutDoc?.isInPlaceContainer) { - const inPlaceView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; - const data = inPlaceView?.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)]; - if (inPlaceView && (!data || data instanceof List)) { - inPlaceView.layoutDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); - return true; - } - } - } - case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location); + case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) { + const lightboxView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; + const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)]; + if (lightboxView && (!data || data instanceof List)) { + lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List([doc]); + return true; + } + } + return LightboxView.AddDocTab(doc, location); case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 78804b070..be8a72369 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1414,8 +1414,8 @@ export class CollectionFreeFormView extends CollectionSubView(doc instanceof Doc ? [doc] : doc); return true; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index beb0f8e3a..8257c9c49 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -72,7 +72,6 @@ export enum ViewAdjustment { } export enum OpenWhere { - inPlace = 'inPlace', lightbox = 'lightbox', add = 'add', addLeft = 'add:left', @@ -109,6 +108,7 @@ export interface DocFocusOptions { effect?: Doc; // animation effect for focus noSelect?: boolean; // whether target should be selected after focusing playAudio?: boolean; // whether to play audio annotation on focus + openInLightbox?: boolean; // whether to open target in lightbox or just focus on it zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections toggleTarget?: boolean; // whether to toggle target on and off originatingDoc?: Doc; // document that triggered the focus @@ -123,6 +123,7 @@ export interface DocComponentView { scrollPreview?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus scrollFocus?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; + addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox 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. @@ -640,7 +641,8 @@ export class DocumentViewInternal extends DocComponent (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) { - UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap'); + //UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap'); + UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } @@ -870,13 +872,16 @@ export class DocumentViewInternal extends DocComponent { + makeIntoPortal = () => { const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document); if (!portalLink) { - const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }); - DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, 'portal to:portal from'); + DocUtils.MakeLink( + { doc: this.props.Document }, + { doc: Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }) }, + 'portal to:portal from' + ); } - this.Document.followLinkLocation = OpenWhere.inPlace; + this.Document.followLinkLocation = OpenWhere.lightbox; this.Document.followLinkZoom = true; this.Document._isLinkButton = true; }; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 039f11cd1..c182dfd19 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -331,7 +331,6 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (doc.lat !== undefined && doc.lng !== undefined) { const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng); - doc.onClickBehavior = 'enterPortal'; if (existingMarker) { Doc.AddDocToList(existingMarker, 'data', doc); } else { diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 10324449f..5b489a96c 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -84,11 +84,11 @@ export const documentSchema = createSchema({ onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, ) + followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, lightbox, default, ) hideLinkButton: 'boolean', // whether the blue link counter button should be hidden hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints. - isInPlaceContainer: 'boolean', // whether the marked object will display addDocTab() calls that target "inPlace" destinations + isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked layers: listSpec('string'), // which layers the document is part of _lockedPosition: 'boolean', // whether the document can be moved (dragged) -- cgit v1.2.3-70-g09d2 From 0c38e4dc096d6abf82ef11286616856b7119c6e1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 5 Mar 2023 21:24:09 -0500 Subject: replace jumpToDocument with showDocument. restructure code to get rid of scrollFocus by adding getView() and fixing focus() and restoreTargetView --- src/Utils.ts | 27 +++ src/client/documents/Documents.ts | 2 +- src/client/util/DocumentManager.ts | 235 +++++++-------------- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/util/LinkFollower.ts | 56 +---- src/client/util/SharingManager.tsx | 2 +- src/client/views/InkingStroke.tsx | 8 +- src/client/views/LightboxView.tsx | 4 +- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesButtons.tsx | 4 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/SidebarAnnos.tsx | 2 +- .../views/collections/CollectionNoteTakingView.tsx | 15 +- .../collections/CollectionStackedTimeline.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 13 +- .../views/collections/CollectionTimeView.tsx | 6 +- src/client/views/collections/TabDocView.tsx | 10 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 151 ++++--------- .../collections/collectionFreeForm/MarqueeView.tsx | 3 +- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../CollectionMulticolumnView.tsx | 3 +- .../CollectionMultirowView.tsx | 1 - .../collectionSchema/CollectionSchemaCells.tsx | 2 +- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 7 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 7 +- src/client/views/nodes/ComparisonBox.tsx | 19 +- src/client/views/nodes/DocumentView.tsx | 66 ++---- src/client/views/nodes/FilterBox.tsx | 2 +- src/client/views/nodes/FunctionPlotBox.tsx | 20 +- src/client/views/nodes/ImageBox.tsx | 7 +- src/client/views/nodes/LinkDocPreview.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 24 +-- src/client/views/nodes/VideoBox.tsx | 35 +-- src/client/views/nodes/WebBox.tsx | 82 ++++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 28 +-- src/client/views/nodes/trails/PresBox.scss | 2 +- src/client/views/nodes/trails/PresBox.tsx | 160 +++++++------- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 9 +- src/client/views/search/SearchBox.tsx | 2 +- src/fields/ScriptField.ts | 7 +- src/fields/util.ts | 6 +- 43 files changed, 410 insertions(+), 635 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index 22c8cb902..ae1478943 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,6 +1,7 @@ import v4 = require('uuid/v4'); import v5 = require('uuid/v5'); import { ColorState } from 'react-color'; +import * as rp from 'request-promise'; import { Socket } from 'socket.io'; import { Colors } from './client/views/global/globalEnums'; import { Message } from './server/Message'; @@ -34,6 +35,31 @@ export namespace Utils { return v5(seed, v5.URL); } + /** + * Uploads an image buffer to the server and stores with specified filename. by default the image + * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large) + * @param imageUri the bytes of the image + * @param returnedFilename the base filename to store the image on the server + * @param nosuffix optionally suppress creating multiple resolution images + */ + export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) { + try { + const posting = Utils.prepend('/uploadURI'); + const returnedUri = await rp.post(posting, { + body: { + uri: imageUri, + name: returnedFilename, + nosuffix, + replaceRootFilename, + }, + json: true, + }); + return returnedUri; + } catch (e) { + console.log('VideoBox :' + e); + } + } + export function GetScreenTransform(ele?: HTMLElement): { scale: number; translateX: number; translateY: number } { if (!ele) { return { scale: 1, translateX: 1, translateY: 1 }; @@ -218,6 +244,7 @@ export namespace Utils { } export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) { + if (!targetHgt) return targetY; // if there's no height, then assume that if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) { return Math.ceil(targetY + minSpacing + targetHgt - contextHgt); } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ebd06dbe2..98469a2f9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1319,7 +1319,7 @@ export namespace DocUtils { } export function DefaultFocus(doc: Doc, options: DocFocusOptions) { - options?.afterFocus?.(false); + return undefined; } export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0d10bed43..1a38e9aff 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,17 +1,22 @@ +import { loadAsync } from 'jszip'; import { action, observable, ObservableSet, runInAction } from 'mobx'; import { AnimationSym, Doc, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../fields/Types'; import { AudioField } from '../../fields/URLField'; -import { returnFalse } from '../../Utils'; -import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; +import { emptyFunction } from '../../Utils'; +import { CollectionViewType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { CollectionView } from '../views/collections/CollectionView'; +import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; -import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { MainView } from '../views/MainView'; +import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; +import { PresBox } from '../views/nodes/trails'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; const { Howl } = require('howler'); @@ -165,12 +170,12 @@ export class DocumentManager { public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { const views: DocumentView[] = []; Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view)); - return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); + return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined); }; public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc); const views = this.getDocumentViews(toFind); //.filter(view => view.rootDoc !== originatingDoc); - return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); + return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined); }; public getDocumentViews(toFindIn: Doc): DocumentView[] { const toFind = @@ -198,7 +203,7 @@ export class DocumentManager { static GetContextPath(doc: Opt, includeExistingViews?: boolean) { if (!doc) return []; - const srcContext = Cast(doc.context, Doc, null) ?? Cast(Cast(doc.annotationOn, Doc, null)?.context, Doc, null); + const srcContext = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); var containerDocContext = srcContext ? [srcContext, doc] : [doc]; while ( containerDocContext.length && @@ -235,171 +240,91 @@ export class DocumentManager { CollectionDockingView.AddSplit(doc, OpenWhereMod.right); finished?.(); }; - public jumpToDocument = ( + + // shows a documentView by: + // traverses down through the viewPath of contexts to the view: + // focusing on each context + public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => { + const docViewPath = targetDocView.docViewPath.slice(); + let rootContextView = docViewPath.shift(); + return rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })); + }; + + // shows a document by first: + // traversing down through the contexts that contain target until an existing view is found + // if no container view is found, create one by: opening an existing tab that has the top-level view, or showing the top-level context in the lightbox. + // once a containing view is found, it then traverses back down through the contexts to the target document by: + // focusing on each context + // and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration + public showDocument = async ( targetDoc: Doc, // document to display options: DocFocusOptions, // options for how to navigate to target - createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist - docContextPath: Doc[], // context to load that should contain the target finished?: () => void - ): void => { - const originalTarget = options.originalTarget ?? targetDoc; - const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc); - const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); - const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field - var wasHidden = resolvedTarget.hidden; - if (wasHidden) { - runInAction(() => { - resolvedTarget.hidden = false; // if the target is hidden, un-hide it here. - docView?.props.bringToFront(resolvedTarget); - }); - } - const focusAndFinish = action((didFocus: boolean) => { - const finalTargetDoc = resolvedTarget; - if (options.toggleTarget) { - if (!didFocus && !wasHidden) { - // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal - finalTargetDoc.hidden = !finalTargetDoc.hidden; - } - } else { - finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); - !options.noSelect && docView?.select(false); - } - if (targetDoc.textHtml && options.zoomTextSelections) { - const containerView = DocumentManager.Instance.getFirstDocumentView(finalTargetDoc); - if (containerView) { - containerView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect)); - containerView.textHtmlOverlay = StrCast(targetDoc.textHtml); - DocumentManager._overlayViews.add(containerView); - if (Doc.UnhighlightTimer) { - Doc.AddUnHighlightWatcher(() => { - DocumentManager.removeOverlayViews(); - containerView.htmlOverlayEffect = ''; - }); - } else setTimeout(() => (containerView.htmlOverlayEffect = '')); - } - } - finished?.(); + ) => { + const docContextPath = DocumentManager.GetContextPath(targetDoc, true); + let rootContextView = await new Promise(res => { + const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc)); + if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!); + docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere); + this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); }); - const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && this.getFirstDocumentView(annotatedDoc); - if (annoContainerView) { - if (annoContainerView.props.Document.layoutKey === 'layout_icon') { - return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, originalTarget, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30); - } - if (!docView && targetDoc.type !== DocumentType.MARKER) { - annoContainerView.focus(targetDoc, options); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below - } - } - const contextDoc = docContextPath.length ? docContextPath[0] : undefined; - const remainingDocContext = docContextPath.length ? docContextPath.slice(1) : []; - const targetDocContext = contextDoc || annotatedDoc; - const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above - const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; - if (focusView) { - // if (focusView.rootDoc === originalTarget) { - if (!options.noSelect) Doc.linkFollowHighlight(focusView.rootDoc, undefined, options.effect); //TODO:glr make this a setting in PresBox - else { - Doc.linkFollowHighlight(focusView.rootDoc, undefined, options.effect); //TODO:glr make this a setting in PresBox - focusView.rootDoc[AnimationSym] = options.effect; - if (Doc.UnhighlightTimer) { - Doc.AddUnHighlightWatcher(action(() => (focusView.rootDoc[AnimationSym] = undefined))); - } - } - //} - if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); - const doFocus = (forceDidFocus: boolean) => { - if (options.openInLightbox || (!options.originatingDoc?.followLinkLocation && DocCast(focusView.rootDoc.context)?._isLightbox)) { - focusView.props.addDocTab(targetDoc, OpenWhere.lightbox); - focusAndFinish(true); - } else { - focusView.focus(originalTarget, { - ...options, - originalTarget, - afterFocus: (didFocus: boolean) => - new Promise(res => { - focusAndFinish(forceDidFocus || didFocus); - res(ViewAdjustment.doNothing); - }), - }); - } - }; - if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) { - focusView.iconify(() => doFocus(true)); - } else { - doFocus(false); - } - } else { - if (!targetDocContext) { - // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default - createViewFunc(Doc.BrushDoc(targetDoc), () => focusAndFinish(true)); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); - } else { - // otherwise try to get a view of the context of the target - if (targetDocContextView) { - // we found a context view and aren't forced to create a new one ... focus on the context first.. - wasHidden = wasHidden || targetDocContextView.rootDoc.hidden; - targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden + docContextPath.shift(); + const childViewIterator = async () => { + const innerDoc = docContextPath.shift(); + return { viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.unrendered ? (await rootContextView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined }; + }; + const target = await this.focusViewsInPath(rootContextView, options, childViewIterator); + this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc); - if (targetDocContext.layoutKey === 'layout_icon') { - return targetDocContextView.iconify( - () => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed? */ }, createViewFunc, docContextPath, finished)), - 30 - ); - } + finished?.(); + }; - const contextFocusTime = options.zoomTime ? options.zoomTime / 2 : 500; - const remainingFocustime = options.zoomTime ? options.zoomTime - contextFocusTime : undefined; - targetDocContextView.setViewTransition('transform', contextFocusTime); - // this makes focusing on contexts run in parallel -- jutmp to document below makes them run sequentially - this.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished)); - targetDocContextView.props.focus(targetDocContextView.rootDoc, { - ...options, - zoomTime: contextFocusTime, - // originalTarget, // needed? - afterFocus: async () => { - // now find the target document within the context - if (targetDoc._timecodeToShow) { - // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; - targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow; - finished?.(); - } else { - // otherwise, just look for the target document in this context view now that we've focused the context view - if (this.getFirstDocumentView(resolvedTarget)) { - // test again for the target view snce we presumably created the context above by focusing on it - this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished); - } else if (targetDoc.layout) { - // there will no layout for a TEXTANCHOR type document - createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target - } - } - return ViewAdjustment.doNothing; - }, - }); - } else { - if (docContextPath.length && docContextPath[0]?.layoutKey === 'layout_icon') { - Doc.deiconifyView(docContextPath[0]); - this.jumpToDocument(targetDoc, options, createViewFunc, docContextPath, finished); - } else { - // there's no context view so we need to create one first and try again when that finishes - createViewFunc( - targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => this.jumpToDocument(targetDoc, { ...options }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), remainingDocContext, finished) - ); - } - } - } + focusViewsInPath = async (docView: DocumentView, options: DocFocusOptions, iterator: () => Promise<{ viewSpec: Opt; childDocView: Opt }>) => { + let contextView: DocumentView | undefined; // view containing context that contains target + while (true) { + docView.rootDoc.layoutKey === 'layout_icon' ? await new Promise(res => docView.iconify(res)) : undefined; + docView.props.focus(docView.rootDoc, options); // focus the view within its container + const { childDocView, viewSpec } = await iterator(); + if (!childDocView) return { viewSpec: viewSpec ?? docView.rootDoc, docView, contextView }; + contextView = docView; + docView = childDocView; } }; + + @action + restoreDocView(viewSpec: Opt, docView: DocumentView, options: DocFocusOptions, contextView: Opt, targetDoc: Doc) { + if (viewSpec && docView) { + if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options); + PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500); + Doc.linkFollowHighlight(docView.rootDoc, undefined, options.effect); + if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc); + if (options.toggleTarget) docView.rootDoc.hidden = !docView.rootDoc.hidden; + if (options.effect) docView.rootDoc[AnimationSym] = options.effect; + + if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.textHtml) { + // if the docView is a text anchor, the contextView is the PDF/Web/Text doc + contextView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect)); + contextView.textHtmlOverlay = StrCast(targetDoc.textHtml); + DocumentManager._overlayViews.add(contextView); + } + Doc.AddUnHighlightWatcher(() => { + docView.rootDoc[AnimationSym] = undefined; + DocumentManager.removeOverlayViews(); + contextView && (contextView.htmlOverlayEffect = ''); + }); + } + } } export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc); const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView); - if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) { - dv.props.focus(dv.props.Document, { willPanZoom: true }); - Doc.linkFollowHighlight(dv?.props.Document, false); + if (dv) { + DocumentManager.Instance.showDocumentView(dv, { willZoomCentered: true }); } else { const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; - CollectionDockingView.AddSplit(Doc.BestAlias(showDoc), OpenWhereMod.right) && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {})); + DocumentManager.Instance.showDocument(Doc.BestAlias(showDoc), { openLocation: OpenWhere.addRight }, () => DocumentManager.Instance.showDocument(doc, { willZoomCentered: true })); } } ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 7f0c8a3e8..559958c2b 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -168,7 +168,7 @@ export class DirectoryImportBox extends React.Component { await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); - DocumentManager.Instance.jumpToDocument(importContainer, { willPanZoom: true }, undefined, []); + DocumentManager.Instance.showDocument(importContainer, { willZoomCentered: true }); } runInAction(() => { diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index b40c7bdf9..c9a178db7 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,15 +1,11 @@ import { action, runInAction } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { List } from '../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; -import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { DocumentDecorations } from '../views/DocumentDecorations'; -import { LightboxView } from '../views/LightboxView'; -import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/nodes/DocumentView'; import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; -import { LinkManager } from './LinkManager'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; @@ -32,46 +28,10 @@ export class LinkFollower { // depending on the followLinkLocation property of the source (or the link itself as a fallback); public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => { const batch = UndoManager.StartBatch('follow link click'); - // open up target if it's not already in view ... - const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { - const createTabForTarget = (didFocus: boolean) => - new Promise(res => { - const where = LightboxView.LightboxDoc ? OpenWhere.lightbox : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere); - const lightbox = where === OpenWhere.lightbox && DocumentManager.GetContextPath(doc).find(container => container.isLightbox && DocumentManager.Instance.getDocumentView(container)); - const addDocTab = lightbox ? DocumentManager.Instance.getDocumentView(lightbox)?.ComponentView?.addDocTab : undefined; - (addDocTab ?? docViewProps.addDocTab)(doc, where); - setTimeout(() => { - const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. - if (targDocView) { - targDocView.props.focus(doc, { - willPan: true, - willPanZoom: BoolCast(sourceDoc.followLinkZoom, false), - afterFocus: (didFocus: boolean) => { - finished?.(); - res(ViewAdjustment.resetView); - return new Promise(res2 => res2(ViewAdjustment.doNothing)); - }, - }); - } else { - finished?.(); - res(BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); - } - }, 100); - }); - - if (!sourceDoc.followLinkZoom) { - createTabForTarget(false); - } else { - // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target - docViewProps.focus(sourceDoc, { willPan: true, willPanZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomTime: 1000, zoomScale: 1, afterFocus: createTabForTarget }); - } - }; runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value LinkFollower.traverseLink( linkDoc, sourceDoc, - createViewFunc, - docViewProps.ContainingCollectionDoc, action(() => { batch.end(); Doc.AddUnHighlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false))); @@ -80,7 +40,7 @@ export class LinkFollower { ); }; - public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { + public static traverseLink(link: Opt, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(sourceDoc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 @@ -104,15 +64,17 @@ export class LinkFollower { ) as Doc; if (target) { const doFollow = (canToggle?: boolean) => { + const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle); const options: DocFocusOptions = { playAudio: BoolCast(sourceDoc.followLinkAudio), - toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle), + toggleTarget, + noSelect: true, willPan: true, - willPanZoom: BoolCast(sourceDoc.followLinkZoom, false), + willZoomCentered: BoolCast(sourceDoc.followLinkZoom, false), zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500), zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, - openInLightbox: sourceDoc.followLinkLocation === OpenWhere.lightbox, + openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox), effect: sourceDoc, originatingDoc: sourceDoc, zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText), @@ -125,9 +87,7 @@ export class LinkFollower { } finished?.(); } else { - const containerDocContext = DocumentManager.GetContextPath(target); - const targetContexts = !sourceDoc.followLinkToOuterContext && containerDocContext.length ? [containerDocContext.lastElement()] : containerDocContext; - DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.lightbox), finished), targetContexts, allFinished); + DocumentManager.Instance.showDocument(target, options, allFinished); } }; let movedTarget = false; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 36095700c..3c05af4bb 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -375,7 +375,7 @@ export class SharingManager extends React.Component<{}> { onClick={() => { let context: Opt; if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) { - DocumentManager.Instance.jumpToDocument(this.targetDoc, { willPanZoom: true }, undefined, [context.props.Document]); + DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true }); } }} onPointerEnter={action(() => { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 73e46a553..4f08a8e22 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -91,18 +91,12 @@ export class InkingStroke extends ViewBoxBaseComponent() { if (anchor) { anchor.backgroundColor = 'transparent'; // /* addAsAnnotation &&*/ this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, inkable: true } }, this.rootDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), inkable: true } }, this.rootDoc); return anchor; } return this.rootDoc; }; - scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { - if (this._subContentView) return this._subContentView?.scrollFocus?.(docView, anchor, options); - - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; - }; /** * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 8d60da0dd..2567d44bb 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -160,7 +160,7 @@ export class LightboxView extends React.Component { if (targetDocView && target) { const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); - targetDocView.focus(target, { originalTarget: target, willPanZoom: true, zoomScale: 0.9 }); + DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target }); } else { if (!target && LightboxView.path.length) { @@ -195,7 +195,7 @@ export class LightboxView extends React.Component { const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); if (docView) { LightboxView._docTarget = target; - target && docView.focus(target, { willPanZoom: true, zoomScale: 0.9 }); + target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); } else { LightboxView.SetLightboxDoc(doc, target); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 95c0f3755..e1ba5943d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -205,7 +205,7 @@ export class MainView extends React.Component { document.addEventListener('dash', (e: any) => { // event used by chrome plugin to tell Dash which document to focus on const id = FormattedTextBox.GetDocFromUrl(e.detail); - DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, { willPan: false }, undefined, []) : null)); + DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null)); }); document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener); this.initEventListeners(); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index a5c01490f..ac1b66013 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -16,11 +16,11 @@ import { undoBatch } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; -import { VideoBox } from './nodes/VideoBox'; import { pasteImageBitmap } from './nodes/WebBoxRenderer'; import './PropertiesButtons.scss'; import React = require('react'); import { LinkManager } from '../util/LinkManager'; +import { Utils } from '../../Utils'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -222,7 +222,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { setTimeout(() => pasteImageBitmap((data_url: any, error: any) => { error && console.log(error); - data_url && VideoBox.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename))); + data_url && Utils.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename))); }) ); } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 5105cc615..e750dc43a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -335,7 +335,7 @@ export class PropertiesView extends React.Component { NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined} PanelWidth={panelWidth} PanelHeight={panelHeight} - focus={returnFalse} + focus={emptyFunction} ScreenToLocalTransform={this.getTransform} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index b9af28413..74ea624a6 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -139,7 +139,7 @@ export class SidebarAnnos extends React.Component { return target; }; makeDocUnfiltered = (doc: Doc) => { - if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) { + if (DocListCast(this.props.rootDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.unrendered ? DocCast(doc.annotationOn) : doc, anno))) { if (this.props.layoutDoc[this.filtersKey]) { this.props.layoutDoc[this.filtersKey] = new List(); } diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 0e1601f35..6035871cd 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -3,7 +3,7 @@ import { CursorProperty } from 'csstype'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; -import { Copy, Id } from '../../../fields/FieldSymbols'; +import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -11,7 +11,6 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -19,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -187,20 +186,16 @@ export class CollectionNoteTakingView extends CollectionSubView() { // let's dive in and get the actual document we want to drag/move around focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); - let focusSpeed = 0; const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + let focusSpeed = options.zoomTime ?? 500; + smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + return focusSpeed; } } - const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); - this.props.focus(this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), - }); }; styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 6fd4c8c0a..572fbfec2 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -765,7 +765,7 @@ class StackedTimelineAnchor extends React.Component // starting the drag event for anchor resizing onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - this.props._timeline?.setPointerCapture(e.pointerId); + //this.props._timeline?.setPointerCapture(e.pointerId); const newTime = (e: PointerEvent) => { const rect = (e.target as any).getBoundingClientRect(); return this.props.toTimeline(e.clientX - rect.x, rect.width); @@ -793,7 +793,7 @@ class StackedTimelineAnchor extends React.Component }, e => { this.props.setTime(newTime(e)); - this.props._timeline?.releasePointerCapture(e.pointerId); + // this.props._timeline?.releasePointerCapture(e.pointerId); undo?.end(); }, emptyFunction diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 6314b4529..4805a748b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -22,7 +22,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -250,20 +250,17 @@ export class CollectionStackingView extends CollectionSubView { Doc.BrushDoc(doc); - let focusSpeed = 0; const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + let focusSpeed = options.zoomTime ?? 500; + smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + return focusSpeed; } } - const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; - this.props.focus(this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), - }); + return undefined; }; styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 437b22040..89b2fbfe3 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -66,11 +66,11 @@ export class CollectionTimeView extends CollectionSubView() { }; @action - scrollPreview = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { + scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => { // if in preview, then override document's fields with view spec - this._focusFilters = StrListCast(anchor.presPinDocFilters); + this._focusFilters = StrListCast(anchor.presDocFilters); this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); - this._focusPivotField = StrCast(anchor.presPinPivotField); + this._focusPivotField = StrCast(anchor.presPivotField); return undefined; }; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 042c2b71b..99283996e 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -305,7 +305,7 @@ export class TabDocView extends React.Component { const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); - setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), { willPan: true }, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 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 } @@ -388,13 +388,17 @@ export class TabDocView extends React.Component { getCurrentFrame = () => { return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame); }; + static Activate = (tabDoc: Doc) => { + const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc); + tab?.header.parent.setActiveContentItem(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) + return tab !== undefined; + }; @action focusFunc = (doc: Doc, options: DocFocusOptions) => { - 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) } + return undefined; }; active = () => this._isActive; @observable _forceInvalidateScreenToLocal = 0; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 79832eb39..63b566f28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -15,13 +15,12 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnTransparent, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { HistoryUtil } from '../../../util/History'; import { InteractionUtils } from '../../../util/InteractionUtils'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -33,15 +32,15 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; +import { DocumentDecorations } from '../../DocumentDecorations'; import { GestureOverlay } from '../../GestureOverlay'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../../nodes/trails/PresBox'; -import { VideoBox } from '../../nodes/VideoBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; @@ -52,8 +51,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); -import { DocumentDecorations } from '../../DocumentDecorations'; -import { PresEffect } from '../../nodes/trails'; export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -300,6 +297,30 @@ export class CollectionFreeFormView extends CollectionSubView= -1e-4 && curTime <= endTime); } + focus = (anchor: Doc, options: DocFocusOptions) => { + const xfToCollection = options?.docTransform ?? Transform.Identity(); + const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; + const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); + const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined); + + // focus on the document in the collection + const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); + // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... + if (didMove) { + const focusTime = options?.instant ? 0 : options.zoomTime ?? 500; + options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); + this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + return focusTime; + } + }; + + getView = async (doc: Doc): Promise> => { + return new Promise>(res => { + const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + findDoc(dv => res(dv)); + }); + }; + @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false; @@ -1199,79 +1220,6 @@ export class CollectionFreeFormView extends CollectionSubView(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset } - _focusCount = 0; - focusDocument = (doc: Doc, options: DocFocusOptions) => { - const state = HistoryUtil.getState(); - - // TODO This technically isn't correct if type !== "doc", as - // currently nothing is done, but we should probably push a new state - if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) { - const init = state.initializers![this.Document[Id]]; - if (!init) { - state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) }; - HistoryUtil.pushState(state); - } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) { - init.panX = NumCast(this.Document._panX); - init.panY = NumCast(this.Document._panY); - HistoryUtil.pushState(state); - } - } - // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) { - // SelectionManager.DeselectAll(); - // } - if (this.props.getScrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined) { - this.props.focus(doc, options); - } else { - const xfToCollection = options?.docTransform ?? Transform.Identity(); - const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willPanZoom ? this.Document[this.scaleFieldKey] : undefined }; - const newState = HistoryUtil.getState(); - const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); - const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willPanZoom) ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willPanZoom ? options?.zoomScale || 0.75 : undefined); - if (!cantTransform) { - // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection - newState.initializers![this.Document[Id]] = { panX, panY }; - HistoryUtil.pushState(newState); - } - // focus on the document in the collection - const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); - const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0; - // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... - if (didMove) { - options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); - this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow - } - - const focusCount = ++this._focusCount; - const startTime = Date.now(); - // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection - const endFocus = async (moved: boolean) => { - doc.hidden && Doc.UnHighlightDoc(doc); - const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing; - if (resetView) { - const restoreState = savedState; - if (typeof restoreState !== 'boolean') { - this.Document._panX = restoreState.panX; - this.Document._panY = restoreState.panY; - this.Document[this.scaleFieldKey] = restoreState.scale; - } - } - this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0)); - return resetView; - }; - const xf = !cantTransform - ? Transform.Identity() - : this.props.isAnnotationOverlay - ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) - : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); - - this.props.focus(!cantTransform ? this.rootDoc : doc, { - ...options, - docTransform: xf, - afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), - }); - } - }; - calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const layoutdoc = Doc.Layout(doc); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); @@ -1371,7 +1319,7 @@ export class CollectionFreeFormView extends CollectionSubView { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - if (options.preview) { - this._focusFilters = StrListCast(anchor.presPinDocFilters); - this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); - return undefined; - } - return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; - }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc }); @@ -1744,7 +1681,7 @@ export class CollectionFreeFormView extends CollectionSubView { - const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); + const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { @@ -2303,21 +2240,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component { - if (!didMove) { - const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; - const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview - await ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5); - } - return ViewAdjustment.doNothing; - }, - }); - Doc.linkFollowHighlight(dv?.props.Document, false); + if ( + dv.props.focus(dv.props.Document, { + willZoomCentered: true, + zoomScale: 0.8, + zoomTime: browseTransitionTime, + }) === undefined + ) { + const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; + const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime); + Doc.linkFollowHighlight(dv?.props.Document, false); + } } ScriptingGlobals.add(CollectionBrowseClick); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bd33c4d80..a73627165 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { OpenWhere } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; import { SubCollectionViewProps } from '../CollectionSubView'; @@ -167,7 +166,7 @@ export class MarqueeView extends React.Component { error && console.log(error); data && - VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { + Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { this.props.Document['thumb-frozen'] = new ImageField(returnedfilename); }); }) diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index b9cda7130..d54e8ce98 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -152,7 +152,7 @@ export class CollectionLinearView extends CollectionSubView() { Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( <> - DocumentManager.Instance.jumpToDocument(clip.rootDoc, { willPanZoom: true }, undefined, [])}> + DocumentManager.Instance.showDocument(clip.rootDoc, { willZoomCentered: true })}> {clip.rootDoc.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')} clip.ComponentView?.TogglePause?.()} />{' '} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b88da5191..88d045fa7 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -234,7 +234,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { @@ -260,7 +259,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { hideResizeHandles={this.props.childHideResizeHandles?.()} hideDecorationTitle={this.props.childHideDecorationTitle?.()} fitContentsToBox={this.props.fitContentsToBox} - focus={this.focusDocument} + focus={this.props.focus} docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 407deaabd..f18917bef 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -234,7 +234,6 @@ export class CollectionMultirowView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index 97a6c5c18..18ddd881b 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -214,7 +214,7 @@ export class CollectionSchemaCell extends React.Component { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc)); + DocumentManager.Instance.showDocument(this._rowDoc, { willPan: true }, () => this.props.setPreviewDoc(this._rowDoc)); } }; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a2a2255e6..cdac91a62 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -139,7 +139,7 @@ export class LinkMenuItem extends React.Component { ? Cast(this.props.linkDoc.anchor12, Doc, null) : undefined; - if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, { instant: true }); + if (focusDoc) this.props.docView.props.focus(focusDoc, { instant: true }); LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 3966aecf6..4c26468b9 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -71,12 +71,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val))); CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode))); - CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (Doc.GetProto(doc)[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', Doc.GetProto(doc), currTimecode))); - const targetDoc = doc.type === DocumentType.RTF ? Doc.GetProto(doc) : doc; // data fields, like rtf 'text' exist on the data doc, so - doc !== targetDoc && (targetDoc.context = doc.context); // the computed fields don't see the layout doc -- need to copy the context to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!) + CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode))); + const targetDoc = doc; // data fields, like rtf 'text' exist on the data doc, so + //doc !== targetDoc && (targetDoc.context = doc.context); // the computed fields don't see the layout doc -- need to copy the context to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!) targetDoc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0'); targetDoc.dataTransition = 'inherit'; }); @@ -190,7 +190,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.sizeProvider?.width || this.props.PanelWidth?.(); panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.(); screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); - focusDoc = (doc: Doc) => this.props.focus(doc, {}); returnThis = () => this; render() { TraceMobx(); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index dd03b9b99..ecffe6c4f 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; @@ -12,6 +13,7 @@ import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; +import { PinProps, PresBox } from './trails'; import React = require('react'); @observer @@ -24,6 +26,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { this._disposers[disposerId]?.(); if (ele) { @@ -72,6 +78,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { + const anchor = Docs.Create.ImageanchorDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); + if (anchor) { + if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; + /* addAsAnnotation &&*/ this.addDocument(anchor); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.rootDoc); + return anchor; + } + return this.rootDoc; + }; + @undoBatch clearDoc = (e: React.MouseEvent, fieldKey: string) => { e.stopPropagation; // prevent click event action (slider movement) in registerSliding @@ -103,7 +120,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { - whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); + //whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); }} {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} isContentActive={returnFalse} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f2aaa5cbc..dcccd1143 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -66,11 +66,6 @@ declare class MediaRecorder { constructor(e: any); } -export enum ViewAdjustment { - resetView = 1, - doNothing = 0, -} - export enum OpenWhere { lightbox = 'lightbox', add = 'add', @@ -96,33 +91,30 @@ export enum OpenWhereMod { } export interface DocFocusOptions { - originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab willPan?: boolean; // determines whether to pan to target document - willPanZoom?: boolean; // determines whether to zoom in on target document + willZoomCentered?: boolean; // determines whether to zoom in on target document zoomScale?: number; // percent of containing frame to zoom into document zoomTime?: number; - afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom) preview?: boolean; // whether changes should be previewed by the componentView or written to the document effect?: Doc; // animation effect for focus noSelect?: boolean; // whether target should be selected after focusing playAudio?: boolean; // whether to play audio annotation on focus - openInLightbox?: boolean; // whether to open target in lightbox or just focus on it + openLocation?: string; // where to open a missing document zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections toggleTarget?: boolean; // whether to toggle target on and off originatingDoc?: Doc; // document that triggered the focus easeFunc?: 'linear' | 'ease'; // transition method for scrolling } -export type DocAfterFocusFunc = (notFocused: boolean) => Promise; -export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void; +export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => 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) - scrollPreview?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus - scrollFocus?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus + scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; + getView?: (doc: Doc) => Promise>; // returns a nested DocumentView for the specified doc or undefined addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox 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 @@ -131,10 +123,10 @@ export interface DocComponentView { getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) playFrom?: (time: number, endTime?: number) => void; - Pause?: () => void; - IsPlaying?: () => boolean; - TogglePause?: (keep?: boolean) => void; - setFocus?: () => void; + Pause?: () => void; // pause a media document (eg, audio/video) + IsPlaying?: () => boolean; // is a media document playing + TogglePause?: (keep?: boolean) => void; // toggle media document playing state + setFocus?: () => void; // sets input focus to the componentView componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; incrementalRendering?: () => void; fieldKey?: string; @@ -583,32 +575,15 @@ export class DocumentViewInternal extends DocComponent { - LightboxView.SetCookie(StrCast(anchor['cookies-set'])); - - // Restore viewing specification of target by reading them out of the anchor and applying to the target doc. - const presItem = DocCast(options.originatingDoc); // if originating doc was a presItem, then anchor will be its proto. use presItem instead - const targetMatch = Doc.AreProtosEqual(anchor, this.rootDoc) || (DocCast(anchor)?.unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) ? true : false; - const scrollFocus = - (LinkDocPreview.LinkInfo ? this._componentView?.scrollPreview : undefined) ?? - this._componentView?.scrollFocus ?? - ((docView: DocumentView, anchor: Doc, options: DocFocusOptions) => (focusSpeed => (PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined))(options.instant ? 0 : options.zoomTime ?? 500)); - const focusSpeed = targetMatch && scrollFocus?.(this.props.DocumentView(), presItem?.proto === anchor ? presItem : anchor, options); - - // FOCUS: navigate through the display hierarchy making sure the target is in view - const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; - const startTime = Date.now(); - this.props.focus(options?.docTransform ? anchor : this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => - new Promise(async res => - setTimeout( - async () => res(endFocus ? await endFocus(didFocus || focusSpeed !== undefined) : ViewAdjustment.doNothing), // - didFocus ? Math.max(0, (options.zoomTime ?? 500) - (Date.now() - startTime)) : 0 - ) - ), - }); + defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => { + const targetMatch = + Doc.AreProtosEqual(anchor, this.rootDoc) || // anchor is this document, so anchor's properties apply to this document + (DocCast(anchor)?.unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) // the anchor is an unrendered annotation on this document, so anchor properties applie to this document + ? true + : false; + return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; }; + onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { let stopPropagate = true; @@ -1201,7 +1176,7 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} @@ -1242,8 +1217,8 @@ export class DocumentViewInternal extends DocComponent Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) || Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) || - ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) || - ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc)) + ((link.anchor1 as Doc)?.unrendered && Doc.AreProtosEqual((link.anchor1 as Doc)?.annotationOn as Doc, this.rootDoc)) || + ((link.anchor2 as Doc)?.unrendered && Doc.AreProtosEqual((link.anchor2 as Doc)?.annotationOn as Doc, this.rootDoc)) ); } @computed get allLinks() { @@ -1721,7 +1696,6 @@ export class DocumentView extends React.Component { } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); - focus = (doc: Doc, options: DocFocusOptions) => this.docView?.focus(doc, options); getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.props.Document.type === DocumentType.PRES || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 78ba499ec..d41a4bb82 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -465,7 +465,7 @@ export class FilterBox extends ViewBoxBaseComponent() { isContentActive={returnTrue} whenChildContentsActiveChanged={returnFalse} treeViewHideTitle={true} - focus={returnFalse} + focus={emptyFunction} onCheckedClick={this.scriptField} treeViewHideHeaderFields={false} dontRegisterView={true} diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 8fa01c97a..75b4907cd 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -15,6 +15,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { DocFocusOptions, DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; +import { PinProps, PresBox } from './trails'; const EquationSchema = createSchema({}); @@ -37,23 +38,18 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent componentDidMount() { this.props.setContentView?.(this); reaction( - () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.dataDoc.xRange, this.dataDoc.yRange], + () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.rootDoc.xRange, this.rootDoc.yRange], () => this.createGraph() ); } - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); - anchor.xRange = new List(Array.from(this._plot.options.xAxis.domain)); - anchor.yRange = new List(Array.from(this._plot.options.yAxis.domain)); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.rootDoc); + anchor.presXRange = new List(Array.from(this._plot.options.xAxis.domain)); + anchor.presYRange = new List(Array.from(this._plot.options.yAxis.domain)); if (addAsAnnotation) this.addDocument(anchor); return anchor; }; - @action - scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => { - this.dataDoc.xRange = new List(Array.from(Cast(doc.xRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10])))); - this.dataDoc.yRange = new List(Array.from(Cast(doc.yRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9])))); - return 0; - }; createGraph = (ele?: HTMLDivElement) => { this._plotEle = ele || this._plotEle; const width = this.props.PanelWidth(); @@ -65,8 +61,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent target: '#' + this._plotEle.id, width, height, - xAxis: { domain: Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10]) }, - yAxis: { domain: Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9]) }, + xAxis: { domain: Cast(this.rootDoc.xRange, listSpec('number'), [-10, 10]) }, + yAxis: { domain: Cast(this.rootDoc.yRange, listSpec('number'), [-1, 9]) }, grid: true, data: fns.map(fn => ({ fn, diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 363cd1d94..c6d4cb694 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -17,6 +17,7 @@ import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_ser import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; +import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../../views/ContextMenu'; @@ -26,15 +27,13 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView'; +import { OpenWhere } from './DocumentView'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import { PinProps, PresBox } from './trails'; import React = require('react'); import Color = require('color'); -import { LinkDocPreview } from './LinkDocPreview'; -import { DocumentManager } from '../../util/DocumentManager'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 1eab06381..bbe9b4323 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -253,7 +253,7 @@ export class LinkDocPreview extends React.Component { { const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor, {}); + targetanchor && this._targetDoc !== targetanchor && r?.props.focus?.(targetanchor, {}); }} Document={this._targetDoc!} moveDocument={returnFalse} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 6b2f2246a..5f207cc0d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -11,6 +11,7 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; +import { DocumentManager } from '../../util/DocumentManager'; import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @@ -27,7 +28,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; import { PinProps, PresBox } from './trails'; -import { VideoBox } from './VideoBox'; import React = require('react'); @observer @@ -141,7 +141,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - VideoBox.convertDataUri(data_url, region[Id]).then(returnedfilename => + Utils.convertDataUri(data_url, region[Id]).then(returnedfilename => setTimeout( action(() => { croppingProto.data = new ImageField(returnedfilename); @@ -207,17 +207,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - let didToggle = false; - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { - this.toggleSidebar(options.preview); - didToggle = true; - } - if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1; + focus = (anchor: Doc, options: DocFocusOptions) => { this._initialScrollTarget = anchor; - PresBox.restoreTargetDocView(docView, anchor, options.zoomTime ?? 500); - return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.presPinViewScroll, NumCast(anchor.y)), options) ?? (didToggle ? 1 : undefined); + return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.presViewScroll)), options); + }; + + getView = async (doc: Doc) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); + return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt = undefined; if (this._pdfViewer?.selectionContent()) { @@ -296,7 +295,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._playing; - _keepCurrentlyPlaying = false; TogglePause = () => { if (!this._playing) this.Play(); else { @@ -369,7 +344,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY)); + Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY)); } }; @@ -439,6 +414,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + // return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + // }; + // extracts video thumbnails and saves them as field of doc getVideoThumbnails = () => { if (this.dataDoc.thumbnails !== undefined) return; @@ -455,7 +434,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent a.textInlineAnnotations); } @computed get webField() { - return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; + return Cast(this.rootDoc[this.props.fieldKey], WebField)?.url; } @computed get webThumb() { return ( @@ -163,7 +163,7 @@ export class WebBox extends ViewBoxAnnotatableComponent + Utils.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => setTimeout( action(() => { this.rootDoc.thumbLockout = false; @@ -188,10 +188,10 @@ export class WebBox extends ViewBoxAnnotatableComponent this._urlHash + '-annotations'; const reqdFuncs: { [key: string]: string } = {}; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) - reqdFuncs[this.fieldKey + '-annotations'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`; + reqdFuncs[this.fieldKey + '-annotations'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"])`; reqdFuncs[this.fieldKey + '-annotations-setter'] = `this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"] = value`; - reqdFuncs[this.fieldKey + '-sidebar'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`; - DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); + reqdFuncs[this.fieldKey + '-sidebar'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"])`; + DocUtils.AssignScripts(this.rootDoc, {}, reqdFuncs); }); reaction( () => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), @@ -294,32 +294,25 @@ export class WebBox extends ViewBoxAnnotatableComponent void) => (this._setBrushViewer = func); brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view); - focus = (doc: Doc, options: DocFocusOptions) => { - !doc.unrendered && this.props.DocumentView?.() && this.scrollFocus(this.props.DocumentView?.(), doc, {}); - this.props.focus(doc, options); - }; - scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { - if (this._url && StrCast(anchor.webUrl) !== this._url) this.submitURL(StrCast(anchor.webUrl), options.preview); - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { - this.toggleSidebar(options.preview); - } - if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1; + focus = (anchor: Doc, options: DocFocusOptions) => { if (anchor !== this.rootDoc && this._outerRef.current) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = !anchor[HeightSym]() - ? NumCast(anchor.y) - : Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight)); - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - if (scrollTo !== undefined && this._initialScroll === undefined) { - this.goTo(scrollTo, focusSpeed, options.easeFunc); - PresBox.restoreTargetDocView(docView, anchor, focusSpeed); - return focusSpeed; - } else if (!this._webPageHasBeenRendered || !this._scrollHeight || this._initialScroll !== undefined) { - this._initialScroll = scrollTo; - return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; + const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight)); + if (scrollTo !== undefined) { + if (this._initialScroll === undefined) { + this.goTo(scrollTo, options.zoomTime ?? 500, options.easeFunc); + } else { + this._initialScroll = scrollTo; + } } } - return undefined; + }; + + getView = async (doc: Doc) => { + if (this.rootDoc.layoutKey === 'layout_icon') this.props.DocumentView?.().iconify(); + if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl)); + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); + return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; sidebarAddDocTab = (doc: Doc, where: OpenWhere) => { @@ -545,14 +538,14 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string'), []); - const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []); + const future = Cast(this.rootDoc[this.fieldKey + '-future'], listSpec('string'), []); + const history = Cast(this.rootDoc[this.fieldKey + '-history'], listSpec('string'), []); if (checkAvailable) return future.length; runInAction(() => { if (future.length) { const curUrl = this._url; - this.dataDoc[this.fieldKey + '-history'] = new List([...history, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + this.rootDoc[this.fieldKey + '-history'] = new List([...history, this._url]); + this.rootDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); if (this._webUrl === this._url) { this._webUrl = curUrl; setTimeout(action(() => (this._webUrl = this._url))); @@ -566,15 +559,15 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string')); - const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []); + const future = Cast(this.rootDoc[this.fieldKey + '-future'], listSpec('string')); + const history = Cast(this.rootDoc[this.fieldKey + '-history'], listSpec('string'), []); if (checkAvailable) return history.length; runInAction(() => { if (history.length) { const curUrl = this._url; - if (future === undefined) this.dataDoc[this.fieldKey + '-future'] = new List([this._url]); - else this.dataDoc[this.fieldKey + '-future'] = new List([...future, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); + if (future === undefined) this.rootDoc[this.fieldKey + '-future'] = new List([this._url]); + else this.rootDoc[this.fieldKey + '-future'] = new List([...future, this._url]); + this.layoutDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); if (this._webUrl === this._url) { this._webUrl = curUrl; setTimeout(action(() => (this._webUrl = this._url))); @@ -601,11 +594,11 @@ export class WebBox extends ViewBoxAnnotatableComponent([...(history || []), url]); + this.rootDoc[this.fieldKey + '-history'] = new List([...(history || []), url]); this.layoutDoc._scrollTop = 0; if (this._webPageHasBeenRendered) { this.layoutDoc.thumb = undefined; @@ -616,7 +609,7 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />; @@ -849,7 +842,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); if (!nativeWidth) { - const defaultNativeWidth = this.dataDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym](); + const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym](); Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth); Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[HeightSym]() / this.Document[WidthSym]()) * defaultNativeWidth); nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); @@ -1006,6 +999,7 @@ export class WebBox extends ViewBoxAnnotatableComponent (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance?.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None ? 'all' : 'none'); render() { + setTimeout(() => DocListCast(this.rootDoc[this.annotationKey]).forEach(doc => (doc.webUrl = this._url))); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const scale = previewScale * (this.props.NativeDimScaling?.() || 1); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index dc7a11e6e..d857b150c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -230,16 +230,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (DocListCast(this.props.Document[this.props.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { - this.toggleSidebar(false); - return true; - } - return this.props.addDocTab(doc, where); - }; - getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - if (!pinProps) return this.rootDoc; + if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc; const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); this.addDocument(anchor); this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation); @@ -948,12 +940,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - let didToggle = false; - if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) { - this.toggleSidebar(options.preview); - didToggle = true; + getView = async (doc: Doc) => { + if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + !this.SidebarShown && this.toggleSidebar(false); + setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc)); } + return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + }; + focus = (textAnchor: Doc, options: DocFocusOptions) => { + const focusSpeed = options.zoomTime ?? 500; const textAnchorId = textAnchor[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -992,7 +987,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) { - !options.instant && (this._focusSpeed = 500); + !options.instant && (this._focusSpeed = focusSpeed); let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected @@ -1004,9 +999,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000)); } } - const finalFocusSpeed = this._didScroll ? this._focusSpeed : didToggle ? 1 : undefined; - PresBox.restoreTargetDocView(docView, textAnchor, finalFocusSpeed ?? 0); - return finalFocusSpeed; // if we actually scrolled, then return some focusSpeed }; // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss index fd202590e..eb91c82f3 100644 --- a/src/client/views/nodes/trails/PresBox.scss +++ b/src/client/views/nodes/trails/PresBox.scss @@ -12,7 +12,7 @@ height: 100%; min-height: 35px; letter-spacing: 2px; - overflow: hidden; + //overflow: hidden; transition: 0.7s opacity ease; .presBox-listCont { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index ab4822117..94962650a 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -46,6 +46,7 @@ export interface pinDataTypes { pivot?: boolean; temporal?: boolean; clippable?: boolean; + datarange?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; @@ -329,6 +330,7 @@ export class PresBox extends ViewBoxBaseComponent() { const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType); const clippable = [DocumentType.COMPARISON].includes(targetType); + const datarange = [DocumentType.FUNCPLOT].includes(targetType); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(targetType) && target?.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; @@ -350,23 +352,33 @@ export class PresBox extends ViewBoxBaseComponent() { if ( bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) || bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) || - bestTarget.rotation !== NumCast(activeItem.presRot, NumCast(bestTarget.rotation)) || + bestTarget.rotation !== NumCast(activeItem.presRotation, NumCast(bestTarget.rotation)) || bestTarget.width !== NumCast(activeItem.presWidth, NumCast(bestTarget.width)) || bestTarget.height !== NumCast(activeItem.presHeight, NumCast(bestTarget.height)) ) { bestTarget._dataTransition = `all ${transTime}ms`; bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); - bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation)); + bestTarget.rotation = NumCast(activeItem.presRotation, NumCast(bestTarget.rotation)); bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); changed = true; } } - if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presPinClipWidth !== undefined)) { - if (bestTarget._clipWidth !== activeItem.presPinClipWidth) { - bestTarget._clipWidth = activeItem.presPinClipWidth; + if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.presXRange !== undefined)) { + if (bestTarget.xRange !== activeItem.presXRange) { + bestTarget.xRange = (activeItem.presXRange as ObjectField)?.[Copy](); + changed = true; + } + if (bestTarget.yRange !== activeItem.presYRange) { + bestTarget.yRange = (activeItem.presYRange as ObjectField)?.[Copy](); + changed = true; + } + } + if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presClipWidth !== undefined)) { + if (bestTarget._clipWidth !== activeItem.presClipWidth) { + bestTarget._clipWidth = activeItem.presClipWidth; changed = true; } } @@ -381,8 +393,8 @@ export class PresBox extends ViewBoxBaseComponent() { Doc.GetProto(bestTarget).fillColor = activeItem.presFillColor; changed = true; } - if (bestTarget.color !== activeItem.color) { - Doc.GetProto(bestTarget).color = activeItem.color; + if (bestTarget.color !== activeItem.presColor) { + Doc.GetProto(bestTarget).color = activeItem.presColor; changed = true; } if (bestTarget.width !== activeItem.width) { @@ -394,31 +406,31 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } - if ((pinDataTypes?.viewType && activeItem.presPinViewType !== undefined) || (!pinDataTypes && activeItem.presPinViewType !== undefined)) { - if (bestTarget._viewType !== activeItem.presPinViewType) { - bestTarget._viewType = activeItem.presPinViewType; + if ((pinDataTypes?.viewType && activeItem.presViewType !== undefined) || (!pinDataTypes && activeItem.presViewType !== undefined)) { + if (bestTarget._viewType !== activeItem.presViewType) { + bestTarget._viewType = activeItem.presViewType; changed = true; } } - if ((pinDataTypes?.filters && activeItem.presPinDocFilters !== undefined) || (!pinDataTypes && activeItem.presPinDocFilters !== undefined)) { - if (bestTarget.docFilters !== activeItem.presPinDocFilters) { - bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presPinDocFilters as ObjectField) || new List([]); + if ((pinDataTypes?.filters && activeItem.presDocFilters !== undefined) || (!pinDataTypes && activeItem.presDocFilters !== undefined)) { + if (bestTarget.docFilters !== activeItem.presDocFilters) { + bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List([]); changed = true; } } - if ((pinDataTypes?.pivot && activeItem.presPinPvitoField !== undefined) || (!pinDataTypes && activeItem.presPinPivotField !== undefined)) { - if (bestTarget.pivotField !== activeItem.presPinPivotField) { - bestTarget.pivotField = activeItem.presPinPivotField; + if ((pinDataTypes?.pivot && activeItem.presPivotField !== undefined) || (!pinDataTypes && activeItem.presPivotField !== undefined)) { + if (bestTarget.pivotField !== activeItem.presPivotField) { + bestTarget.pivotField = activeItem.presPivotField; bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView changed = true; } } - if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presPinViewScroll !== undefined)) { - if (bestTarget._scrollTop !== activeItem.presPinViewScroll) { - bestTarget._scrollTop = activeItem.presPinViewScroll; + if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { + if (bestTarget._scrollTop !== activeItem.presViewScroll) { + bestTarget._scrollTop = activeItem.presViewScroll; changed = true; const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { @@ -479,7 +491,7 @@ export class PresBox extends ViewBoxBaseComponent() { }); setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10); } - if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPinViewX !== undefined || activeItem.presPinViewScale !== undefined))) { + if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) { const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; @@ -492,10 +504,10 @@ export class PresBox extends ViewBoxBaseComponent() { dv.ComponentView?.brushView?.(viewport); } } else { - if (bestTarget._panX !== activeItem.presPinViewX || bestTarget._panY !== activeItem.presPinViewY || bestTarget._viewScale !== activeItem.presPinViewScale) { - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; + if (bestTarget._panX !== activeItem.presPanX || bestTarget._panY !== activeItem.presPanY || bestTarget._viewScale !== activeItem.presViewScale) { + bestTarget._panX = activeItem.presPanX; + bestTarget._panY = activeItem.presPanY; + bestTarget._viewScale = activeItem.presViewScale; changed = true; } } @@ -514,7 +526,7 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); - pinDoc.presRot = NumCast(targetDoc.rotation); + pinDoc.presRotation = NumCast(targetDoc.rotation); pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); } @@ -526,6 +538,7 @@ export class PresBox extends ViewBoxBaseComponent() { pinProps.pinData.pannable || pinProps.pinData.viewType || pinProps.pinData.clippable || + pinProps.pinData.datarange || pinProps.pinData.dataview || pinProps.pinData.textview || pinProps.pinData.poslayoutview || @@ -546,8 +559,12 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presWidth = targetDoc._width; pinDoc.presHeight = targetDoc._height; } - if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop; - if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth; + if (pinProps.pinData.scrollable) pinDoc.presViewScroll = targetDoc._scrollTop; + if (pinProps.pinData.clippable) pinDoc.presClipWidth = targetDoc._clipWidth; + if (pinProps.pinData.datarange) { + pinDoc.presXRange = undefined; //targetDoc?.xrange; + pinDoc.presYRange = undefined; //targetDoc?.yrange; + } if (pinProps.pinData.poslayoutview) pinDoc.presPinLayoutData = new List( DocListCast(targetDoc[fkey] as ObjectField).map(d => @@ -564,13 +581,13 @@ export class PresBox extends ViewBoxBaseComponent() { }) ) ); - if (pinProps.pinData.viewType) pinDoc.presPinViewType = targetDoc._viewType; - if (pinProps.pinData.filters) pinDoc.presPinDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); - if (pinProps.pinData.pivot) pinDoc.presPinPivotField = targetDoc._pivotField; + if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; + if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); + if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; if (pinProps.pinData.pannable) { - pinDoc.presPinViewX = NumCast(targetDoc._panX); - pinDoc.presPinViewY = NumCast(targetDoc._panY); - pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); + pinDoc.presPanX = NumCast(targetDoc._panX); + pinDoc.presPanY = NumCast(targetDoc._panY); + pinDoc.presViewScale = NumCast(targetDoc._viewScale, 1); } if (pinProps.pinData.temporal) { pinDoc.presStartTime = targetDoc._currentTimecode; @@ -582,9 +599,9 @@ export class PresBox extends ViewBoxBaseComponent() { // If pinWithView option set then update scale and x / y props of slide const bounds = pinProps.pinViewport; pinDoc.presPinView = true; - pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); - pinDoc.presPinViewX = bounds.left + bounds.width / 2; - pinDoc.presPinViewY = bounds.top + bounds.height / 2; + pinDoc.presViewScale = NumCast(targetDoc._viewScale, 1); + pinDoc.presPanX = bounds.left + bounds.width / 2; + pinDoc.presPanY = bounds.top + bounds.height / 2; pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } } @@ -618,14 +635,10 @@ export class PresBox extends ViewBoxBaseComponent() { } finished(); }); - const createDocView = (doc: Doc, finished?: () => void) => { - DocumentManager.Instance.AddViewRenderedCb(doc, () => finished?.()); - LightboxView.AddDocTab(doc, OpenWhere.lightbox); - }; - PresBox.NavigateToTarget(targetDoc, activeItem, createDocView, resetSelection); + PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection); }; - static NavigateToTarget(targetDoc: Doc, activeItem: Doc, createDocView: any, finished?: () => void) { + static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); return; @@ -634,7 +647,7 @@ export class PresBox extends ViewBoxBaseComponent() { const presTime = NumCast(activeItem.presTransition, effect ? 750 : 500); const options: DocFocusOptions = { willPan: activeItem.presMovement !== PresMovement.None, - willPanZoom: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center, + willZoomCentered: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center, zoomScale: activeItem.presMovement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1), zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime), effect: activeItem, @@ -644,38 +657,23 @@ export class PresBox extends ViewBoxBaseComponent() { zoomTextSelections: BoolCast(activeItem.presZoomText), playAudio: BoolCast(activeItem.presPlayAudio), }; - const restoreLayout = () => { - // 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. - const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; - if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { - // targetDoc may or may not be displayed. so get the first available document (or alias) view that matches targetDoc and use it - // PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500), undefined, targetDoc); - } - }; - const finishAndRestoreLayout = () => { - finished?.(); - restoreLayout(); - }; - const containerDocContext = DocumentManager.GetContextPath(targetDoc); - - let context = containerDocContext.length ? containerDocContext[0] : targetDoc; if (activeItem.presOpenInLightbox) { - if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(DocCast(targetDoc.annotationOn) ?? targetDoc))) { - context = DocCast(targetDoc.annotationOn) ?? targetDoc; - LightboxView.SetLightboxDoc(context); // openInTab(targetDoc); + const context = DocCast(targetDoc.annotationOn) ?? targetDoc; + if (!DocumentManager.Instance.getLightboxDocumentView(context)) { + LightboxView.SetLightboxDoc(context); } } if (targetDoc) { if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[AnimationSym] = undefined; DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => { - if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(context.annotationOn) ?? context)) { + // if target or the doc it annotates is not in the lightbox, then close the lightbox + if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { LightboxView.SetLightboxDoc(undefined); } - DocumentManager.Instance.jumpToDocument(targetDoc, options, createDocView, containerDocContext, finishAndRestoreLayout); + DocumentManager.Instance.showDocument(targetDoc, options, finished); }); - } else finishAndRestoreLayout(); + } else finished?.(); } /** @@ -992,11 +990,14 @@ export class PresBox extends ViewBoxBaseComponent() { presDocView && SelectionManager.SelectView(presDocView, false); }; - focusElement = (doc: Doc, options: DocFocusOptions) => this.selectElement(doc); + focusElement = (doc: Doc, options: DocFocusOptions) => { + this.selectElement(doc); + return undefined; + }; //Regular click @action - selectElement = async (doc: Doc, noNav = false) => { + selectElement = (doc: Doc, noNav = false) => { CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.()); if (noNav) { const index = this.childDocs.indexOf(doc); @@ -1192,15 +1193,15 @@ export class PresBox extends ViewBoxBaseComponent() { } } else if (doc.presPinView && presCollection === tagDoc && dv) { // Case B: Document is presPinView and is presCollection - const scale: number = 1 / NumCast(doc.presPinViewScale); + const scale: number = 1 / NumCast(doc.presViewScale); const height: number = dv.props.PanelHeight() * scale; const width: number = dv.props.PanelWidth() * scale; const indWidth = width / 10; const indHeight = Math.max(height / 10, 15); const indEdge = Math.max(indWidth, indHeight); const indFontSize = indEdge * 0.8; - const xLoc: number = NumCast(doc.presPinViewX) - width / 2; - const yLoc: number = NumCast(doc.presPinViewY) - height / 2; + const xLoc: number = NumCast(doc.presPanX) - width / 2; + const yLoc: number = NumCast(doc.presPanY) - height / 2; docs.push(tagDoc); order.push( <> @@ -1233,8 +1234,8 @@ export class PresBox extends ViewBoxBaseComponent() { if ((index = 0)) pathPoints = n1x + ',' + n1y; else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } else if (doc.presPinView) { - const n1x = NumCast(doc.presPinViewX); - const n1y = NumCast(doc.presPinViewY); + const n1x = NumCast(doc.presPanX); + const n1y = NumCast(doc.presPanY); if ((index = 0)) pathPoints = n1x + ',' + n1y; else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } @@ -1960,12 +1961,6 @@ export class PresBox extends ViewBoxBaseComponent() { ); } - scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => { - // this.gotoDocument(0); - // this.startOrPause(false); - return undefined; - }; - _keyTimer: NodeJS.Timeout | undefined; /** @@ -2041,9 +2036,10 @@ export class PresBox extends ViewBoxBaseComponent() { const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; + const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; - return mode === CollectionViewType.Carousel3D ? null : ( + return mode === CollectionViewType.Carousel3D || inOverlay ? null : (
{/*
{"Add new slide"}
}>
this.newDocumentTools = !this.newDocumentTools)}> @@ -2404,11 +2400,7 @@ export class PresBox extends ViewBoxBaseComponent() { ); } static NavigateToDoc(bestTarget: Doc, activeItem: Doc) { - const openInTab = (doc: Doc, finished?: () => void) => { - CollectionDockingView.AddSplit(doc, OpenWhereMod.right); - finished?.(); - }; - PresBox.NavigateToTarget(bestTarget, activeItem, openInTab); + PresBox.NavigateToTarget(bestTarget, activeItem); } } diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index d19b78dbc..698a2817e 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -297,7 +297,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc; activeItem.presX = NumCast(targetDoc.x); activeItem.presY = NumCast(targetDoc.y); - activeItem.presRot = NumCast(targetDoc.rotation); + activeItem.presRotation = NumCast(targetDoc.rotation); activeItem.presWidth = NumCast(targetDoc.width); activeItem.presHeight = NumCast(targetDoc.height); activeItem.presPinLayout = true; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index ffc49e4f3..86d14c22e 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -179,10 +179,10 @@ export class PDFViewer extends React.Component { let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight); + const scrollTo = Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc }; - else if (!options.instant && !options.preview) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); + else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) }); } } else { @@ -518,10 +518,6 @@ export class PDFViewer extends React.Component { return this.props.styleProvider?.(doc, props, property); }; - focus = (doc: Doc, options: DocFocusOptions) => { - !doc.unrendered && this.props.DocumentView?.() && this.scrollToAnnotation(doc); - this.props.focus(doc, options); - }; renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
{ PanelWidth={this.panelWidth} ScreenToLocalTransform={this.overlayTransform} dropAction={'alias'} - focus={this.focus} docFilters={docFilters} select={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 4c4275ce7..3ba60edd7 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -414,7 +414,7 @@ export class SearchBox extends ViewBoxBaseComponent() { * or opening it in a new tab. */ selectElement = async (doc: Doc, finishFunc: () => void) => { - await DocumentManager.Instance.jumpToDocument(doc, { willPanZoom: true }, undefined, [], finishFunc); + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc); }; /** diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 5f02bd73d..16da0f9e2 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -218,7 +218,12 @@ export class ComputedField extends ScriptField { doc[`${fieldKey}-indexed`] = flist; } const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {}); - const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {}); + const setField = ScriptField.CompileScript( + `{setIndexVal (self['${fieldKey}-indexed'], self.${interpolatorKey}, value); console.log(self["data-indexed"][self.${interpolatorKey}],self.data,self["data-indexed"]))}`, + { value: 'any' }, + false, + {} + ); return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; } } diff --git a/src/fields/util.ts b/src/fields/util.ts index 6024705ec..e517e7604 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -335,8 +335,10 @@ export function setter(target: any, in_prop: string | symbol | number, value: an return true; } } - if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) { - return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; + if (target.__fields[prop] instanceof ComputedField) { + if (target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) { + return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; + } } return _setter(target, prop, value, receiver); } -- cgit v1.2.3-70-g09d2 From 98fd2ecbbf5168986a5eba889bff6aeea0eebb49 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Mar 2023 21:54:03 -0500 Subject: some cleanup of presBox properties panels. fixes for undoing some slider actions. --- src/client/views/MainView.tsx | 2 - src/client/views/PropertiesView.tsx | 31 +- src/client/views/collections/TabDocView.tsx | 8 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- src/client/views/nodes/trails/PresBox.tsx | 722 ++++++++++----------- 5 files changed, 369 insertions(+), 397 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e1ba5943d..945cd61db 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -553,8 +553,6 @@ export class MainView extends React.Component { Doc.MyTrails && (Doc.ActivePresentation = pres); Doc.AddDocToList(Doc.MyTrails, 'data', pres); this.closeFlyout(); - } else { - PresBox.NavigateToDoc(DocCast(pres.presentationTargetDoc), pres); } }; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 4a63930bd..7a985628f 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -95,6 +95,8 @@ export class PropertiesView extends React.Component { //Pres Trails booleans: @observable openPresTransitions: boolean = true; + @observable openPresProgressivize: boolean = false; + @observable openPresVisibilityAndDuration: boolean = false; @observable openAddSlide: boolean = false; @observable openSlideOptions: boolean = false; @@ -1740,7 +1742,9 @@ export class PropertiesView extends React.Component { } if (this.isPres) { const selectedItem: boolean = PresBox.Instance?.selectedArray.size > 0; - const type = PresBox.Instance.activeItem?.type; + const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType) + ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType) + : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type; return (
@@ -1764,6 +1768,31 @@ export class PropertiesView extends React.Component { {this.openPresTransitions ?
{PresBox.Instance.transitionDropdown}
: null}
)} + {!selectedItem ? null : ( +
+
(this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))} + style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> +     Visibilty +
+ +
+
+ {this.openPresVisibilityAndDuration ?
{PresBox.Instance.visibiltyDurationDropdown}
: null} +
+ )} + {!selectedItem ? null : ( +
+
(this.openPresProgressivize = !this.openPresProgressivize))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> +     Progressivize +
+ +
+
+ {this.openPresProgressivize ?
{PresBox.Instance.progressivizeDropdown}
: null} +
+ )} {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
(this.openSlideOptions = !this.openSlideOptions))} style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}> diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 99283996e..9459aaf1e 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -263,9 +263,6 @@ export class TabDocView extends React.Component { 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.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); if (!pinProps?.audioRange && duration !== undefined) { @@ -274,10 +271,6 @@ export class TabDocView extends React.Component { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - //PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, anchorDoc && anchorDoc !== doc && !anchorDoc.unrendered ? anchorDoc : doc); - pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); - Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); - //save position if (pinProps?.activeFrame !== undefined) { pinDoc.presActiveFrame = pinProps?.activeFrame; pinDoc.title = doc.title + ' (move)'; @@ -294,6 +287,7 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; + Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement()); PresBox.Instance?.clearSelectedArray(); pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7322f98b5..b92a8ec4d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -181,8 +181,6 @@ export class CollectionFreeFormView extends CollectionSubView { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 8738e47af..b5a24a380 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3,13 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { ColorState, SketchPicker } from 'react-color'; import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; +import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { AudioField } from '../../../../fields/URLField'; import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; @@ -69,9 +69,20 @@ export class PresBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + static navigateToDocScript: ScriptField; + + constructor(props: any) { + super(props); + if (!PresBox.navigateToDocScript) { + PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)')!; + } + } private _disposers: { [name: string]: IReactionDisposer } = {}; public selectedArray = new ObservableSet(); + _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves + _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation. + _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things @observable public static Instance: PresBox; @@ -143,14 +154,12 @@ export class PresBox extends ViewBoxBaseComponent() { addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc)); removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc)); - _unmounting = false; @action componentWillUnmount() { this._unmounting = true; if (this._presTimer) clearTimeout(this._presTimer); document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); this.resetPresentation(); - // Turn of progressivize editors this.turnOffEdit(true); Object.values(this._disposers).forEach(disposer => disposer?.()); } @@ -196,8 +205,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (editing) { this.childDocs.forEach(doc => { if (doc.presIndexed !== undefined) { - this.presIndexedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); - doc.presIndexed = Math.min(this.presIndexedItems(doc)?.length ?? 0, 1); + this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); + doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1); } }); } @@ -222,7 +231,7 @@ export class PresBox extends ViewBoxBaseComponent() { }; stopTempMedia = (targetDocField: FieldResult) => { - const targetDoc = Cast(targetDocField, Doc, null); + const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.Pause?.(); @@ -235,7 +244,7 @@ export class PresBox extends ViewBoxBaseComponent() { nextSlide = (slideNum?: number) => { const nextSlideInd = slideNum ?? this.itemIndex + 1; let curSlideInd = nextSlideInd; - CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.()); + //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.()); this.clearSelectedArray(); const doGroupWithUp = (nextSelected: number, force = false) => @@ -261,7 +270,8 @@ export class PresBox extends ViewBoxBaseComponent() { doGroupWithUp(curSlideInd, true)(); }; - presIndexedItems = (doc: Doc) => { + // docs within a slide target that will be progressively revealed + progressivizedItems = (doc: Doc) => { const targetList = PresBox.targetRenderedDoc(doc); if (doc.presIndexed !== undefined && targetList) { const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '-annotations']); @@ -278,7 +288,7 @@ export class PresBox extends ViewBoxBaseComponent() { targetRenderedDoc._dataTransition = 'all 1s'; targetRenderedDoc.opacity = 1; setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000); - const listItems = this.presIndexedItems(this.activeItem); + const listItems = this.progressivizedItems(this.activeItem); if (listItems && presIndexed < listItems.length) { if (!first) { const listItemDoc = listItems[presIndexed]; @@ -365,7 +375,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array this.turnOffEdit(); this.navigateToActiveItem(finished); //Handles movement to element only when presTrail is list - this.onHideDocument(); //Handles hide after/before + this.doHideBeforeAfter(); //Handles hide after/before } }); static pinDataTypes(target?: Doc): pinDataTypes { @@ -740,7 +750,7 @@ export class PresBox extends ViewBoxBaseComponent() { * they are hidden each time the presentation is updated. */ @action - onHideDocument = () => { + doHideBeforeAfter = () => { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); const tagDoc = PresBox.targetRenderedDoc(curDoc); @@ -872,10 +882,10 @@ export class PresBox extends ViewBoxBaseComponent() { if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0; if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0; if (doc.presIndexed !== undefined && index >= startIndex) { - this.presIndexedItems(doc) + this.progressivizedItems(doc) ?.slice(doc.type === DocumentType.COL ? 1 : 0) .forEach(indexedDoc => (indexedDoc.opacity = 0)); - doc.presIndexed = Math.min(this.presIndexedItems(doc)?.length ?? 0, doc.type === DocumentType.COL ? 1 : 0); + doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, doc.type === DocumentType.COL ? 1 : 0); } // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0; }); @@ -1338,12 +1348,15 @@ export class PresBox extends ViewBoxBaseComponent() { if (timeInMS > 100000) timeInMS = 100000; setter(timeInMS); }; - setTransitionTime = (number: String, change?: number) => { + + @undoBatch + updateTransitionTime = (number: String, change?: number) => { PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presTransition = timeInMS)), change); }; // Converts seconds to ms and updates presTransition - setZoom = (number: String, change?: number) => { + @undoBatch + updateZoom = (number: String, change?: number) => { let scale = Number(number) / 100; if (change) scale += change; if (scale < 0.01) scale = 0.01; @@ -1351,8 +1364,11 @@ export class PresBox extends ViewBoxBaseComponent() { this.selectedArray.forEach(doc => (doc.presZoom = scale)); }; - // Converts seconds to ms and updates presDuration - setDurationTime = (number: String, change?: number) => { + /* + * Converts seconds to ms and updates presDuration + */ + @undoBatch + updateDurationTime = (number: String, change?: number) => { let timeInMS = Number(number) * 1000; if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; @@ -1360,9 +1376,6 @@ export class PresBox extends ViewBoxBaseComponent() { this.selectedArray.forEach(doc => (doc.presDuration = timeInMS)); }; - /** - * When the movement dropdown is changes - */ @undoBatch updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement))); @@ -1393,6 +1406,7 @@ export class PresBox extends ViewBoxBaseComponent() { activeItem.presOpenInLightbox = !activeItem.presOpenInLightbox; this.selectedArray.forEach(doc => (doc.presOpenInLightbox = activeItem.presOpenInLightbox)); }; + @undoBatch @action updateEaseFunc = (activeItem: Doc) => { @@ -1408,10 +1422,8 @@ export class PresBox extends ViewBoxBaseComponent() { @action updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presEffect = effect))); - _batch: UndoManager.Batch | undefined = undefined; - + static _sliderBatch: any; public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => { - let batch: any; return ( () { style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }} className={`toolbar-slider ${active ? '' : 'none'}`} onPointerDown={e => { - batch = UndoManager.StartBatch('pres slider'); + PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); e.stopPropagation(); }} - onPointerUp={() => batch?.end()} + onPointerUp={() => PresBox._sliderBatch.end()} onChange={e => { e.stopPropagation(); change(e.target.value); @@ -1433,11 +1445,146 @@ export class PresBox extends ViewBoxBaseComponent() { /> ); }; + + @undoBatch + @action + applyTo = (array: Doc[]) => { + this.updateMovement(this.activeItem.presMovement as PresMovement, true); + this.updateEffect(this.activeItem.presEffect as PresEffect, false, true); + this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true); + this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true); + const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem; + array.forEach(curDoc => { + curDoc.presTransition = presTransition; + curDoc.presDuration = presDuration; + curDoc.presHideBefore = presHideBefore; + curDoc.presHideAfter = presHideAfter; + }); + }; + + @computed get visibiltyDurationDropdown() { + const activeItem = this.activeItem; + if (activeItem && this.targetDoc) { + const targetType = this.targetDoc.type; + let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0; + if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); + return ( +
+
+ {'Hide before presented'}
}> +
this.updateHideBefore(activeItem)}> + Hide before +
+ + {'Hide while presented'}
}> +
this.updateHide(activeItem)}> + Hide +
+ + + {'Hide after presented'}
}> +
this.updateHideAfter(activeItem)}> + Hide after +
+ + + {'Open in lightbox view'}
}> +
this.updateOpenDoc(activeItem)}> + Lightbox +
+ + {'Transition movement style'}
}> +
this.updateEaseFunc(activeItem)}> + {`${StrCast(activeItem.presEaseFunc, 'ease')}`} +
+ +
+ {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : ( + <> +
+
Slide Duration
+
+ e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s +
+
+
this.updateDurationTime(String(duration), 1000)}> + +
+
this.updateDurationTime(String(duration), -1000)}> + +
+
+
+ {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)} +
+
Short
+
Medium
+
Long
+
+ + )} +
+ ); + } + } + @computed get progressivizeDropdown() { + const activeItem = this.activeItem; + if (activeItem && this.targetDoc && activeItem.presIndexed !== undefined) { + const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None; + const bulletEffect = (effect: PresEffect) => ( +
this.updateEffect(effect, true)}> + {effect} +
+ ); + return ( +
+
+
Progressivize Collection
+ { + activeItem.presIndexed = activeItem.presIndexed === undefined ? 0 : undefined; + activeItem.presHideBefore = activeItem.presIndexed !== undefined; + }} + checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false} + /> +
+ {activeItem.presIndexed === undefined ? null : ( +
+
Expand Current Bullet
+ (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} /> +
+ )} +
Progressive effect
+
{ + e.stopPropagation(); + this._openBulletEffectDropdown = !this._openBulletEffectDropdown; + })} + style={{ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, border: this._openBulletEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {effect?.toString()} + +
e.stopPropagation()}> + {bulletEffect(PresEffect.None)} + {bulletEffect(PresEffect.Fade)} + {bulletEffect(PresEffect.Flip)} + {bulletEffect(PresEffect.Rotate)} + {bulletEffect(PresEffect.Bounce)} + {bulletEffect(PresEffect.Roll)} +
+
+
+ ); + } + return null; + } @computed get transitionDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const presEffect = (effect: PresEffect, bullet: boolean) => ( -
this.updateEffect(effect, bullet)}> + const activeItem = this.activeItem; + const presEffect = (effect: PresEffect) => ( +
this.updateEffect(effect, false)}> {effect}
); @@ -1458,15 +1605,10 @@ export class PresBox extends ViewBoxBaseComponent() { ); }; - if (activeItem && targetDoc) { - const type = targetDoc.type; + if (activeItem && this.targetDoc) { const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; const zoom = NumCast(activeItem.presZoom, 1) * 100; - let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0; - if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = activeItem.presEffect ? activeItem.presEffect : PresMovement.None; - const bulletEffect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None; - // activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : PresMovement.Zoom; return (
() {
Zoom (% screen filled)
- this.setZoom(e.target.value))} />% + this.updateZoom(e.target.value)} />%
-
this.setZoom(String(zoom), 0.1))}> +
this.updateZoom(String(zoom), 0.1)}>
-
this.setZoom(String(zoom), -0.1))}> +
this.updateZoom(String(zoom), -0.1)}>
- {PresBox.inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)} + {PresBox.inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.updateZoom)}
-
Transition Speed
+
Transition Time
- e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s + e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
-
this.setTransitionTime(String(transitionSpeed), 1000))}> +
this.updateTransitionTime(String(transitionSpeed), 1000)}>
-
this.setTransitionTime(String(transitionSpeed), -1000))}> +
this.updateTransitionTime(String(transitionSpeed), -1000)}>
- {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.setTransitionTime)} + {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)}
Fast
Medium
Slow
-
- Visibility {'&'} Duration -
- {'Hide before presented'}
}> -
this.updateHideBefore(activeItem)}> - Hide before -
- - {'Hide while presented'}
}> -
this.updateHide(activeItem)}> - Hide -
- - - {'Hide after presented'}
}> -
this.updateHideAfter(activeItem)}> - Hide after -
- - - {'Open in lightbox view'}
}> -
this.updateOpenDoc(activeItem)}> - Lightbox -
- - {'Transition movement style'}
}> -
this.updateEaseFunc(activeItem)}> - {`${StrCast(activeItem.presEaseFunc, 'ease')}`} -
- -
- {type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( - <> -
-
Slide Duration
-
- e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s -
-
-
this.setDurationTime(String(duration), 1000))}> - -
-
this.setDurationTime(String(duration), -1000))}> - -
-
-
- {PresBox.inputter('0.1', '0.1', '20', duration, targetDoc.type !== DocumentType.AUDIO, this.setDurationTime)} -
-
Short
-
Medium
-
Long
-
- - )} -
Effects
@@ -1609,12 +1695,12 @@ export class PresBox extends ViewBoxBaseComponent() { {effect?.toString()}
e.stopPropagation()}> - {presEffect(PresEffect.None, false)} - {presEffect(PresEffect.Fade, false)} - {presEffect(PresEffect.Flip, false)} - {presEffect(PresEffect.Rotate, false)} - {presEffect(PresEffect.Bounce, false)} - {presEffect(PresEffect.Roll, false)} + {presEffect(PresEffect.None)} + {presEffect(PresEffect.Fade)} + {presEffect(PresEffect.Flip)} + {presEffect(PresEffect.Rotate)} + {presEffect(PresEffect.Bounce)} + {presEffect(PresEffect.Roll)}
@@ -1628,49 +1714,6 @@ export class PresBox extends ViewBoxBaseComponent() { {presDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} {presDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
-
Progressive Disclosure
-
-
Progressivize Collection
- { - activeItem.presIndexed = activeItem.presIndexed === undefined ? 0 : undefined; - activeItem.presHideBefore = activeItem.presIndexed !== undefined; - }} - checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false} - /> -
- {activeItem.presIndexed === undefined ? null : ( -
-
Expand Current Bullet
- (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} /> -
- )} - {activeItem.presIndexed === undefined ? null : ( -
- Progressive effect -
{ - e.stopPropagation(); - this._openBulletEffectDropdown = !this._openBulletEffectDropdown; - })} - style={{ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, border: this._openBulletEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> - {bulletEffect?.toString()} - -
e.stopPropagation()}> - {presEffect(PresEffect.None, true)} - {presEffect(PresEffect.Fade, true)} - {presEffect(PresEffect.Flip, true)} - {presEffect(PresEffect.Rotate, true)} - {presEffect(PresEffect.Bounce, true)} - {presEffect(PresEffect.Roll, true)} -
-
-
- )}
this.applyTo(this.childDocs)}> @@ -1681,174 +1724,154 @@ export class PresBox extends ViewBoxBaseComponent() { ); } } - - @undoBatch - @action - applyTo = (array: Doc[]) => { - this.updateMovement(this.activeItem.presMovement as PresMovement, true); - this.updateEffect(this.activeItem.presEffect as PresEffect, false, true); - this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true); - this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true); - const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem; - array.forEach(curDoc => { - curDoc.presTransition = presTransition; - curDoc.presDuration = presDuration; - curDoc.presHideBefore = presHideBefore; - curDoc.presHideAfter = presHideAfter; - }); - }; - @computed get mediaOptionsDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const clipStart: number = NumCast(activeItem.clipStart); - const clipEnd: number = NumCast(activeItem.clipEnd, NumCast(activeItem[Doc.LayoutFieldKey(activeItem) + '-duration'])); - const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc); - if (activeItem && targetDoc) { + const activeItem = this.activeItem; + if (activeItem && this.targetDoc) { + const clipStart = NumCast(activeItem.clipStart); + const clipEnd = NumCast(activeItem.clipEnd, NumCast(activeItem[Doc.LayoutFieldKey(activeItem) + '-duration'])); return ( -
-
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> -
-
- Start {'&'} End Time -
-
-
- Start time (s) -
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { - activeItem.presStartTime = Number(e.target.value); - })} - /> -
-
-
-
- Duration (s) -
-
- {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10} -
+
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
+
+ Start {'&'} End Time +
+
+
+ Start time (s)
-
-
- End time (s) -
-
- e.stopPropagation()} - style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} - type="number" - value={NumCast(activeItem.presEndTime).toFixed(2)} - onChange={action((e: React.ChangeEvent) => { - activeItem.presEndTime = Number(e.target.value); - })} - /> -
+
+ e.stopPropagation()} + onChange={action((e: React.ChangeEvent) => { + activeItem.presStartTime = Number(e.target.value); + })} + />
-
- { - this._batch = UndoManager.StartBatch('presEndTime'); - const endBlock = document.getElementById('endTime'); - if (endBlock) { - endBlock.style.color = Colors.LIGHT_GRAY; - endBlock.style.backgroundColor = Colors.MEDIUM_BLUE; - } - e.stopPropagation(); - }} - onPointerUp={() => { - this._batch?.end(); - const endBlock = document.getElementById('endTime'); - if (endBlock) { - endBlock.style.color = Colors.BLACK; - endBlock.style.backgroundColor = Colors.LIGHT_GRAY; - } - }} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - activeItem.presEndTime = Number(e.target.value); - }} - /> - { - this._batch = UndoManager.StartBatch('presStartTime'); - const startBlock = document.getElementById('startTime'); - if (startBlock) { - startBlock.style.color = Colors.LIGHT_GRAY; - startBlock.style.backgroundColor = Colors.MEDIUM_BLUE; - } - e.stopPropagation(); - }} - onPointerUp={() => { - this._batch?.end(); - const startBlock = document.getElementById('startTime'); - if (startBlock) { - startBlock.style.color = Colors.BLACK; - startBlock.style.backgroundColor = Colors.LIGHT_GRAY; - } - }} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - activeItem.presStartTime = Number(e.target.value); - }} - /> -
-
-
{clipStart.toFixed(2)} s
-
-
{clipEnd.toFixed(2)} s
-
-
-
- Playback -
Start playing:
-
-
- (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} /> -
On click
+
+
+ Duration (s)
-
- (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} /> -
Automatically
+
+ {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
-
Stop playing:
-
-
- (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} /> -
At audio end time
+
+
+ End time (s)
-
- (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} /> -
On slide change
+
+ e.stopPropagation()} + style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} + type="number" + value={NumCast(activeItem.presEndTime).toFixed(2)} + onChange={action((e: React.ChangeEvent) => { + activeItem.presEndTime = Number(e.target.value); + })} + />
- {/*
+
+
+
+ { + this._batch = UndoManager.StartBatch('presEndTime'); + const endBlock = document.getElementById('endTime'); + if (endBlock) { + endBlock.style.color = Colors.LIGHT_GRAY; + endBlock.style.backgroundColor = Colors.MEDIUM_BLUE; + } + e.stopPropagation(); + }} + onPointerUp={() => { + this._batch?.end(); + const endBlock = document.getElementById('endTime'); + if (endBlock) { + endBlock.style.color = Colors.BLACK; + endBlock.style.backgroundColor = Colors.LIGHT_GRAY; + } + }} + onChange={(e: React.ChangeEvent) => { + e.stopPropagation(); + activeItem.presEndTime = Number(e.target.value); + }} + /> + { + this._batch = UndoManager.StartBatch('presStartTime'); + const startBlock = document.getElementById('startTime'); + if (startBlock) { + startBlock.style.color = Colors.LIGHT_GRAY; + startBlock.style.backgroundColor = Colors.MEDIUM_BLUE; + } + e.stopPropagation(); + }} + onPointerUp={() => { + this._batch?.end(); + const startBlock = document.getElementById('startTime'); + if (startBlock) { + startBlock.style.color = Colors.BLACK; + startBlock.style.backgroundColor = Colors.LIGHT_GRAY; + } + }} + onChange={(e: React.ChangeEvent) => { + e.stopPropagation(); + activeItem.presStartTime = Number(e.target.value); + }} + /> +
+
+
{clipStart.toFixed(2)} s
+
+
{clipEnd.toFixed(2)} s
+
+
+
+ Playback +
Start playing:
+
+
+ (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} /> +
On click
+
+
+ (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} /> +
Automatically
+
+
+
Stop playing:
+
+
+ (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} /> +
At audio end time
+
+
+ (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} /> +
On slide change
+
+ {/*
activeItem.mediaStop = "afterSlide"} @@ -1865,7 +1888,6 @@ export class PresBox extends ViewBoxBaseComponent() {
*/} -
@@ -1873,7 +1895,6 @@ export class PresBox extends ViewBoxBaseComponent() { ); } } - @computed get newDocumentToolbarDropdown() { return (
() { ); } - _keyTimer: NodeJS.Timeout | undefined; - - /** - * Returns the collection type as a string for headers - */ - @computed get stringType() { - if (this.activeItem) { - // prettier-ignore - switch (this.targetDoc.type) { - case DocumentType.PDF: return 'PDF'; - case DocumentType.RTF: return 'Text node'; - case DocumentType.COL: return 'Collection'; - case DocumentType.AUDIO: return 'Audio'; - case DocumentType.VID: return 'Video'; - case DocumentType.IMG: return 'Image'; - case DocumentType.WEB: return 'Web page'; - case DocumentType.MAP: return 'Map'; - default: return 'Other node'; - } - } - return ''; - } - - @observable private openActiveColorPicker: boolean = false; - @observable private openViewedColorPicker: boolean = false; - - @undoBatch - @action - switchActive = (color: ColorState) => { - this.targetDoc['pres-text-color'] = String(color.hex); - return true; - }; - @undoBatch - @action - switchPresented = (color: ColorState) => { - this.targetDoc['pres-text-viewed-color'] = String(color.hex); - return true; - }; - - @computed get activeColorPicker() { - return !this.openActiveColorPicker ? null : ( - - ); - } - - @computed get viewedColorPicker() { - return !this.openViewedColorPicker ? null : ( - - ); - } - @action turnOffEdit = (paths?: boolean) => paths && this.togglePath(true); // Turn off paths @@ -2174,14 +2136,6 @@ export class PresBox extends ViewBoxBaseComponent() { {isMini ? null : ( <>
- {/*
{this._expandBoolean ? "Minimize all" : "Expand all"}
}> -
- -
-
-
*/} {this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
}>
@@ -2256,7 +2210,7 @@ export class PresBox extends ViewBoxBaseComponent() { } @computed get playButtons() { - const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.presIndexedItems(this.activeItem)?.length ?? 0)); + const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); // Case 1: There are still other frames and should go through all frames before going to next slide @@ -2345,7 +2299,7 @@ export class PresBox extends ViewBoxBaseComponent() {
this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> {inOverlay ? '' : 'Slide'} {this.itemIndex + 1} - {this.activeItem?.presIndexed !== undefined ? `.${this.activeItem.presIndexed}/${this.presIndexedItems(this.activeItem)?.length}` : ''} / {this.childDocs.length} + {this.activeItem?.presIndexed !== undefined ? `.${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length}` : ''} / {this.childDocs.length}
{this.props.PanelWidth() > 250 ? ( @@ -2392,6 +2346,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc.presStatus = PresStatus.Manual; } }; + @undoBatch @action exitClicked = () => { @@ -2423,14 +2378,13 @@ export class PresBox extends ViewBoxBaseComponent() { return this.childDocs; }; - // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1]) sort = (treeViewMap: Map) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]); render() { // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.presIndexedItems(this.activeItem)?.length ?? 0)); + const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0; const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player @@ -2466,7 +2420,7 @@ export class PresBox extends ViewBoxBaseComponent() {
Slide {this.itemIndex + 1} - {this.activeItem.presIndexed !== undefined ? `.${this.activeItem.presIndexed}/${this.presIndexedItems(this.activeItem)?.length}` : ''} / {this.childDocs.length} + {this.activeItem.presIndexed !== undefined ? `.${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length}` : ''} / {this.childDocs.length}
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> @@ -2493,6 +2447,7 @@ export class PresBox extends ViewBoxBaseComponent() { //childFitWidth={returnTrue} childOpacity={returnOne} //childLayoutString={PresElementBox.LayoutString('data')} + childClickScript={PresBox.navigateToDocScript} childLayoutTemplate={this.childLayoutTemplate} childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined} filterAddDocument={this.addDocumentFilter} @@ -2520,11 +2475,8 @@ export class PresBox extends ViewBoxBaseComponent() {
); } - static NavigateToDoc(bestTarget: Doc, activeItem: Doc) { - PresBox.NavigateToTarget(bestTarget, activeItem); - } } ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { - PresBox.NavigateToDoc(bestTarget, activeItem); + PresBox.NavigateToTarget(bestTarget, activeItem); }); -- cgit v1.2.3-70-g09d2 From 20c0190e820f2bd343693368b7ef55a91f19c880 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 14 Mar 2023 20:31:06 -0400 Subject: simplified Deserialize code. streamlined currentUserUtils to need fewer compiled functions by parameterizing more functions. . --- src/client/documents/Documents.ts | 13 +- src/client/util/CurrentUserUtils.ts | 145 +++++------ src/client/util/Scripting.ts | 4 +- src/client/util/SelectionManager.ts | 7 +- src/client/util/SerializationHelper.ts | 86 +------ src/client/views/DashboardView.tsx | 1 - src/client/views/GestureOverlay.tsx | 7 +- src/client/views/MainView.tsx | 20 -- src/client/views/OverlayView.tsx | 1 + .../views/collections/CollectionDockingView.tsx | 19 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 10 +- src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/WebBox.tsx | 8 +- src/client/views/nodes/button/FontIconBox.tsx | 272 +++++++-------------- src/fields/Doc.ts | 57 ++--- src/fields/ScriptField.ts | 31 +-- src/fields/util.ts | 22 +- 18 files changed, 253 insertions(+), 454 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 251e432ef..457811e26 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -200,6 +200,8 @@ export class DocumentOptions { 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view'); 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view'); 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)'); + openFactoryLocation?: string; // an OpenWhere value to place the factory created document + openFactoryAsDelegate?: boolean; // lat?: number; lng?: number; infoWindowOpen?: boolean; @@ -220,6 +222,8 @@ export class DocumentOptions { autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. toolTip?: string; // tooltip to display on hover + toolType?: string; // type of pen tool + expertMode?: boolean; // something available only in expert (not novice) mode contextMenuFilters?: List; contextMenuScripts?: List; contextMenuLabels?: List; @@ -1353,6 +1357,8 @@ export namespace DocUtils { const script = scripts[key]; if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { doc[key] = ScriptField.MakeScript(script, { + self: Doc.name, + this: Doc.name, dragData: DragManager.DocumentDragData.name, value: 'any', _readOnly_: 'boolean', @@ -1862,11 +1868,8 @@ export namespace DocUtils { } ScriptingGlobals.add('Docs', Docs); -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { - return DocUtils.copyDragFactory(dragFactory); -}); -ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { - return DocUtils.delegateDragFactory(dragFactory); +ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { + return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory; }); ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c4fb4788c..2820c66ee 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -21,6 +21,7 @@ import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; +import { OpenWhere } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; import { DragManager } from "./DragManager"; import { MakeTemplate } from "./DropConverter"; @@ -40,6 +41,8 @@ interface Button { numBtnMax?: number; switchToggle?: boolean; width?: number; + toolType?: string; // type of pen tool + expertMode?: boolean;// available only in expert mode btnList?: List; ignoreClick?: boolean; buttonText?: string; @@ -78,7 +81,7 @@ export class CurrentUserUtils { ]; const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => { const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title); - const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' }; + const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' }; const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { if (templateBtn) { DocUtils.AssignOpts(templateBtn,btnOpts); @@ -105,7 +108,7 @@ export class CurrentUserUtils { const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"}, - { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: "openOnRight(self.doubleClickView)"}]; + { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined; @@ -121,11 +124,11 @@ export class CurrentUserUtils { const tempClicks = DocCast(doc[field]); const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, system: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ - { opts: { title: "onClick"}, script: "console.log( 'click')"}, - { opts: { title: "onDoubleClick"}, script: "console.log( 'double click')"}, - { opts: { title: "onChildClick"}, script: "console.log( 'child click')"}, - { opts: { title: "onChildDoubleClick"}, script: "console.log( 'child double click')"}, - { opts: { title: "onCheckedClick"}, script: "console.log( heading, checked, containingTreeView)"}, + { opts: { title: "onClick"}, script: "console.log('click')"}, + { opts: { title: "onDoubleClick"}, script: "console.log('click')"}, + { opts: { title: "onChildClick"}, script: "console.log('click')"}, + { opts: { title: "onChildDoubleClick"}, script: "console.log('click')"}, + { opts: { title: "onCheckedClick"}, script: "console.log(heading, checked, containingTreeView)"}, ]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; @@ -183,7 +186,7 @@ export class CurrentUserUtils { const allopts = {system: true, ...opts}; return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), - {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView"}); + {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)"}); }; const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts @@ -211,7 +214,7 @@ export class CurrentUserUtils { /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyTrail static creatorBtnDescriptors(doc: Doc): { title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, - backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, + backgroundColor?: string, openFactoryAsDelegate?:boolean, openFactoryLocation?:string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: false, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); const json = { @@ -257,13 +260,13 @@ export class CurrentUserUtils { creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, - {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200 }}, + {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _fitWidth: true}}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _fitWidth: true }}, {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, - {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, + {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }}, @@ -282,30 +285,34 @@ export class CurrentUserUtils { emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs)); return [ - { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, }, - { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, }, - { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab), scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, }, - { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, }, - { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, }, - { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, }, - { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, }, - { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,scripts: { onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: { onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, - ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, })) + { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, + { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, + { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc,clickFactory: DocCast(doc.emptyTab)}, + { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)}, + { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, + { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc,clickFactory: DocCast(doc.emptyComparison)}, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc,clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript)}, + { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, + { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true }, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", clickFactory: "repl" as any, openFactoryLocation: OpenWhere.overlay}, + ].map(tuple => ( + { openFactoryLocation: OpenWhere.addRight, + scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', + onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'}, + ...tuple, })) } /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; - const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, + const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), @@ -603,53 +610,50 @@ export class CurrentUserUtils { static textTools():Button[] { return [ - { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'}, + { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_);}'}}, - { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scripts: {script: '{ return setFontHighlight(value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, - { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} }, - { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, - { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, - + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}}, + { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, + { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}}, - { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, - { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} }, - { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}}, - { title: "Dictate",toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}}, - ]; + ]; } static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", false, _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }}, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} }, - { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} }, - { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} }, - { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} }, - { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} }, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }}, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, + { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip", toolType: "fillColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, ]; } static schemaTools():Button[] { - return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{return toggleSchemaPreview(_readOnly_);}'}, }]; + return [{ title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'}, }]; } static webTools() { return [ { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(_readOnly_); }' }}, { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(_readOnly_); }'}}, - //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } @@ -661,18 +665,18 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), - title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, - { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, - { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected - { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform", true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.RTF}")`} }, // Always available - { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.INK}")`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available - { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), funcs: {hidden: `!SelectionManager_selectedDocType("${DocumentType.WEB}")`, linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.WEB}")`, } }, // Only when Web is selected - { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), funcs: {hidden: `!SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`, linearViewIsExpanded: `SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected + title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, toolType:"tab", funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, + { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, + { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, + { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected + { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; } @@ -684,6 +688,7 @@ export class CurrentUserUtils { color: Colors.WHITE, system: true, dontUndo: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, + toolType: params.toolType, expertMode: params.expertMode, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, _removeDropProperties: new List([ "_stayInCollection"]), }; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 6dcdcb71b..d32298c83 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -248,8 +248,10 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp ScriptingGlobals.resetScriptingGlobals(); } !signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript); - //console.log('COMPILED: ' + script + ':' + signature); return result; } ScriptingGlobals.add(CompileScript); +ScriptingGlobals.add(function runScript(self: Doc, script: ScriptField) { + return script?.script.run({ this: self, self: self }).result; +}); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index c0fc25376..0f4f77588 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -104,10 +104,11 @@ export namespace SelectionManager { return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking); } } -ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { - if (colType === ('tab' as any)) { +ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { + if (Doc.noviceMode && expertMode) return false; + if (type === 'tab') { return SelectionManager.Views().lastElement()?.props.renderDepth === 0; } let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); - return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; + return selected?.type === type || selected?.viewType === type || !type; }); diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 2d1f61cfb..76037a7e9 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -44,12 +44,8 @@ export namespace SerializationHelper { } if (!obj.__type) { - if (true || ClientUtils.RELEASE) { - console.warn("No property 'type' found in JSON."); - return undefined; - } else { - throw Error("No property 'type' found in JSON."); - } + console.warn("No property 'type' found in JSON."); + return undefined; } if (!(obj.__type in serializationTypes)) { @@ -58,9 +54,8 @@ export namespace SerializationHelper { const type = serializationTypes[obj.__type]; const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result))); - if (type.afterDeserialize) { - type.afterDeserialize(value); - } + type.afterDeserialize?.(value); + return value; } } @@ -68,75 +63,20 @@ export namespace SerializationHelper { const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise } } = {}; const reverseMap: { [ctor: string]: string } = {}; -export interface DeserializableOpts { - (constructor: { new (...args: any[]): any }): void; - withFields(fields: string[]): Function; -} - -export function Deserializable(name: string, afterDeserialize?: (obj: any) => void | Promise): DeserializableOpts; -export function Deserializable(constructor: { new (...args: any[]): any }): void; -export function Deserializable(constructor: { new (...args: any[]): any } | string, afterDeserialize?: (obj: any) => void): DeserializableOpts | void { - function addToMap(name: string, ctor: { new (...args: any[]): any }) { +export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { + function addToMap(className: string, ctor: { new (...args: any[]): any }) { const schema = getDefaultModelSchema(ctor) as any; - if (schema.targetClass !== ctor) { - const newSchema = { ...schema, factory: () => new ctor() }; - setDefaultModelSchema(ctor, newSchema); + if (schema.targetClass !== ctor || constructorArgs) { + setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) }); } - if (!(name in serializationTypes)) { - serializationTypes[name] = { ctor, afterDeserialize }; - reverseMap[ctor.name] = name; + if (!(className in serializationTypes)) { + serializationTypes[className] = { ctor, afterDeserialize }; + reverseMap[ctor.name] = className; } else { - throw new Error(`Name ${name} has already been registered as deserializable`); + throw new Error(`Name ${className} has already been registered as deserializable`); } } - if (typeof constructor === 'string') { - return Object.assign( - (ctor: { new (...args: any[]): any }) => { - addToMap(constructor, ctor); - }, - { withFields: (fields: string[]) => Deserializable.withFields(fields, constructor, afterDeserialize) } - ); - } - addToMap(constructor.name, constructor); -} - -export namespace Deserializable { - export function withFields(fields: string[], name?: string, afterDeserialize?: (obj: any) => void | Promise) { - return function (constructor: { new (...fields: any[]): any }) { - Deserializable(name || constructor.name, afterDeserialize)(constructor); - let schema = getDefaultModelSchema(constructor); - if (schema) { - schema.factory = context => { - const args = fields.map(key => context.json[key]); - return new constructor(...args); - }; - // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing - // fields.forEach(field => { - // if (field in schema.props) { - // let propSchema = schema.props[field]; - // if (propSchema === false) { - // return; - // } else if (propSchema === true) { - // propSchema = primitive(); - // } - // schema.props[field] = custom(propSchema.serializer, - // () => { - // return SKIP; - // }); - // } - // }); - } else { - schema = { - props: {}, - factory: context => { - const args = fields.map(key => context.json[key]); - return new constructor(...args); - }, - }; - setDefaultModelSchema(constructor, schema); - } - }; - } + return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor); } export function autoObject(): PropSchema { diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 7ebe8d0e3..2b586b0e2 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -233,7 +233,6 @@ export class DashboardView extends React.Component { /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) public static openDashboard = (doc: Doc | undefined, fromHistory = false) => { if (!doc) return false; - Doc.MainDocId = doc[Id]; Doc.AddDocToList(Doc.MyDashboards, 'data', doc); // this has the side-effect of setting the main container since we're assigning the active/guest dashboard diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 6058eaaf9..0feccb742 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -94,7 +94,7 @@ export class GestureOverlay extends Touchable { } static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string; icon: string; drag?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [ + const docProtoData: { title: string; icon: string; drag?: string; toolType?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [ { title: 'use pen', icon: 'pen-nib', pointerUp: 'resetPen()', pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: 'blue' }, { title: 'use highlighter', icon: 'highlighter', pointerUp: 'resetPen()', pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: 'yellow' }, { @@ -105,8 +105,8 @@ export class GestureOverlay extends Touchable { clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: 'orange', }, - { title: 'interpret text', icon: 'font', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: 'orange' }, - { title: 'ignore gestures', icon: 'signature', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: 'green' }, + { title: 'interpret text', icon: 'font', toolType: 'inktotext', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'orange' }, + { title: 'ignore gestures', icon: 'signature', toolType: 'ignoregesture', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'green' }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ @@ -116,6 +116,7 @@ export class GestureOverlay extends Touchable { _height: 10, title: data.title, icon: data.icon, + toolType: data.toolType, _dropAction: data.pointerDown ? 'copy' : undefined, ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 945cd61db..2e04ca3dd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -230,8 +230,6 @@ export class MainView extends React.Component { if (window.location.pathname !== '/home') { const pathname = window.location.pathname.substr(1).split('/'); if (pathname.length > 1 && pathname[0] === 'doc') { - Doc.MainDocId = pathname[1]; - //!this.userDoc && DocServer.GetRefField(pathname[1]).then( action(field => { if (field instanceof Doc && field._viewType !== CollectionViewType.Docking) { @@ -482,7 +480,6 @@ export class MainView extends React.Component { fa.faHandPointUp, ] ); - this.initAuthenticationRouters(); } globalPointerDown = action((e: PointerEvent) => { @@ -521,23 +518,6 @@ export class MainView extends React.Component { document.oncontextmenu = () => false; }; - initAuthenticationRouters = async () => { - const received = Doc.MainDocId; - if (received && !this.userDoc) { - reaction( - () => Doc.GuestTarget, - target => target && DashboardView.createNewDashboard(), - { fireImmediately: true } - ); - } - // else { - // PromiseValue(this.userDoc.activeDashboard).then(dash => { - // if (dash instanceof Doc) DashboardView.openDashboard(dash); - // else Doc.createNewDashboard(); - // }); - // } - }; - @action createNewPresentation = () => { const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 08285ff0c..34e8cd6dd 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -255,4 +255,5 @@ export class OverlayView extends React.Component { // bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) { OverlayView.Instance.addWindow(, options); + addOverlayWindow('ScriptingRepl', { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); }); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1ead80bd0..9b6554d67 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -26,6 +26,9 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; +import { OverlayView } from '../OverlayView'; +import { ScriptingRepl } from '../ScriptingRepl'; +import { ScriptField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -580,15 +583,21 @@ ScriptingGlobals.add( '(doc: any)' ); ScriptingGlobals.add( - function openOnRight(doc: any) { - return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); + function openDoc(doc: any, where: OpenWhere) { + switch (where) { + case OpenWhere.addRight: + return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); + case OpenWhere.overlay: + if (doc === 'repl') OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); + else Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + } }, - 'opens up document in tab on right side of the screen', + 'opens up document in location specified', '(doc: any)' ); ScriptingGlobals.add( - function openInOverlay(doc: any) { - return Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + function openRepl() { + return 'openRepl'; }, 'opens up document in screen overlay layer', '(doc: any)' diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 48e5748a0..aed88aa1a 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -169,7 +169,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; - if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) { + if (cm && !e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 this.setupViewTypes( 'UI Controls...', diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 2bdcf472f..af2d148e0 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; @@ -172,11 +172,7 @@ export class TreeView extends React.Component { childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); - return ( - (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field - (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field]) - ); // otherwise use the document's data field + return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field]))); } moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { @@ -759,7 +755,7 @@ export class TreeView extends React.Component { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' }; const folderOp = this.childDocs?.length ? [makeFolder] : []; - const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' }; + const openAlias = { script: ScriptField.MakeFunction(`openDoc(getAlias(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open Alias' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; return [ ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 964231e9d..02af30d0c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -80,6 +80,7 @@ export enum OpenWhere { replaceLeft = 'replace:left', inParent = 'inParent', inParentFromScreen = 'inParentFromScreen', + overlay = 'overlay', } export enum OpenWhereMod { none = '', @@ -612,6 +613,7 @@ export class DocumentViewInternal extends DocComponent this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), async selected => { if (selected) { + this._thumbTimer && clearTimeout(this._thumbTimer); this._webPageHasBeenRendered = true; } else if ( (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) @@ -204,7 +209,8 @@ export class WebBox extends ViewBoxAnnotatableComponent() { @computed get numberButton() { const numBtnType: string = StrCast(this.rootDoc.numBtnType); const numScript = ScriptCast(this.rootDoc.script); - const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ value, _readOnly_: false }), 'set num value'); + const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value'); // Script for checking the outcome of the toggle - const checkResult = Number(numScript?.script.run({ value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); + const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); const label = !FontIconBox.GetShowLabels() ? null :
{this.label}
; @@ -150,7 +151,7 @@ export class FontIconBox extends DocComponent() { min={NumCast(this.rootDoc.numBtnMin, 0)} max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult} - className={'menu-slider'} + className="menu-slider" onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))} onPointerUp={() => this._batch?.end()} onChange={e => { @@ -284,27 +285,23 @@ export class FontIconBox extends DocComponent() { text = 'User Default'; } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; - } else if (script?.script.originalScript.startsWith('setFont')) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text']; - } + } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); } catch (e) { console.log(e); } // Get items to place into the list const list = this.buttonList - .filter(value => !Doc.noviceMode || noviceList.includes(value)) + .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) .map(value => (
script.script.run({ value }))}> + onClick={undoBatch(() => script.script.run({ self: this.rootDoc, value }))}> {value[0].toUpperCase() + value.slice(1)}
)); @@ -357,7 +354,7 @@ export class FontIconBox extends DocComponent() { ev.preventDefault(); ev.stopPropagation(); const s = this.colorScript; - s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)(); + s && undoBatch(() => s.script.run({ self: this.rootDoc, value: Utils.colorString(value), _readOnly_: false }).result)(); }; const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']; return ; @@ -368,7 +365,7 @@ export class FontIconBox extends DocComponent() { @computed get colorButton() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? 'transparent'; + const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; const label = !this.label || !FontIconBox.GetShowLabels() ? null : ( @@ -377,13 +374,6 @@ export class FontIconBox extends DocComponent() {
); - // dropdown caret seems superfluous since clicking the color button does the same thing - // const dropdownCaret =
- // - //
; - //setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return (
any; setDoc: () => void; setMode?: () => void }> = new Map([ + ['font', { + checkResult: () => RichTextMenu.Instance?.fontFamily, + setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), + setMode: () => Doc.UserDoc().textAlign = value, + }], + ['highlight', { + checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, + setDoc: () => value && RichTextMenu.Instance.setHighlight(value), + }], + ['fontColor', { + checkResult: () => RichTextMenu.Instance?.fontColor, + setDoc: () => value && RichTextMenu.Instance.setColor(value), + }], + ['fontSize', { + checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), + setDoc: () => { + if (typeof value === 'number') value = value.toString(); + if (value && Number(value).toString() === value) value += 'px'; + RichTextMenu.Instance.setFontSize(value); + }, + }], + ]); if (checkResult) { - return (selected ?? Doc.UserDoc())._fontHighlight; + return map.get(attr)?.checkResult(); } - color && RichTextMenu.Instance.setHighlight(color); + if (editorView?.state) map.get(attr)?.setDoc(); + else map.get(attr)?.setMode?.(); }); -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) { - if (checkResult) { - return RichTextMenu.Instance?.fontSize.replace('px', ''); - } - if (typeof size === 'number') size = size.toString(); - if (size && Number(size).toString() === size) size += 'px'; - RichTextMenu.Instance.setFontSize(size); -}); -ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.noAutoLink : false) ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor(); -}); -ScriptingGlobals.add(function toggleDictation(checkResult?: boolean) { +type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal'; +type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; +ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { const textView = RichTextMenu.Instance?.TextView; - if (checkResult) { - return textView?._recording ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (textView) runInAction(() => (textView._recording = !textView._recording)); -}); - -ScriptingGlobals.add(function toggleBold(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === 'bold') ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleBold(); - else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; -}); - -ScriptingGlobals.add(function toggleUnderline(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === 'underline') ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleUnderline(); - else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; -}); - -ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === 'italics') ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleItalics(); - else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; + const editorView = textView?.EditorView; + // prettier-ignore + const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) => + [ where, { checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false), + toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]); + // prettier-ignore + const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => + [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false), + toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); + // prettier-ignore + const attrs:attrfuncs[] = [ + ['dictation', { checkResult: () => textView?._recording ? true:false, + toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }], + ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), + toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], + ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], + ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], + ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] + + const map = new Map(attrs.concat(alignments).concat(listings)); + if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent'; + map.get(charStyle)?.toggle(); }); export function checkInksToGroup() { @@ -832,62 +771,39 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // toggle: Set overlay status of selected document -ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - if (checkResult) { - if (selected?.type === DocumentType.INK) { - return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent'; - } - return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent'; - } - SetActiveIsInkMask(!ActiveIsInkMask()); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.isInkMask = !doc.isInkMask)); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); - if (checkResult) { - if (selected?.type === DocumentType.INK) { - return StrCast(selected.fillColor); - } - return ActiveFillColor(); - } - SetActiveFillColor(StrCast(color)); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.fillColor = color)); -}); - -ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolean) { - if (checkResult) { - const selected = SelectionManager.Docs().lastElement(); - if (selected?.type === DocumentType.INK) { - return NumCast(selected.strokeWidth); - } - return ActiveInkWidth(); - } - SetActiveInkWidth(width.toString()); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.strokeWidth = Number(width))); -}); + // prettier-ignore + const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ + ['inkMask', { + checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'), + setInk: (doc: Doc) => (doc.isInkMask = !doc.isInkMask), + setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), + }], + ['fillColor', { + checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ? Colors.MEDIUM_BLUE : 'transparent'), + setInk: (doc: Doc) => (doc.fillColor = StrCast(value)), + setMode: () => SetActiveFillColor(StrCast(value)), + }], + [ 'strokeWidth', { + checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.strokeWidth) : ActiveInkWidth()), + setInk: (doc: Doc) => (doc.strokeWidth = NumCast(value)), + setMode: () => SetActiveInkWidth(value.toString()), + }], + ['strokeColor', { + checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.color) : ActiveInkColor()), + setInk: (doc: Doc) => (doc.color = String(value)), + setMode: () => SetActiveInkColor(StrCast(value)), + }], + ]); -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boolean) { if (checkResult) { - const selected = SelectionManager.Docs().lastElement(); - if (selected?.type === DocumentType.INK) { - return StrCast(selected.color); - } - return ActiveInkColor(); + return map.get(option)?.checkResult(); } - SetActiveInkColor(StrCast(color)); + map.get(option)?.setMode(); SelectionManager.Docs() .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.color = String(color))); + .map(doc => map.get(option)?.setInk(doc)); }); /** WEB diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ee164ab31..de94ed5db 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -33,20 +33,19 @@ export namespace Field { return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { - if (typeof field === 'string') { - if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s - return `"${field}"`; + switch (typeof field) { + case 'string': + if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s + return `"${field}"`; + case 'number': + case 'boolean': + return String(field); } - if (typeof field === 'number' || typeof field === 'boolean') return String(field); - if (field === undefined || field === null) return 'null'; - return field[ToScriptString](); + return field?.[ToScriptString]?.() ?? 'null'; } export function toString(field: Field): string { - if (typeof field === 'string') return field; - if (typeof field === 'number' || typeof field === 'boolean') return String(field); - if (field instanceof ObjectField) return field[ToString](); - if (field instanceof RefField) return field[ToString](); - return ''; + if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field); + return field?.[ToString]?.() || ''; } export function IsField(field: any): field is Field; export function IsField(field: any, includeUndefined: true): field is Field | undefined; @@ -79,17 +78,14 @@ export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } -export function NumListCast(field: FieldResult) { - return Cast(field, listSpec('number'), []); +export function NumListCast(field: FieldResult, defaultVal: number[] = []) { + return Cast(field, listSpec('number'), defaultVal); } -export function StrListCast(field: FieldResult) { - return Cast(field, listSpec('string'), []); +export function StrListCast(field: FieldResult, defaultVal: string[] = []) { + return Cast(field, listSpec('string'), defaultVal); } -export function DocListCast(field: FieldResult) { - return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; -} -export function DocListCastOrNull(field: FieldResult) { - return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; +export function DocListCast(field: FieldResult, defaultVal: Doc[] = []) { + return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[]; } export const WidthSym = Symbol('Width'); @@ -153,17 +149,8 @@ export function updateCachedAcls(doc: Doc) { } @scriptingGlobal -@Deserializable('Doc', updateCachedAcls).withFields(['id']) +@Deserializable('Doc', updateCachedAcls, ['id']) export class Doc extends RefField { - //TODO tfs: these should be temporary... - private static mainDocId: string | undefined; - public static get MainDocId() { - return this.mainDocId; - } - public static set MainDocId(id: string | undefined) { - this.mainDocId = id; - } - @observable public static CurrentlyLoading: Doc[]; // removes from currently loading display @action @@ -582,21 +569,13 @@ export namespace Doc { // compare whether documents or their protos match export function AreProtosEqual(doc?: Doc, other?: Doc) { - if (!doc || !other) return false; - const r = doc === other; - const r2 = Doc.GetProto(doc) === other; - const r3 = Doc.GetProto(other) === doc; - const r4 = Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined; - return r || r2 || r3 || r4; + return doc && other && Doc.GetProto(doc) === Doc.GetProto(other); } // Gets the data document for the document. Note: this is mis-named -- it does not specifically // return the doc's proto, but rather recursively searches through the proto inheritance chain // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype'). export function GetProto(doc: Doc): Doc { - if (doc instanceof Promise) { - // console.log("GetProto: warning: got Promise insead of Doc"); - } const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc); return proto === doc ? proto : Doc.GetProto(proto); } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index b5eca78dd..feb419597 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -103,30 +103,9 @@ export class ScriptField extends ObjectField { } this.rawscript = rawscript; this.setterscript = setterscript; - this.script = script ?? (CompileScript('false', { addReturn: true }) as CompiledScript); + this.script = script ?? ScriptField.GetScriptFieldCache('false:') ?? (CompileScript('false', { addReturn: true }) as CompiledScript); } - // init(callback: (res: Field) => any) { - // const options = this.options!; - // const keys = Object.keys(options.options.capturedIds); - // Server.GetFields(keys).then(fields => { - // let captured: { [name: string]: Field } = {}; - // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); - // const opts: ScriptOptions = { - // addReturn: options.options.addReturn, - // params: options.options.params, - // requiredType: options.options.requiredType, - // capturedVariables: captured - // }; - // const script = CompileScript(options.script, opts); - // if (!script.compiled) { - // throw new Error("Can't compile script"); - // } - // this._script = script; - // callback(this); - // }); - // } - [Copy](): ObjectField { return new ScriptField(this.script, this.setterscript, this.rawscript); } @@ -172,7 +151,7 @@ export class ComputedField extends ScriptField { _lastComputedResult: any; //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); + _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) ?? doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); [ToValue](doc: Doc) { return ComputedField.toValue(doc, this); @@ -181,12 +160,8 @@ export class ComputedField extends ScriptField { return new ComputedField(this.script, this.setterscript, this.rawscript); } - public static MakeScript(script: string, params: object = {}) { - const compiled = ScriptField.CompileScript(script, params, false); - return compiled.compiled ? new ComputedField(compiled) : undefined; - } public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { - const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); + const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables }); const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined; return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined; diff --git a/src/fields/util.ts b/src/fields/util.ts index 6ff13d5d3..70d9ed61f 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -48,15 +48,11 @@ export function TraceMobx() { } const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { - if (SerializationHelper.IsSerializing()) { + if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') { target[prop] = value; return true; } - if (typeof prop === 'symbol') { - target[prop] = value; - return true; - } if (value !== undefined) { value = value[SelfProxy] || value; } @@ -95,12 +91,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number delete target.__fields[prop]; } else { target.__fieldKeys && (target.__fieldKeys[prop] = true); - // if (target.__fields[prop] !== value) { - // console.log("ASSIGN " + prop + " " + value); - // } target.__fields[prop] = value; } - //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; if (writeToServer) { if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } }); @@ -143,13 +135,6 @@ export function denormalizeEmail(email: string) { return email.replace(/__/g, '.'); } -// playground mode allows the user to add/delete documents or make layout changes without them saving to the server -// let playgroundMode = false; - -// export function togglePlaygroundMode() { -// playgroundMode = !playgroundMode; -// } - /** * Copies parent's acl fields to the child */ @@ -317,7 +302,6 @@ export function setter(target: any, in_prop: string | symbol | number, value: an if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; - // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { if (!prop.startsWith('__')) prop = prop.substring(1); @@ -377,9 +361,9 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea export function deleteProperty(target: any, prop: string | number | symbol) { if (typeof prop === 'symbol') { delete target[prop]; - return true; + } else { + target[SelfProxy][prop] = undefined; } - target[SelfProxy][prop] = undefined; return true; } -- cgit v1.2.3-70-g09d2 From 8aeab8c3f25c556e39f3e9e58f9c321e79459df8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Mar 2023 12:22:03 -0400 Subject: fixed scriptingbox to remove script if text is empty. fixed scripting with capturedvariables not to cache scripts with lists of captured documents. fixed runtime warnings with stackedTimelined and AudioBox --- src/client/util/Scripting.ts | 8 +++++++- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/MainView.tsx | 3 +-- src/client/views/StyleProvider.tsx | 2 +- src/client/views/TemplateMenu.tsx | 1 - .../views/collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 10 +++------- src/client/views/nodes/DocumentView.tsx | 4 ---- src/client/views/nodes/ScriptingBox.tsx | 16 +++++++++------- src/fields/Doc.ts | 9 +-------- 10 files changed, 24 insertions(+), 33 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index d32298c83..f17a98616 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -7,7 +7,9 @@ import * as typescriptlib from '!!raw-loader!./type_decls.d'; import * as ts from 'typescript'; import { Doc, Field } from '../../fields/Doc'; +import { ToScriptString } from '../../fields/FieldSymbols'; import { ObjectField } from '../../fields/ObjectField'; +import { RefField } from '../../fields/RefField'; import { ScriptField } from '../../fields/ScriptField'; import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals'; export { ts }; @@ -180,7 +182,11 @@ function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, inde export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { const captured = options.capturedVariables ?? {}; - const signature = Object.keys(captured).reduce((p, v) => p + `${v}=${captured[v] instanceof ObjectField ? 'XXX' : captured[v].toString()}`, ''); + const signature = Object.keys(captured).reduce((p, v) => { + const formatCapture = (obj: any) => `${v}=${obj instanceof RefField ? 'XXX' : obj.toString()}`; + if (captured[v] instanceof Array) return p + (captured[v] as any).map(formatCapture); + return p + formatCapture(captured[v]); + }, ''); const found = ScriptField.GetScriptFieldCache(script + ':' + signature); if (found) return found as CompiledScript; const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index af868ba9c..9389fed01 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -518,7 +518,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ) }>
- {} +
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2e04ca3dd..118635a38 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -10,7 +10,7 @@ import 'normalize.css'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; -import { DocCast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -57,7 +57,6 @@ import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview } from './nodes/LinkDocPreview'; import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; -import { PresBox } from './nodes/trails'; import { OverlayView } from './OverlayView'; import { AnchorMenu } from './pdf/AnchorMenu'; import { PreviewCursor } from './PreviewCursor'; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index dba5d703f..b11117b11 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -5,7 +5,7 @@ import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt } from '../../fields/Doc'; import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; -import { DashColor, emptyFunction, lightOrDark } from '../../Utils'; +import { DashColor, lightOrDark } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 863829a51..681ff66e0 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,7 +1,6 @@ import { action, computed, observable, ObservableSet, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../fields/Doc'; -import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index f9249e7d5..302d4a464 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -565,7 +565,7 @@ export class CollectionStackedTimeline extends CollectionSubView Math.max(m, o.level), 0) + 2; - return ( + return this.clipDuration === 0 ? null : (
{ - e.stopPropagation(); - }} - onChange={(e: React.ChangeEvent) => { - this.zoom(Number(e.target.value)); - }} + onPointerDown={(e: React.PointerEvent) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => this.zoom(Number(e.target.value))} />
)} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6ae102a0c..b7a760c1e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1489,10 +1489,6 @@ export class DocumentViewInternal extends DocComponent (params[p.split(':')[0].trim()] = p.split(':')[1].trim())); - const result = CompileScript(this.rawText, { - editable: true, - transformer: DocumentIconContainer.getTransformer(), - params, - typecheck: false, - }); - Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : new ScriptField(undefined, undefined, this.rawText), true); + const result = !this.rawText.trim() + ? ({ compiled: false, errors: undefined } as any) + : CompileScript(this.rawText, { + editable: true, + transformer: DocumentIconContainer.getTransformer(), + params, + typecheck: false, + }); + Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : undefined, true); this.onError(result.compiled ? undefined : result.errors); return result.compiled; }; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 3169031b4..44314dca2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -518,20 +518,13 @@ export namespace Doc { } export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { if (key.startsWith('_')) key = key.substring(1); - const hasProto = doc.proto instanceof Doc; + const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined; const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; if (onDeleg || !hasProto || (!onProto && !defaultProto)) { doc[key] = value; } else doc.proto![key] = value; } - export async function SetOnPrototype(doc: Doc, key: string, value: Field) { - const proto = Object.getOwnPropertyNames(doc).indexOf('isPrototype') === -1 ? doc.proto : doc; - - if (proto) { - proto[key] = value; - } - } export function GetAllPrototypes(doc: Doc): Doc[] { const protos: Doc[] = []; let d: Opt = doc; -- cgit v1.2.3-70-g09d2 From 2a4e86be98ad0f8d4aa4cb09b982d448b542d916 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Mar 2023 14:20:09 -0400 Subject: removed gitlike and branch stuff. updated fortawesome. added Z ordering buttons. moved ctrl-t/alt-t to edit text title into formattedTextBox --- package-lock.json | 66 +++--- package.json | 10 +- src/client/documents/Gitlike.ts | 226 ++++++++++----------- src/client/util/CurrentUserUtils.ts | 8 + src/client/util/DragManager.ts | 7 + src/client/views/MainView.tsx | 4 + .../views/collections/CollectionDockingView.tsx | 3 +- src/client/views/collections/CollectionView.tsx | 38 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 + src/client/views/nodes/DocumentView.tsx | 32 +-- src/client/views/nodes/button/ButtonScripts.ts | 16 -- src/client/views/nodes/button/FontIconBox.tsx | 1 - .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/fields/Doc.ts | 26 ++- 14 files changed, 211 insertions(+), 236 deletions(-) delete mode 100644 src/client/views/nodes/button/ButtonScripts.ts (limited to 'src/client/views/MainView.tsx') diff --git a/package-lock.json b/package-lock.json index cc51ad9e0..18a435a41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -698,67 +698,53 @@ } }, "@fortawesome/fontawesome-common-types": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz", - "integrity": "sha512-CA3MAZBTxVsF6SkfkHXDerkhcQs0QPofy43eFdbWJJkZiq3SfiaH1msOkac59rQaqto5EqWnASboY1dBuKen5w==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", + "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" }, "@fortawesome/fontawesome-svg-core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz", - "integrity": "sha512-UIL6crBWhjTNQcONt96ExjUnKt1D68foe3xjEensLDclqQ6YagwCRYVQdrp/hW0ALRp/5Fv/VKw+MqTUWYYvPg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz", + "integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.3.0" + "@fortawesome/fontawesome-common-types": "6.3.0" } }, "@fortawesome/free-brands-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz", - "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz", + "integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - }, - "dependencies": { - "@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" - } + "@fortawesome/fontawesome-common-types": "6.3.0" } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", - "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz", + "integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" + "@fortawesome/fontawesome-common-types": "6.3.0" }, "dependencies": { "@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", + "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" } } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", - "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz", + "integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - }, - "dependencies": { - "@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" - } + "@fortawesome/fontawesome-common-types": "6.3.0" } }, "@fortawesome/react-fontawesome": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", - "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", "requires": { "prop-types": "^15.8.1" } @@ -20903,7 +20889,7 @@ "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "optional": true, "requires": { "memory-pager": "^1.0.2" diff --git a/package.json b/package.json index 2c4c41917..41bdd5f8f 100644 --- a/package.json +++ b/package.json @@ -129,11 +129,11 @@ "dependencies": { "@ffmpeg/core": "0.10.0", "@ffmpeg/ffmpeg": "0.10.0", - "@fortawesome/fontawesome-svg-core": "^1.3.0", - "@fortawesome/free-brands-svg-icons": "^5.15.4", - "@fortawesome/free-regular-svg-icons": "^5.15.4", - "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@fortawesome/react-fontawesome": "^0.1.19", + "@fortawesome/fontawesome-svg-core": "^6.3.0", + "@fortawesome/free-brands-svg-icons": "^6.3.0", + "@fortawesome/free-regular-svg-icons": "^6.3.0", + "@fortawesome/free-solid-svg-icons": "^6.3.0", + "@fortawesome/react-fontawesome": "^0.2.0", "@hig/flyout": "^1.3.1", "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.23.1", diff --git a/src/client/documents/Gitlike.ts b/src/client/documents/Gitlike.ts index 575c984f5..5e2baf924 100644 --- a/src/client/documents/Gitlike.ts +++ b/src/client/documents/Gitlike.ts @@ -1,118 +1,118 @@ -import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { Cast, DateCast } from "../../fields/Types"; -import { DateField } from "../../fields/DateField"; -import { Id } from "../../fields/FieldSymbols"; +// import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc"; +// import { List } from "../../fields/List"; +// import { Cast, DateCast } from "../../fields/Types"; +// import { DateField } from "../../fields/DateField"; +// import { Id } from "../../fields/FieldSymbols"; -// synchs matching documents on the two branches that are being merged/pulled -// currently this just synchs the main 'fieldKey' component of the data since -// we don't have individual timestamps for all fields -- this is a problematic design issue. -function GitlikeSynchDocs(bd: Doc, md: Doc) { - const fieldKey = Doc.LayoutFieldKey(md); - const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date; - const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date; - const bdproto = bd && Doc.GetProto(bd); - if (bdate !== mdate && bdate <= mdate) { - if (bdproto && md) { - bdproto[fieldKey] = Field.Copy(md[fieldKey]); - bdproto[`${fieldKey}-lastModified`] = new DateField(); - } - } - const bldate = DateCast(bd._lastModified)?.date; - const mldate = DateCast(md._lastModified)?.date; - if (bldate === mldate || bldate > mldate) return; - if (bdproto && md) { - bd.x = Field.Copy(md.x); - bd.y = Field.Copy(md.y); - bd.width = Field.Copy(md.width); - bd.height = Field.Copy(md.height); - bdproto._lastModified = new DateField(); - } -} +// // synchs matching documents on the two branches that are being merged/pulled +// // currently this just synchs the main 'fieldKey' component of the data since +// // we don't have individual timestamps for all fields -- this is a problematic design issue. +// function GitlikeSynchDocs(bd: Doc, md: Doc) { +// const fieldKey = Doc.LayoutFieldKey(md); +// const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date; +// const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date; +// const bdproto = bd && Doc.GetProto(bd); +// if (bdate !== mdate && bdate <= mdate) { +// if (bdproto && md) { +// bdproto[fieldKey] = Field.Copy(md[fieldKey]); +// bdproto[`${fieldKey}-lastModified`] = new DateField(); +// } +// } +// const bldate = DateCast(bd._lastModified)?.date; +// const mldate = DateCast(md._lastModified)?.date; +// if (bldate === mldate || bldate > mldate) return; +// if (bdproto && md) { +// bd.x = Field.Copy(md.x); +// bd.y = Field.Copy(md.y); +// bd.width = Field.Copy(md.width); +// bd.height = Field.Copy(md.height); +// bdproto._lastModified = new DateField(); +// } +// } -// pulls documents onto a branch from the branch's master -// if a document exists on master but not on the branch, it is branched and added -// NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp. -async function GitlikePullFromMaster(branch: Doc, suffix = "") { - const masterMain = Cast(branch.branchOf, Doc, null); - // get the set of documents on both the branch and master - const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]); - const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); - // get the master documents that correspond to the branch documents - const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd); - const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc)); - // get documents on master that don't have a corresponding master doc (form a branch doc), and ... - const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md))); - const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md))); - oldDocsFromMaster?.forEach(md => { - const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md); - bd && GitlikeSynchDocs(bd, md); - }); - const cloneMap = new Map(); cloneMap.set(masterMain[Id], branch); - // make branch clones of them, then add them to the branch - const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []); - newlyBranchedDocs.forEach(nd => { - Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd); - nd.context = branch; - }); - // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted. - const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context); - // so then remove all the deleted main docs from this branch. - remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd)); -} +// // pulls documents onto a branch from the branch's master +// // if a document exists on master but not on the branch, it is branched and added +// // NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp. +// async function GitlikePullFromMaster(branch: Doc, suffix = "") { +// const masterMain = Cast(branch.branchOf, Doc, null); +// // get the set of documents on both the branch and master +// const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]); +// const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); +// // get the master documents that correspond to the branch documents +// const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd); +// const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc)); +// // get documents on master that don't have a corresponding master doc (form a branch doc), and ... +// const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md))); +// const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md))); +// oldDocsFromMaster?.forEach(md => { +// const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md); +// bd && GitlikeSynchDocs(bd, md); +// }); +// const cloneMap = new Map(); cloneMap.set(masterMain[Id], branch); +// // make branch clones of them, then add them to the branch +// const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []); +// newlyBranchedDocs.forEach(nd => { +// Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd); +// nd.context = branch; +// }); +// // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted. +// const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context); +// // so then remove all the deleted main docs from this branch. +// remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd)); +// } -// merges all branches from the master branch by first merging the top-level collection of documents, -// and then merging all the annotations on those documents. -// TODO: need to add an incrementing timestamp whenever anything merges. don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp. -async function GitlikeMergeWithMaster(master: Doc, suffix = "") { - const branches = await DocListCastAsync(master.branches); - branches?.map(async branch => { - const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); - branchChildren && await Promise.all(branchChildren.map(async bd => { - const cloneMap = new Map(); cloneMap.set(master[Id], branch); - // see if the branch's child exists on master. - const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone; - // if the branch's child didn't exist on master, we make a branch clone of the child to add to master. - // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields - // on the branch child and master clone. - if (masterChild.branchOf) { - const branchDocProto = Doc.GetProto(bd); - const masterChildProto = Doc.GetProto(masterChild); - const branchTitle = bd.title; - branchDocProto.title = masterChildProto.title; - masterChildProto.title = branchTitle; - masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf' - masterChildProto.branches = new List([bd]); // the master child's branches needs to include the branch child - Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto); // the branch child should not have the master child in its branch list. - branchDocProto.branchOf = masterChild; // the branch child is now a branch of the master child - } - Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op) - masterChild.context = master; - GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd); - })); - const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]); - masterChildren?.forEach(mc => { // see if any master children - if (!branchChildren?.find(bc => bc.branchOf === mc)) { // are not in the list of children for this branch. - Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it. - mc.context = undefined; // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp - } - }); - }); -} +// // merges all branches from the master branch by first merging the top-level collection of documents, +// // and then merging all the annotations on those documents. +// // TODO: need to add an incrementing timestamp whenever anything merges. don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp. +// async function GitlikeMergeWithMaster(master: Doc, suffix = "") { +// const branches = await DocListCastAsync(master.branches); +// branches?.map(async branch => { +// const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]); +// branchChildren && await Promise.all(branchChildren.map(async bd => { +// const cloneMap = new Map(); cloneMap.set(master[Id], branch); +// // see if the branch's child exists on master. +// const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone; +// // if the branch's child didn't exist on master, we make a branch clone of the child to add to master. +// // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields +// // on the branch child and master clone. +// if (masterChild.branchOf) { +// const branchDocProto = Doc.GetProto(bd); +// const masterChildProto = Doc.GetProto(masterChild); +// const branchTitle = bd.title; +// branchDocProto.title = masterChildProto.title; +// masterChildProto.title = branchTitle; +// masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf' +// masterChildProto.branches = new List([bd]); // the master child's branches needs to include the branch child +// Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto); // the branch child should not have the master child in its branch list. +// branchDocProto.branchOf = masterChild; // the branch child is now a branch of the master child +// } +// Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op) +// masterChild.context = master; +// GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd); +// })); +// const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]); +// masterChildren?.forEach(mc => { // see if any master children +// if (!branchChildren?.find(bc => bc.branchOf === mc)) { // are not in the list of children for this branch. +// Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it. +// mc.context = undefined; // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp +// } +// }); +// }); +// } -// performs a "git"-like task: pull or merge -// if pull, then target is a specific branch document that will be updated from its associated master -// if merge, then target is the master doc that will merge in all branches associated with it. -// TODO: parameterize 'merge' to specify which branch(es) should be merged. -// extend 'merge' to allow a specific branch to be merge target (not just master); -// make pull/merge be recursive (ie, this func currently just operates on the main doc and its children) -export async function BranchTask(target: Doc, action: "pull" | "merge") { - const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster; - await func(target, ""); - await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations")); - await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar")); -} +// // performs a "git"-like task: pull or merge +// // if pull, then target is a specific branch document that will be updated from its associated master +// // if merge, then target is the master doc that will merge in all branches associated with it. +// // TODO: parameterize 'merge' to specify which branch(es) should be merged. +// // extend 'merge' to allow a specific branch to be merge target (not just master); +// // make pull/merge be recursive (ie, this func currently just operates on the main doc and its children) +// export async function BranchTask(target: Doc, action: "pull" | "merge") { +// const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster; +// await func(target, ""); +// await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations")); +// await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar")); +// } -export async function BranchCreate(target: Doc) { - return (await Doc.MakeClone(target, false, true)).clone; -} \ No newline at end of file +// export async function BranchCreate(target: Doc) { +// return (await Doc.MakeClone(target, false, true)).clone; +// } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c038fd6ab..f9766c3fb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -608,6 +608,13 @@ export class CurrentUserUtils { return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); } + static zTools(): Button[] { + return [ + { title: "Bottom", icon: "arrows-down-to-line", toolTip: "Make doc topmost",btnType: ButtonType.ClickButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost",btnType: ButtonType.ClickButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: 'tab'}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform + ] + } static textTools():Button[] { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, @@ -675,6 +682,7 @@ export class CurrentUserUtils { { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Z", icon: "z", toolTip: "Z order functions", subMenu: CurrentUserUtils.zTools(), expertMode: false, toolType:"tab", funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index a56f87075..5a35fcd53 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -10,7 +10,9 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types import { emptyFunction, Utils } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import * as globalCssVariables from '../views/global/globalCssVariables.scss'; +import { Colors } from '../views/global/globalEnums'; import { DocumentView } from '../views/nodes/DocumentView'; +import { ScriptingGlobals } from './ScriptingGlobals'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; @@ -590,3 +592,8 @@ export namespace DragManager { endDrag?.(); } } + +ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { + if (readOnly) return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE : 'transparent'; + DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); +}); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 118635a38..e755f204b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -477,6 +477,10 @@ export class MainView extends React.Component { fa.faHighlighter, fa.faRemoveFormat, fa.faHandPointUp, + fa.faXRay, + fa.faZ, + fa.faArrowsUpToLine, + fa.faArrowsDownToLine, ] ); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9b6554d67..057c1e30f 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -410,11 +410,10 @@ export class CollectionDockingView extends CollectionSubView() { const _width = Number(getComputedStyle(content).width.replace('px', '')); const _height = Number(getComputedStyle(content).height.replace('px', '')); return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => { - const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title + '-icon', _width, _height, _nativeWidth, _nativeHeight }); const proto = this.dataDoc; // Cast(img.proto, Doc, null)!; proto['thumb-nativeWidth'] = _width; proto['thumb-nativeHeight'] = _height; - this.dataDoc.thumb = new ImageField(iconFile); + proto.thumb = new ImageField(iconFile); }); } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index aed88aa1a..eafa50d27 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -2,7 +2,6 @@ import { computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; import { ObjectField } from '../../../fields/ObjectField'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; @@ -10,13 +9,12 @@ import { TraceMobx } from '../../../fields/util'; import { returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; -import { BranchCreate, BranchTask } from '../../documents/Gitlike'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; -import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; +import { OpenWhere } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; @@ -193,23 +191,23 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' }); - if (!Doc.noviceMode && false) { - optionItems.push({ - description: 'Create Branch', - event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight), - icon: 'project-diagram', - }); - optionItems.push({ - description: 'Pull Master', - event: () => BranchTask(this.rootDoc, 'pull'), - icon: 'project-diagram', - }); - optionItems.push({ - description: 'Merge Branches', - event: () => BranchTask(this.rootDoc, 'merge'), - icon: 'project-diagram', - }); - } + // if (!Doc.noviceMode && false) { + // optionItems.push({ + // description: 'Create Branch', + // event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight), + // icon: 'project-diagram', + // }); + // optionItems.push({ + // description: 'Pull Master', + // event: () => BranchTask(this.rootDoc, 'pull'), + // icon: 'project-diagram', + // }); + // optionItems.push({ + // description: 'Merge Branches', + // event: () => BranchTask(this.rootDoc, 'merge'), + // icon: 'project-diagram', + // }); + // } !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ac90c67a5..0ea614472 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2280,3 +2280,9 @@ ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: bool TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) }) ); }); +ScriptingGlobals.add(function bringToFront() { + SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc)); +}); +ScriptingGlobals.add(function sendToBack(doc: Doc) { + SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true)); +}); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index edf508c95..c1665ea3d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -162,6 +162,7 @@ export interface DocumentViewSharedProps { childHideResizeHandles?: () => boolean; dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt styleProvider: Opt; + setTitleFocus?: () => void; focus: DocFocusFunc; fitWidth?: (doc: Doc) => boolean | undefined; docFilters: () => string[]; @@ -539,18 +540,11 @@ export class DocumentViewInternal extends DocComponent (ffview.ChildDrag = this.props.DocumentView())); DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }, () => @@ -560,22 +554,6 @@ export class DocumentViewInternal extends DocComponent { - if (e.altKey) { - e.stopPropagation(); - e.preventDefault(); - if (e.key === '†' || e.key === 't') { - if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = 'title'; - if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true)); - else if (!this._titleRef.current.setIsFocused(true)) { - // if focus didn't change, focus on interior text... - this._titleRef.current?.setIsFocused(false); - this._componentView?.setFocus?.(); - } - } - } - }; - defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => { const targetMatch = Doc.AreProtosEqual(anchor, this.rootDoc) || // anchor is this document, so anchor's properties apply to this document @@ -585,6 +563,12 @@ export class DocumentViewInternal extends DocComponent { + if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = 'title'; + setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined + }; + onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { let stopPropagate = true; @@ -1176,6 +1160,7 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} @@ -1492,7 +1477,6 @@ export class DocumentViewInternal extends DocComponent (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)} diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts deleted file mode 100644 index b4a382faf..000000000 --- a/src/client/views/nodes/button/ButtonScripts.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { Colors } from "../../global/globalEnums"; - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function changeView(view: string) { - const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; - selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed"); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function toggleOverlay(readOnly?: boolean) { - const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; - if (readOnly) return selected?.Document.z ? Colors.MEDIUM_BLUE : "transparent"; - selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed"); -}); \ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 339887757..cb962cad3 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,5 +1,4 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faAlignRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, observable, runInAction } from 'mobx'; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index da10bb0dc..a2c1195cb 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1669,8 +1669,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (e.altKey) { + if ((e.altKey || e.ctrlKey) && e.key === 't') { e.preventDefault(); + e.stopPropagation(); + this.props.setTitleFocus?.(); return; } const state = this._editorView!.state; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 40da482d2..4d82551db 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -698,7 +698,7 @@ export namespace Doc { // this lists out all the tag ids that can be in a RichTextField that might contain document ids. // if a document is cloned, we need to make sure to clone all of these referenced documents as well; export const DocsInTextFieldIds = ['audioId', 'textId', 'anchorId', 'docId']; - export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], cloneLinks: boolean, asBranch: boolean): Promise { + export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], cloneLinks: boolean): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = new Doc(undefined, true); @@ -714,7 +714,7 @@ export namespace Doc { const list = await Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch))); + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks))); assignKey(new List(clones)); } else { assignKey(ObjectField.MakeCopy(field)); @@ -731,7 +731,7 @@ export namespace Doc { ); const results = docids && (await DocServer.GetRefFields(docids)); const docs = results && Array.from(Object.keys(results)).map(key => DocCast(results[key])); - docs && docs.map(doc => Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); + docs && docs.map(doc => Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks)); rtfs.push({ copy, key, field }); } } @@ -740,7 +740,7 @@ export namespace Doc { const docAtKey = doc[key]; if (docAtKey instanceof Doc) { if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || key === 'annotationOn' || key === 'proto' || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { - assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); + assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks)); } else { assignKey(docAtKey); } @@ -763,14 +763,11 @@ export namespace Doc { cloneLinks || ((cloneMap.has(DocCast(link.anchor1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor1)?.annotationOn)?.[Id])) && (cloneMap.has(DocCast(link.anchor2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor2)?.annotationOn)?.[Id]))) ) { - linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); + linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks)); } }); - Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); - asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc); - if (!Doc.IsPrototype(copy)) { - Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy)); - } + Doc.SetInPlace(copy, 'title', 'CLONE: ' + doc.title, true); + copy.cloneOf = doc; cloneMap.set(doc[Id], copy); Doc.AddFileOrphan(copy); @@ -794,14 +791,15 @@ export namespace Doc { } }); } - export function MakeClones(docs: Doc[], cloneLinks: boolean, asBranch = false, cloneMap: Map = new Map()) { - return docs.map(doc => Doc.MakeClone(doc, cloneLinks, asBranch, cloneMap)); + export function MakeClones(docs: Doc[], cloneLinks: boolean) { + const cloneMap = new Map(); + return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneMap)); } - export async function MakeClone(doc: Doc, cloneLinks = true, asBranch = false, cloneMap: Map = new Map()) { + export async function MakeClone(doc: Doc, cloneLinks = true, cloneMap: Map = new Map()) { const linkMap = new Map(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], cloneLinks, asBranch); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], cloneLinks); const repaired = new Set(); const linkedDocs = Array.from(linkMap.values()); const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; -- cgit v1.2.3-70-g09d2 From 44a6c5cabd35e8f7734d6f70128245ba5379d3c1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Mar 2023 16:17:19 -0400 Subject: fixed opening keyvalue for tabs and sidebar docs. added more topbar context menu buttons for freeform views. --- src/client/util/CurrentUserUtils.ts | 23 +++++++++++++----- src/client/views/MainView.tsx | 5 ++-- .../views/collections/CollectionDockingView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +++++ src/client/views/nodes/button/FontIconBox.tsx | 27 ++++++++++++++++++++++ 5 files changed, 54 insertions(+), 9 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 968c7a79b..814b7b072 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -46,6 +46,7 @@ interface Button { btnList?: List; ignoreClick?: boolean; buttonText?: string; + backgroundColor?: string; // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } @@ -608,11 +609,19 @@ export class CurrentUserUtils { return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); } - static zTools(): Button[] { + static freeTools(): Button[] { return [ - { title: "Bottom", icon: "arrows-down-to-line", toolTip: "Make doc topmost",btnType: ButtonType.ClickButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform - { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost",btnType: ButtonType.ClickButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform - { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + ] + } + static viewTools(): Button[] { + return [ + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Snap\xA0Lines",icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "View\xA0All", icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { @@ -677,12 +686,14 @@ export class CurrentUserUtils { { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available - { title: "Z", icon: "z", toolTip: "Z order functions", subMenu: CurrentUserUtils.zTools(), expertMode: false, toolType:"tab", funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(),expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; @@ -692,7 +703,7 @@ export class CurrentUserUtils { static setupContextMenuButton(params:Button, btnDoc?:Doc) { const reqdOpts:DocumentOptions = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, - backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background + backgroundColor: params.backgroundColor ??"transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background color: Colors.WHITE, system: true, dontUndo: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e755f204b..a30d139be 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -669,7 +669,8 @@ export class MainView extends React.Component { mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); static addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => { const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); - const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; + const keyValue = whereFields[1]?.includes('KeyValue'); + const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { @@ -678,7 +679,7 @@ export class MainView extends React.Component { case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods); - case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue); } }; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e38905cbc..4d000542c 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -185,7 +185,7 @@ export class CollectionDockingView extends CollectionSubView() { public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; - const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); + const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !keyValue); if (tab) { tab.header.parent.setActiveContentItem(tab.contentItem); return true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aea7a3ad9..c80bafe26 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2286,3 +2286,9 @@ ScriptingGlobals.add(function bringToFront() { ScriptingGlobals.add(function sendToBack(doc: Doc) { SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true)); }); +ScriptingGlobals.add(function resetView() { + SelectionManager.Docs().forEach(doc => { + doc._panX = doc._panY = 0; + doc._viewScale = 1; + }); +}); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index cb962cad3..d9364e5b5 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -587,6 +587,33 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); +ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snap lines' | 'clusters' | 'viewAll', checkResult?: boolean) { + const selected = SelectionManager.Docs().lastElement(); + // prettier-ignore + const map: Map<'grid' | 'snap lines' | 'clusters' | 'viewAll', { checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ + ['grid', { + checkResult: (doc:Doc) => doc._backgroundGridShow, + setDoc: (doc:Doc) => doc._backgroundGridShow = !doc._backgroundGridShow, + }], + ['snap lines', { + checkResult: (doc:Doc) => doc.showSnapLines, + setDoc: (doc:Doc) => doc._showSnapLines = !doc._showSnapLines, + }], + ['viewAll', { + checkResult: (doc:Doc) => doc._fitContentsToBox, + setDoc: (doc:Doc) => doc._fitContentsToBox = !doc._fitContentsToBox, + }], + ['clusters', { + checkResult: (doc:Doc) => doc._useClusters, + setDoc: (doc:Doc) => doc._useClusters = !doc._useClusters, + }], + ]); + + if (checkResult) { + return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent'; + } + SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv)); +}); ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: any, checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; const selected = SelectionManager.Docs().lastElement(); -- cgit v1.2.3-70-g09d2 From 8ab9236664c561d54d6a41ecb1eb2eaf6064fc0c Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Mar 2023 18:21:13 -0400 Subject: changed longPress to always select and to show decorations. fixed single/double-click code and cleaned up behavior timeouts. fixed pointer events for tree view editing titles and using as powerpoint. --- src/client/documents/Documents.ts | 11 +- src/client/util/DragManager.ts | 34 ++-- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/InkingStroke.tsx | 1 - src/client/views/MainView.tsx | 17 ++ .../collections/CollectionNoteTakingViewColumn.tsx | 4 +- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- src/client/views/nodes/DocumentContentsView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 197 ++++++++++----------- src/client/views/nodes/KeyValueBox.tsx | 24 ++- src/client/views/nodes/LinkBox.tsx | 54 +++--- src/client/views/nodes/ScriptingBox.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 3 +- src/client/views/nodes/button/FontIconBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 +- src/client/views/pdf/AnchorMenu.tsx | 2 +- 18 files changed, 201 insertions(+), 183 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0b6d4380d..2bc1b5b1d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -228,7 +228,8 @@ export class DocumentOptions { contextMenuScripts?: List; contextMenuLabels?: List; contextMenuIcons?: List; - allowClickBeforeDoubleClick?: boolean; // whether a click function can fire before the timeout for a double click has expired + defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen + waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document @@ -408,6 +409,7 @@ export namespace Docs { nativeDimModifiable: true, nativeHeightUnfrozen: true, forceReflow: true, + defaultDoubleClick: 'ignore', }, }, ], @@ -436,7 +438,7 @@ export namespace Docs { DocumentType.WEB, { layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true }, + options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always' }, }, ], [ @@ -578,14 +580,14 @@ export namespace Docs { DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, - options: {}, + options: { defaultDoubleClick: 'ignore' }, }, ], [ DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: 'icon' }, - options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40 }, + options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 }, }, ], [ @@ -983,6 +985,7 @@ export namespace Docs { I.author = Doc.CurrentUserEmail; I.rotation = 0; I.data = new InkField(points); + I.defaultDoubleClick = 'click'; I.creationDate = new DateField(); I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; //I['acl-Override'] = SharingPermissions.Unset; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c35941ee5..cc116fb46 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,6 +1,6 @@ import { action, observable, runInAction } from 'mobx'; import { DateField } from '../../fields/DateField'; -import { Doc, Field, Opt } from '../../fields/Doc'; +import { Doc, Field, Opt, StrListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { listSpec } from '../../fields/Schema'; @@ -207,24 +207,26 @@ export namespace DragManager { dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; - docDragData.droppedDocuments = await Promise.all( - dragData.draggedDocuments.map(async d => - !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) - ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) - : docDragData.dropAction === 'alias' - ? Doc.BestAlias(d) - : docDragData.dropAction === 'proto' - ? Doc.GetProto(d) - : docDragData.dropAction === 'copy' - ? ( - await Doc.MakeClone(d) - ).clone - : d + docDragData.droppedDocuments = ( + await Promise.all( + dragData.draggedDocuments.map(async d => + !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) + ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) + : docDragData.dropAction === 'alias' + ? Doc.BestAlias(d) + : docDragData.dropAction === 'proto' + ? Doc.GetProto(d) + : docDragData.dropAction === 'copy' + ? ( + await Doc.MakeClone(d) + ).clone + : d + ) ) - ); + ).filter(d => d); !['same', 'proto'].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { - const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec('string'), []); + const dragProps = StrListCast(dragData.draggedDocuments[i].removeDropProperties); const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); remProps.map(prop => (drop[prop] = undefined)); }); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2982f8a99..2d2d3c2f6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -67,8 +67,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P reaction( () => SelectionManager.Views().slice(), action(docs => { - this._showNothing = true; - docs.length > 1 && (this._showNothing = false); // show decorations if multiple docs are selected + this._showNothing = !DocumentView.LongPress && docs.length === 1; // show decorations if multiple docs are selected or we're long pressing this._editingTitle = false; }) ); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 3861331b5..a085b69a5 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -179,7 +179,6 @@ export class InkingStroke extends ViewBoxBaseComponent() { UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); }), action((e: PointerEvent, doubleTap: boolean | undefined) => { - doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick; if (doubleTap) { InkStrokeProperties.Instance._controlButton = true; InkStrokeProperties.Instance._currentPoint = -1; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a30d139be..c84d204d5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -214,6 +214,8 @@ export class MainView extends React.Component { window.removeEventListener('keyup', KeyManager.Instance.unhandle); window.removeEventListener('keydown', KeyManager.Instance.handle); window.removeEventListener('pointerdown', this.globalPointerDown, true); + window.removeEventListener('pointermove', this.globalPointerMove, true); + window.removeEventListener('mouseclick', this.globalPointerClick, true); window.removeEventListener('paste', KeyManager.Instance.paste as any); document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } @@ -485,7 +487,20 @@ export class MainView extends React.Component { ); } + private longPressTimer: NodeJS.Timeout | undefined; + globalPointerClick = action((e: any) => { + this.longPressTimer && clearTimeout(this.longPressTimer); + DocumentView.LongPress = false; + }); + globalPointerMove = action((e: PointerEvent) => { + if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer); + }); globalPointerDown = action((e: PointerEvent) => { + DocumentView.LongPress = false; + this.longPressTimer = setTimeout( + action(() => (DocumentView.LongPress = true)), + 1000 + ); DocumentManager.removeOverlayViews(); Doc.linkFollowUnhighlight(); AudioBox.Enabled = true; @@ -506,6 +521,8 @@ export class MainView extends React.Component { window.addEventListener('dragover', e => e.preventDefault(), false); // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined)); document.addEventListener('pointerdown', this.globalPointerDown, true); + document.addEventListener('pointermove', this.globalPointerMove, true); + document.addEventListener('mouseclick', this.globalPointerClick, true); document.addEventListener( 'click', (e: MouseEvent) => { diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 621e3d93b..28bdd0cb9 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -268,7 +268,7 @@ export class CollectionNoteTakingViewColumn extends React.Component - {!this.props.chromeHidden && type !== DocumentType.PRES ? ( + {!this.props.chromeHidden ? (
@@ -289,7 +289,7 @@ export class CollectionNoteTakingViewColumn extends React.Component { this._lastView = this._view; })} renderDepth={0} - LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString('data') : undefined} + LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined} Document={this._document} DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} ContainingCollectionView={undefined} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index af2d148e0..257428d56 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -305,7 +305,7 @@ export class TreeView extends React.Component { }; public static makeTextBullet() { - const bullet = Docs.Create.TextDocument('-text-', { + const bullet = Docs.Create.TextDocument('', { layout: CollectionView.LayoutString('data'), title: '-title-', treeViewExpandedViewLock: true, @@ -326,7 +326,8 @@ export class TreeView extends React.Component { }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); Doc.GetProto(bullet).data = new List([]); - FormattedTextBox.SelectOnLoad = bullet[Id]; + DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); + return bullet; } @@ -902,6 +903,7 @@ export class TreeView extends React.Component { hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} + onClickScriptDisable="never" docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index be4c2a60e..8104ab1a7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -822,7 +822,8 @@ export class CollectionFreeFormView extends CollectionSubViewawaiting layout

'; - if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString('data')); + if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString()); const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, 'layout')], 'string'); - if (layout === undefined) return this.props.Document.data ? "" : KeyValueBox.LayoutString(this.layoutDoc.proto ? 'proto' : ''); + if (layout === undefined) return this.props.Document.data ? "" : KeyValueBox.LayoutString(); if (typeof layout === 'string') return layout; return '

Loading layout

'; } @@ -273,7 +273,7 @@ export class DocumentContentsView extends React.Component< jsx={layoutFrame} showWarnings={true} onError={(test: any) => { - console.log('DocumentContentsView:' + test); + console.log('DocumentContentsView:' + test, bindings, layoutFrame); }} /> ); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 42c2b28ba..c8f677c5f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -124,6 +124,7 @@ export interface DocComponentView { select?: (ctrlKey: boolean, shiftKey: boolean) => void; 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 + onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) playFrom?: (time: number, endTime?: number) => void; @@ -133,6 +134,7 @@ export interface DocComponentView { setFocus?: () => void; // sets input focus to the componentView componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; incrementalRendering?: () => void; + fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox) fieldKey?: string; annotationKey?: string; getTitle?: () => string; @@ -192,6 +194,7 @@ export interface DocumentViewSharedProps { ignoreAutoHeight?: boolean; forceAutoHeight?: boolean; disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. + onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected pointerEvents?: () => Opt; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document createNewFilterDoc?: () => void; @@ -245,9 +248,10 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent() { public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. - private _cursorTimer: NodeJS.Timeout | undefined; - private _longPress = false; private _disposers: { [name: string]: IReactionDisposer } = {}; + private _doubleClickTimeout: NodeJS.Timeout | undefined; + private _singleClickFunc: undefined | (() => any); + private _longPressSelector: NodeJS.Timeout | undefined; private _downX: number = 0; private _downY: number = 0; private _downTime: number = 0; @@ -257,7 +261,6 @@ export class DocumentViewInternal extends DocComponent(); private _titleRef = React.createRef(); - private _timeout: NodeJS.Timeout | undefined; private _dropDisposer?: DragManager.DragDropDisposer; private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -265,8 +268,6 @@ export class DocumentViewInternal extends DocComponent; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; - @observable _pendingDoubleClick = false; - @observable _cursorPress = false; private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; @@ -331,9 +332,16 @@ export class DocumentViewInternal extends DocComponent 3 || Math.abs(this._downY - touch.clientY) > 3)) { - if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { + if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) { this.cleanUpInteractions(); this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined); } @@ -585,91 +593,76 @@ export class DocumentViewInternal extends DocComponent= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { let stopPropagate = true; let preventDefault = true; - const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name); (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); - if (this._doubleTap && (![DocumentType.FONTICON, DocumentType.PRES].includes(this.props.Document.type as any) || this.onDoubleClickHandler)) { - // && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click - if (this._timeout) { - clearTimeout(this._timeout); - this._pendingDoubleClick = false; - this._timeout = undefined; + if (this._doubleTap) { + if (this.onDoubleClickHandler?.script) { + const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables + // prettier-ignore + const func = () => this.onDoubleClickHandler.script.run( { + this: this.layoutDoc, + self: this.rootDoc, + scriptContext: this.props.scriptContext, + thisContainer: this.props.ContainingCollectionDoc, + documentView: this.props.DocumentView(), + clientX, clientY, altKey, shiftKey, ctrlKey, + value: undefined, + }, console.log ); + UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); + } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) { + UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); + SelectionManager.DeselectAll(); + Doc.UnBrushDoc(this.props.Document); + } else { + this._singleClickFunc?.(); } - if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { - // bcz: hack? don't execute script if you're clicking on a scripting box itself - const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; + this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); + this._doubleClickTimeout = undefined; + this._singleClickFunc = undefined; + } else { + let clickFunc: undefined | (() => any); + if (!this.disableClickScriptFunc && this.onClickHandler?.script) { + const { clientX, clientY, shiftKey, altKey, metaKey } = e; const func = () => - this.onDoubleClickHandler.script.run( + this.onClickHandler?.script.run( { this: this.layoutDoc, self: this.rootDoc, + _readOnly_: false, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.DocumentView(), clientX, clientY, - altKey, shiftKey, - ctrlKey, - value: undefined, + altKey, + metaKey, }, console.log - ); - UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); - } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) { - //UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap'); - UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); - SelectionManager.DeselectAll(); - Doc.UnBrushDoc(this.props.Document); + ).result?.select === true + ? this.props.select(false) + : ''; + clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); + } else if (!this.disableClickScriptFunc && this.allLinks.length && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { + clickFunc = () => { + SelectionManager.DeselectAll(); + LinkFollower.FollowLink(undefined, this.Document, e.altKey); + }; + } else { + if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { + // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part + stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template + } + preventDefault = false; } - } else if (!this._longPress && this.onClickHandler?.script && !isScriptBox()) { - // bcz: hack? don't execute script if you're clicking on a scripting box itself - const { clientX, clientY, shiftKey, altKey, metaKey } = e; - const func = () => - this.onClickHandler?.script.run( - { - this: this.layoutDoc, - self: this.rootDoc, - _readOnly_: false, - scriptContext: this.props.scriptContext, - thisContainer: this.props.ContainingCollectionDoc, - documentView: this.props.DocumentView(), - clientX, - clientY, - shiftKey, - altKey, - metaKey, - }, - console.log - ).result?.select === true - ? this.props.select(false) - : ''; - const clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); - if (this.onDoubleClickHandler && !this.props.Document.allowClickBeforeDoubleClick) { - runInAction(() => (this._pendingDoubleClick = true)); - this._timeout = setTimeout(() => { - this._timeout = undefined; - clickFunc(); - }, 150); - } else clickFunc(); - } else if (!this._longPress && this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { - SelectionManager.DeselectAll(); - this.allLinks.length && LinkFollower.FollowLink(undefined, this.props.Document, e.altKey); - } else { - if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { - // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part - stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template + + this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey)); + if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') { + this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); + this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); } else { - runInAction(() => (this._pendingDoubleClick = true)); - this._timeout = setTimeout( - action(() => { - this._pendingDoubleClick = false; - this._timeout = undefined; - }), - 350 - ); - (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey); + this._singleClickFunc(); + this._singleClickFunc = undefined; } - preventDefault = false; } stopPropagate && e.stopPropagation(); preventDefault && e.preventDefault(); @@ -678,6 +671,7 @@ export class DocumentViewInternal extends DocComponent { + this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000); if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView(); if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return; // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) @@ -689,13 +683,6 @@ export class DocumentViewInternal extends DocComponent { - this._cursorPress = true; - this.props.select(false); - }), - 1000 // long press required duration - ); this._downX = e.clientX; this._downY = e.clientY; this._downTime = Date.now(); @@ -719,44 +706,36 @@ export class DocumentViewInternal extends DocComponent { - if (e.cancelBubble) return; + if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return; if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; - if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { + if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); - this._cursorTimer && clearTimeout(this._cursorTimer); - this._cursorPress = false; - this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType)); - } + this.cleanupPointerEvents(); + this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType)); } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); } }; + cancelMoveEvents = () => document.removeEventListener('pointermove', this.onPointerMove); + cleanupPointerEvents = () => { this.cleanUpInteractions(); - document.removeEventListener('pointermove', this.onPointerMove); + this.cancelMoveEvents(); document.removeEventListener('pointerup', this.onPointerUp); }; @action onPointerUp = (e: PointerEvent): void => { this.cleanupPointerEvents(); - this._longPress = this._cursorPress; - this._cursorTimer && clearTimeout(this._cursorTimer); - this._cursorPress = false; + this._longPressSelector && clearTimeout(this._longPressSelector); const now = Date.now(); if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { @@ -766,6 +745,7 @@ export class DocumentViewInternal extends DocComponent { @@ -1078,7 +1057,7 @@ export class DocumentViewInternal extends DocComponent this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); - onClickFunc = () => this.onClickHandler as any as ScriptField; // bcz: typing HACK. check and fix. + onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler); setHeight = (height: number) => (this.layoutDoc._height = height); setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); isContentActive = (outsideReaction?: boolean) => { @@ -1101,7 +1080,8 @@ export class DocumentViewInternal extends DocComponent Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc)); const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc)); - return !this.props.isSelected() && + return !this.props.LayoutTemplateString && + !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb && !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) && @@ -1129,7 +1109,7 @@ export class DocumentViewInternal extends DocComponent ); } - contentPointerEvents = () => (this.onClickHandler ? 'none' : this.pointerEvents); + contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); return ( @@ -1326,7 +1306,7 @@ export class DocumentViewInternal extends DocComponent @@ -1360,7 +1340,7 @@ export class DocumentViewInternal extends DocComponent {this.innards} - {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ?
: null} + {!this.disableClickScriptFunc && this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ?
: null} {this.widgetDecorations ?? null}
) @@ -1530,6 +1510,7 @@ export class DocumentViewInternal extends DocComponent { public static ROOT_DIV = 'documentView-effectsWrapper'; + @observable public static LongPress = false; public get displayName() { return 'DocumentView(' + this.props.Document?.title + ')'; } // this makes mobx trace() statements more descriptive @@ -1628,7 +1609,7 @@ export class DocumentView extends React.Component { return this.docView?.LayoutFieldKey || 'layout'; } get fitWidth() { - return this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth; + return this.docView?._componentView?.fitWidth?.() ?? this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth; } @computed get hideLinkButton() { diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 0b1de0ff6..9c31ed3e4 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -3,24 +3,24 @@ import { observer } from 'mobx-react'; import { Doc, Field, FieldResult } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; -import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { Cast, DocCast, FieldValue, NumCast } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting'; import { undoBatch } from '../../util/UndoManager'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; +import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import { ImageBox } from './ImageBox'; import './KeyValueBox.scss'; import { KeyValuePair } from './KeyValuePair'; import React = require('react'); -import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; import e = require('express'); -import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { ImageBox } from './ImageBox'; -import { OpenWhere } from './DocumentView'; +import { returnTrue } from '../../../Utils'; export type KVPScript = { script: CompiledScript; @@ -30,8 +30,8 @@ export type KVPScript = { @observer export class KeyValueBox extends React.Component { - public static LayoutString(fieldStr: string) { - return FieldView.LayoutString(KeyValueBox, fieldStr); + public static LayoutString() { + return FieldView.LayoutString(KeyValueBox, 'data'); } private _mainCont = React.createRef(); @@ -39,6 +39,12 @@ export class KeyValueBox extends React.Component { private _keyInput = React.createRef(); private _valInput = React.createRef(); + componentDidMount() { + this.props.setContentView?.(this); + } + onClickScriptDisable: () => 'always' = () => 'always'; + fitWidth = returnTrue; + @observable private rows: KeyValuePair[] = []; @computed get splitPercentage() { diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 43f4b43fb..470f7e803 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,29 +1,39 @@ -import React = require("react"); -import { observer } from "mobx-react"; -import { emptyFunction, returnFalse } from "../../../Utils"; -import { ViewBoxBaseComponent } from "../DocComponent"; -import { StyleProp } from "../StyleProvider"; -import { ComparisonBox } from "./ComparisonBox"; +import React = require('react'); +import { observer } from 'mobx-react'; +import { emptyFunction, returnFalse, returnTrue } from '../../../Utils'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import { StyleProp } from '../StyleProvider'; +import { ComparisonBox } from './ComparisonBox'; import { FieldView, FieldViewProps } from './FieldView'; -import "./LinkBox.scss"; +import './LinkBox.scss'; @observer export class LinkBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(LinkBox, fieldKey); + } isContentActiveFunc = () => this.isContentActive(); + + onClickScriptDisable: () => 'always' = () => 'always'; + componentDidMount() { + this.props.setContentView?.(this); + } render() { - if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true); - return
- -
; + if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true)); + return ( +
+ +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index a6e2c3e90..5683b0fe7 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -8,7 +8,7 @@ import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { returnEmptyString } from '../../../Utils'; +import { returnEmptyString, returnTrue } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; import { CompileScript, ScriptParam } from '../../util/Scripting'; @@ -114,6 +114,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent 'always' = () => 'always'; + @action componentDidMount() { this.props.setContentView?.(this); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f7425b26a..73283263f 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -205,7 +205,6 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}> {this.urlContent} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index d9364e5b5..468bcc4d8 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -817,7 +817,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | ' setMode: () => SetActiveInkWidth(value.toString()), }], ['strokeColor', { - checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.color) : ActiveInkColor()), + checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()), setInk: (doc: Doc) => (doc.color = String(value)), setMode: () => SetActiveInkColor(StrCast(value)), }], diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ddec86606..3ce2366f8 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1477,7 +1477,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent e.stopPropagation(); + onSelectMove = (e: PointerEvent) => { + this.props.DocumentView?.().docView?.cancelMoveEvents(); + e.stopPropagation(); + }; onSelectEnd = (e: PointerEvent) => { document.removeEventListener('pointerup', this.onSelectEnd); document.removeEventListener('pointermove', this.onSelectMove); @@ -1497,10 +1500,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { @@ -1566,8 +1565,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { { fireImmediately: true } ); this._disposer = reaction( - () => SelectionManager.Views(), + () => SelectionManager.Views().slice(), selected => { this._showLinkPopup = false; AnchorMenu.Instance.fadeOut(true); -- cgit v1.2.3-70-g09d2 From dabac69d26c8e8e69bd55466ce221d9e33e36638 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Mar 2023 14:23:17 -0400 Subject: numerous changes to try to simplify event handling in DocumentView - got rid of isContentActive in DocComponent since it's in DocumentView. Including adding 'enableDragWhenActive' , 'onClickScriptDisable', --- src/Utils.ts | 17 +- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 12 +- src/client/views/DocComponent.tsx | 13 -- src/client/views/DocumentDecorations.tsx | 8 +- src/client/views/InkingStroke.tsx | 2 +- src/client/views/MainView.tsx | 8 +- src/client/views/StyleProvider.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 2 + src/client/views/collections/TabDocView.tsx | 4 +- src/client/views/collections/TreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 100 +-------- .../collectionLinear/CollectionLinearView.scss | 10 +- .../collectionLinear/CollectionLinearView.tsx | 18 +- .../collectionSchema/CollectionSchemaView.scss | 3 +- .../collectionSchema/CollectionSchemaView.tsx | 46 ++-- .../collections/collectionSchema/SchemaRowBox.tsx | 8 +- src/client/views/global/globalCssVariables.scss | 3 +- .../views/global/globalCssVariables.scss.d.ts | 2 +- src/client/views/nodes/ColorBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 238 ++++----------------- src/client/views/nodes/FunctionPlotBox.tsx | 2 +- src/client/views/nodes/KeyValueBox.tsx | 15 +- src/client/views/nodes/KeyValuePair.tsx | 9 +- src/client/views/nodes/LinkBox.tsx | 9 +- src/client/views/nodes/ScriptingBox.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/nodes/button/FontIconBox.scss | 10 +- src/client/views/nodes/button/FontIconBox.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 9 +- src/client/views/nodes/trails/PresBox.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 9 - src/fields/Doc.ts | 2 +- src/fields/ScriptField.ts | 2 +- 34 files changed, 171 insertions(+), 421 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index ae1478943..0c7deaf5d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -8,8 +8,12 @@ import { Message } from './server/Message'; import Color = require('color'); export namespace Utils { + export let CLICK_TIME = 300; export let DRAG_THRESHOLD = 4; export let SNAP_THRESHOLD = 10; + export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) { + return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD; + } export function readUploadedFileAsText(inputFile: File) { const temporaryFileReader = new FileReader(); @@ -509,11 +513,22 @@ export function returnTrue() { return true; } +export function returnAlways(): 'always' { + return 'always'; +} +export function returnNever(): 'never' { + return 'never'; +} + +export function returnDefault(): 'default' { + return 'default'; +} + export function returnFalse() { return false; } -export function returnAll() { +export function returnAll(): 'all' { return 'all'; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2bc1b5b1d..c484db0db 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -230,6 +230,7 @@ export class DocumentOptions { contextMenuIcons?: List; defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait + enableDragWhenActive?: boolean; dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document @@ -587,7 +588,7 @@ export namespace Docs { DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: 'icon' }, - options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', hideLinkButton: true, _width: 40, _height: 40 }, + options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', enableDragWhenActive: true, hideLinkButton: true, _width: 40, _height: 40 }, }, ], [ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 814b7b072..b21f53221 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -62,7 +62,7 @@ export class CurrentUserUtils { const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [ { btnOpts: { title: "slide", icon: "address-card" }, - templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, + templateOpts: { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }, template: (opts:DocumentOptions) => Docs.Create.MultirowDocument( [ Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), @@ -95,7 +95,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true, + _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, }; const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; @@ -184,10 +184,10 @@ export class CurrentUserUtils { case DocumentType.IMG : creator = imageBox; break; case DocumentType.FONTICON: creator = fontBox; break; } - const allopts = {system: true, ...opts}; + const allopts = {system: true, onClickScriptDisable: "never", ...opts}; return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), - {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)"}); + {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", }); }; const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts @@ -314,7 +314,7 @@ export class CurrentUserUtils { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, - _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, + _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), }; @@ -324,7 +324,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, - childDocumentsActive: true, childDropAction: 'alias' + childDropAction: 'alias' }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 0b92fd864..9fc1487a0 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -82,12 +82,6 @@ export function ViewBoxBaseComponent

() { return this.props.fieldKey; } - isContentActive = (outsideReaction?: boolean) => - this.props.isContentActive?.() === false - ? false - : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) - ? true - : undefined; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; @@ -135,13 +129,6 @@ export function ViewBoxAnnotatableComponent

() isAnyChildContentActive = () => this._isAnyChildContentActive; - isContentActive = (outsideReaction?: boolean) => - this.props.isContentActive?.() === false - ? false - : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.isAnyChildContentActive() - ? true - : undefined; - lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2d2d3c2f6..985e6f88f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -916,9 +916,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P pointerEvents: 'none', }}> {this._isRotating ? null : ( -

e.preventDefault()}> - } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> -
+ tap to set rotate center, drag to rotate
}> +
e.preventDefault()}> + } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> +
+ )}
{!this._showRotCenter ? null : ( diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index a085b69a5..e3642fdaf 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -473,7 +473,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { dontRegisterView={true} noSidebar={true} dontScale={true} - isContentActive={this.isContentActive} + isContentActive={this.props.isContentActive} />
)} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c84d204d5..f5adc17d0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,7 +40,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; -import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; +import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; import { InkTranscription } from './InkTranscription'; @@ -86,7 +86,7 @@ export class MainView extends React.Component { return this._hideUI ? 0 : 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { - return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); + return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(TOPBAR_HEIGHT.replace('px', '')); } @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; @@ -872,7 +872,7 @@ export class MainView extends React.Component { @computed get docButtons() { return !Doc.MyDockedBtns ? null : ( -
+
dv.rootDoc.showSnapLines) ? null : (
{SnappingManager.horizSnapLines().map(l => ( diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 1b5eb3342..faaa4e1f9 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -89,6 +89,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); @@ -200,7 +201,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt { ContainingCollectionDoc={undefined} onBrowseClick={MainView.Instance.exploreMode} isContentActive={returnTrue} + isDocumentActive={returnFalse} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} styleProvider={DefaultStyleProvider} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 257428d56..99b7549c0 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -573,6 +573,7 @@ export class TreeView extends React.Component {
)}
    { ); } else if (this.treeViewExpandedView === 'fields') { return ( -
      +
        {this.expandedField}
      ); @@ -903,7 +904,8 @@ export class TreeView extends React.Component { hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} - onClickScriptDisable="never" + enableDragWhenActive={true} + onClickScriptDisable="never" // tree docViews have a script to show fields, etc. docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8104ab1a7..aed3683d4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -821,9 +821,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { - if (!e.cancelBubble) { - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - if (myTouches[0]) { - if (Doc.ActiveTool === InkTool.None) { - if (this.tryDragCluster(e, this._hitCluster)) { - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); - document.removeEventListener('pointermove', this.onPointerMove); - return; - } - // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected' - this.pan(myTouches[0]); - } - } - // e.stopPropagation(); - e.preventDefault(); - } - }; - - handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - // pinch zooming - if (!e.cancelBubble) { - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - const pt1 = myTouches[0]; - const pt2 = myTouches[1]; - - if (this.prevPoints.size === 2) { - const oldPoint1 = this.prevPoints.get(pt1.identifier); - const oldPoint2 = this.prevPoints.get(pt2.identifier); - if (oldPoint1 && oldPoint2) { - const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2); - - // if zooming, zoom - if (dir !== 0) { - const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2)); - const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - - // calculate the raw delta value - const rawDelta = dir * (d1 + d2); - - // this floors and ceils the delta value to prevent jitteriness - const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8); - this.zoom(centerX, centerY, delta * window.devicePixelRatio); - this.prevPoints.set(pt1.identifier, pt1); - this.prevPoints.set(pt2.identifier, pt2); - } - // this is not zooming. derive some form of panning from it. - else { - // use the centerx and centery as the "new mouse position" - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY); - - this._lastX = centerX; - this._lastY = centerY; - } - } - } - // e.stopPropagation(); - e.preventDefault(); - } - }; - - @action - handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - if (this.props.isContentActive(true)) { - // const pt1: React.Touch | null = e.targetTouches.item(0); - // const pt2: React.Touch | null = e.targetTouches.item(1); - // // if (!pt1 || !pt2) return; - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - const pt1 = myTouches[0]; - const pt2 = myTouches[1]; - if (pt1 && pt2) { - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - this._lastX = centerX; - this._lastY = centerY; - - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - e.stopPropagation(); - } - } - }; - cleanUpInteractions = () => { this.removeMoveListeners(); this.removeEndListeners(); @@ -1637,7 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10), + () => 5 + this.dimension() + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[WidthSym]() || this.dimension()) + tot + 4, 0) : 0), width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); @@ -180,10 +178,10 @@ export class CollectionLinearView extends CollectionSubView() { ref={r => (dref = r || undefined)} style={{ pointerEvents: 'all', - width: nested ? undefined : NumCast(doc._width), - height: nested ? undefined : NumCast(doc._height), - marginLeft: !nested ? 2.5 : 0, - marginRight: !nested ? 2.5 : 0, + width: NumCast(doc._width), + height: NumCast(doc._height), + marginLeft: 2, + marginRight: 2, // width: NumCast(pair.layout._width), // height: NumCast(pair.layout._height), }}> @@ -199,7 +197,7 @@ export class CollectionLinearView extends CollectionSubView() { rootSelected={this.props.isSelected} removeDocument={this.props.removeDocument} ScreenToLocalTransform={docXf} - PanelWidth={nested ? doc[WidthSym] : this.dimension} + PanelWidth={doc[WidthSym]} PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} renderDepth={this.props.renderDepth + 1} dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} @@ -236,7 +234,7 @@ export class CollectionLinearView extends CollectionSubView() { return (
      -
      +
      {!this.props.Document.linearViewExpandable ? null : ( {isExpanded ? 'Close' : 'Open'}
      } placement="top"> {menuOpener} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 34e591195..1ef2fb4ef 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -8,7 +8,8 @@ .schema-table { background-color: $white; - cursor: default; + cursor: grab; + overflow: scroll; .schema-column-menu, .schema-filter-menu { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 60202a19e..d47c9762c 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; +import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; @@ -46,7 +46,7 @@ export class CollectionSchemaView extends CollectionSubView() { public static _rowHeight: number = 40; public static _minColWidth: number = 25; - public static _rowMenuWidth: number = 100; + public static _rowMenuWidth: number = 60; public static _previewDividerWidth: number = 4; @computed get _selectedDocs() { @@ -364,7 +364,11 @@ export class CollectionSchemaView extends CollectionSubView() { this.dataDoc[this.fieldKey ?? 'data'] = new List([...removed, ...draggedDocs, ...pushedDocs]); this.setSort(undefined); SelectionManager.DeselectAll(); - setTimeout(() => draggedDocs.forEach(doc => DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true))), 100); + draggedDocs.forEach(doc => { + const draggedView = DocumentManager.Instance.getFirstDocumentView(doc); + if (draggedView) DocumentManager.Instance.RemoveView(draggedView); + DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true)); + }); e.stopPropagation(); return true; } @@ -812,26 +816,17 @@ export class CollectionSchemaView extends CollectionSubView() { previewWidthFunc = () => this.previewWidth; render() { return ( -
      { - this.createDashEventsTarget(ele); - }} - onPointerDown={e => { - // this is analogous to the panning code for a freeform view. - // however, schema views don't pan so it does nothing. but it does eat the pointerDown event - // if the content is active to prevent the schema from being dragged - this.isContentActive() && setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false); - }} - onDrop={this.onExternalDrop.bind(this)}> -
      +
      this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}> +
      this.props.isContentActive() && e.stopPropagation()} + ref={r => { + // prevent wheel events from passively propagating up through containers + r?.addEventListener('wheel', (e: WheelEvent) => {}, { passive: false }); + }}>
      -
      { - this._columnMenuIndex && this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true); - }}> +
      (this._columnMenuIndex === -1 ? this.closeColumnMenu() : this.openColumnMenu(-1, true))}>
      @@ -867,8 +862,9 @@ export class CollectionSchemaView extends CollectionSubView() { DataDoc={undefined} fitContentsToBox={returnTrue} dontCenter={'y'} + onClickScriptDisable="always" focus={DocUtils.DefaultFocus} - renderDepth={this.props.renderDepth} + renderDepth={this.props.renderDepth + 1} rootSelected={this.rootSelected} PanelWidth={this.previewWidthFunc} PanelHeight={this.props.PanelHeight} @@ -915,16 +911,22 @@ class CollectionSchemaViewDocs extends React.Component () { return (
      { @@ -103,7 +99,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth, - pointerEvents: !this.isContentActive() ? 'none' : undefined, + pointerEvents: !this.props.isContentActive() ? 'none' : undefined, }}>
      () { const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]()); return (
      e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => e.stopPropagation()} style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 541dddd83..9a3e77e6e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -5,7 +5,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; -import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -15,7 +14,7 @@ import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnNone, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -50,11 +49,9 @@ import { FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { RadialMenu } from './RadialMenu'; -import { ScriptingBox } from './ScriptingBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); -import { KeyValueBox } from './KeyValueBox'; const { Howl } = require('howler'); interface Window { @@ -135,6 +132,7 @@ export interface DocComponentView { componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; incrementalRendering?: () => void; fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox) + overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document fieldKey?: string; annotationKey?: string; getTitle?: () => string; @@ -195,6 +193,9 @@ export interface DocumentViewSharedProps { forceAutoHeight?: boolean; disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected + enableDragWhenActive?: boolean; + waitForDoubleClickToClick?: () => 'never' | 'always' | undefined; + defaultDoubleClick?: () => 'default' | 'ignore' | undefined; pointerEvents?: () => Opt; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document createNewFilterDoc?: () => void; @@ -255,8 +256,6 @@ export class DocumentViewInternal extends DocComponent(); @@ -333,11 +332,12 @@ export class DocumentViewInternal extends DocComponent disposer?.()); } - handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { - this.removeMoveListeners(); - this.removeEndListeners(); - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); - if (RadialMenu.Instance._display === false) { - this.addHoldMoveListeners(); - this.addHoldEndListeners(); - this.onRadialMenu(e, me); - const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; - this._firstX = pt.pageX; - this._firstY = pt.pageY; - } - }; - - handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; - - if (this._firstX === -1 || this._firstY === -1) { - return; - } - if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { - this.handle1PointerHoldEnd(e, me); - } - }; - - handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - this.removeHoldMoveListeners(); - this.removeHoldEndListeners(); - RadialMenu.Instance.closeMenu(); - this._firstX = -1; - this._firstY = -1; - SelectionManager.DeselectAll(); - me.touchEvent.stopPropagation(); - me.touchEvent.preventDefault(); - e.stopPropagation(); - if (RadialMenu.Instance.used) { - this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY); - } - }; - - handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - if (!this.props.isSelected()) { - e.stopPropagation(); - e.preventDefault(); - - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - } - }; - - handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - SelectionManager.DeselectAll(); - if (this.Document.onPointerDown) return; - const touch = me.touchEvent.changedTouches.item(0); - if (touch) { - this._downX = touch.clientX; - this._downY = touch.clientY; - if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { - e.stopPropagation(); - } - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - e.stopPropagation(); - } - }; - - handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - if (e.cancelBubble && this.props.isDocumentActive?.()) { - this.removeMoveListeners(); - } else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { - const touch = me.touchEvent.changedTouches.item(0); - if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { - if (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) { - this.cleanUpInteractions(); - this.startDragging(this._downX, this._downY, this.Document.dropAction ? (this.Document.dropAction as any) : e.ctrlKey || e.altKey ? 'alias' : undefined); - } - } - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); - } - }; - - @action - handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { - const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); - const pt1 = myTouches[0]; - const pt2 = myTouches[1]; - const oldPoint1 = this.prevPoints.get(pt1.identifier); - const oldPoint2 = this.prevPoints.get(pt2.identifier); - const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!); - if (pinching !== 0 && oldPoint1 && oldPoint2) { - const dW = Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX); - const dH = Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY); - const dX = -1 * Math.sign(dW); - const dY = -1 * Math.sign(dH); - - if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { - const doc = Document(this.props.Document); - const layoutDoc = Document(Doc.Layout(this.props.Document)); - let nwidth = Doc.NativeWidth(layoutDoc); - let nheight = Doc.NativeHeight(layoutDoc); - const width = layoutDoc._width || 0; - const height = layoutDoc._height || (nheight / nwidth) * width; - const scale = this.props.ScreenToLocalTransform().Scale * this.NativeDimScaling; - const actualdW = Math.max(width + dW * scale, 20); - const actualdH = Math.max(height + dH * scale, 20); - doc.x = (doc.x || 0) + dX * (actualdW - width); - doc.y = (doc.y || 0) + dY * (actualdH - height); - const fixedAspect = e.ctrlKey || (nwidth && nheight); - if (fixedAspect && (!nwidth || !nheight)) { - Doc.SetNativeWidth(layoutDoc, (nwidth = layoutDoc._width || 0)); - Doc.SetNativeHeight(layoutDoc, (nheight = layoutDoc._height || 0)); - } - if (nwidth > 0 && nheight > 0) { - if (Math.abs(dW) > Math.abs(dH)) { - if (!fixedAspect) { - Doc.SetNativeWidth(layoutDoc, (actualdW / (layoutDoc._width || 1)) * Doc.NativeWidth(layoutDoc)); - } - layoutDoc._width = actualdW; - if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = (nheight / nwidth) * layoutDoc._width; - else layoutDoc._height = actualdH; - } else { - if (!fixedAspect) { - Doc.SetNativeHeight(layoutDoc, (actualdH / (layoutDoc._height || 1)) * Doc.NativeHeight(doc)); - } - layoutDoc._height = actualdH; - if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = (nwidth / nheight) * layoutDoc._height; - else layoutDoc._width = actualdW; - } - } else { - dW && (layoutDoc._width = actualdW); - dH && (layoutDoc._height = actualdH); - dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false); - } - } - e.stopPropagation(); - e.preventDefault(); - } - }; - @action onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; @@ -591,11 +446,12 @@ export class DocumentViewInternal extends DocComponent { - if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { + if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap) { + const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick; if (this.onDoubleClickHandler?.script) { const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables // prettier-ignore @@ -609,7 +465,7 @@ export class DocumentViewInternal extends DocComponent (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); - } else if (!Doc.IsSystem(this.rootDoc) && (this.Document.defaultDoubleClick === undefined || this.Document.defaultDoubleClick === 'default')) { + } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) { UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); @@ -649,15 +505,16 @@ export class DocumentViewInternal extends DocComponent 0)) { - // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template } preventDefault = false; } this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey)); - if ((clickFunc && this.Document.waitForDoubleClickToClick !== 'never') || this.Document.waitForDoubleClickToClick === 'always') { + const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; + if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); } else { @@ -673,55 +530,46 @@ export class DocumentViewInternal extends DocComponent { this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000); - if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView(); - if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return; - // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) - if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) { - if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { - e.stopPropagation(); - if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it - // TODO: check here for panning/inking - } - return; - } + if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView(); + this._downX = e.clientX; this._downY = e.clientY; this._downTime = Date.now(); if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { - // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking + // click events stop here if the document is active and no modes are overriding it + // if this is part of a template, let the event go up to the template root unless right/ctrl clicking if ( - (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && + // prettier-ignore + this.props.isDocumentActive?.() && !this.props.onBrowseClick?.() && !this.Document.ignoreClick && !e.ctrlKey && - (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && + e.button === 0 && + this.pointerEvents !== 'none' && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc) ) { e.stopPropagation(); // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); + + // listen to move events if document content isn't active or document is draggable + if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) { + document.addEventListener('pointermove', this.onPointerMove); + } } - if (this.props.isDocumentActive?.()) { - document.removeEventListener('pointermove', this.onPointerMove); - document.addEventListener('pointermove', this.onPointerMove); - } - document.removeEventListener('pointerup', this.onPointerUp); document.addEventListener('pointerup', this.onPointerUp); } }; @action onPointerMove = (e: PointerEvent): void => { - if (this.layoutDoc._lockedPosition || DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) return; if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; - if (((!this.topMost && this.props.isDocumentActive?.()) || this.layoutDoc.onDragStart) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { + if (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { this.cleanupPointerEvents(); this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType)); } - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); } }; @@ -738,13 +586,11 @@ export class DocumentViewInternal extends DocComponent ); } - @observable _: string = ''; + renderDoc = (style: object) => { TraceMobx(); const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png'); - const isButton = this.props.Document.type === DocumentType.FONTICON; + const background = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'); if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null; return ( this.docContents ?? ( @@ -1399,7 +1245,7 @@ export class DocumentViewInternal extends DocComponent { TraceMobx(); const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; - const isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return this.hidden ? null : (
      { ref={this.ContentRef} style={{ transition: this.props.dataTransition, - transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, - height: - isButton || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.props.forceAutoHeight - ? undefined - : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), + transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, + width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, + height: this.props.forceAutoHeight + ? undefined + : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), }}>
      diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 9c31ed3e4..57018fb93 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -4,8 +4,9 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, DocCast, NumCast } from '../../../fields/Types'; +import { DocCast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; +import { returnAll, returnAlways, returnTrue } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting'; @@ -20,7 +21,6 @@ import './KeyValueBox.scss'; import { KeyValuePair } from './KeyValuePair'; import React = require('react'); import e = require('express'); -import { returnTrue } from '../../../Utils'; export type KVPScript = { script: CompiledScript; @@ -42,8 +42,11 @@ export class KeyValueBox extends React.Component { componentDidMount() { this.props.setContentView?.(this); } - onClickScriptDisable: () => 'always' = () => 'always'; + reverseNativeScaling = returnTrue; + able = returnAlways; fitWidth = returnTrue; + overridePointerEvents = returnAll; + onClickScriptDisable = returnAlways; @observable private rows: KeyValuePair[] = []; @@ -69,9 +72,9 @@ export class KeyValueBox extends React.Component { }; public static CompileKVPScript(value: string): KVPScript | undefined { const eq = value.startsWith('='); - value = eq ? value.substr(1) : value; - const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false; - value = dubEq ? value.substr(2) : value; + value = eq ? value.substring(1) : value; + const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false; + value = dubEq ? value.substring(2) : value; const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index c4adc7f1a..94434dce7 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,19 +1,18 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, Field, Opt } from '../../../fields/Doc'; -import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; +import { Doc, Field } from '../../../fields/Doc'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; +import { DefaultStyleProvider } from '../StyleProvider'; +import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; import React = require('react'); -import { DefaultStyleProvider } from '../StyleProvider'; -import { OpenWhere } from './DocumentView'; // Represents one row in a key value plane diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 470f7e803..46ccdecae 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,6 +1,6 @@ import React = require('react'); import { observer } from 'mobx-react'; -import { emptyFunction, returnFalse, returnTrue } from '../../../Utils'; +import { emptyFunction, returnAlways, returnFalse, returnTrue } from '../../../Utils'; import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { ComparisonBox } from './ComparisonBox'; @@ -12,23 +12,22 @@ export class LinkBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } - isContentActiveFunc = () => this.isContentActive(); - onClickScriptDisable: () => 'always' = () => 'always'; + onClickScriptDisable = returnAlways; componentDidMount() { this.props.setContentView?.(this); } render() { if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => (this.dataDoc.treeViewOpen = true)); return ( -
      +
      'always' = () => 'always'; + onClickScriptDisable = returnAlways; @action componentDidMount() { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 73283263f..d57518a8d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -876,7 +876,7 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}> {this.urlContent} diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 7fe1436c7..f3b43501b 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -158,7 +158,7 @@ width: 100%; border-radius: 100%; flex-direction: column; - margin-top: -4px; + // margin-top: -4px; svg { width: 60% !important; @@ -427,11 +427,11 @@ } .dropbox-background { - width: 100vw; - height: 100vh; - top: 0; + width: 200vw; + height: 200vh; + top: -100vh; z-index: 20; - left: 0; + left: -100vw; background: transparent; position: fixed; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 468bcc4d8..28e6eaf1c 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -618,11 +618,10 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh const editorView = RichTextMenu.Instance?.TextView?.EditorView; const selected = SelectionManager.Docs().lastElement(); // prettier-ignore - const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void; setMode?: () => void }> = new Map([ + const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([ ['font', { checkResult: () => RichTextMenu.Instance?.fontFamily, setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), - setMode: () => Doc.UserDoc().textAlign = value, }], ['highlight', { checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, @@ -645,8 +644,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh if (checkResult) { return map.get(attr)?.checkResult(); } - if (editorView?.state) map.get(attr)?.setDoc(); - else map.get(attr)?.setMode?.(); + map.get(attr)?.setDoc?.(); }); type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal'; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3ce2366f8..ee4249b02 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1470,20 +1470,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this.props.DocumentView?.().docView?.cancelMoveEvents(); - e.stopPropagation(); - }; onSelectEnd = (e: PointerEvent) => { document.removeEventListener('pointerup', this.onSelectEnd); - document.removeEventListener('pointermove', this.onSelectMove); }; onPointerUp = (e: React.PointerEvent): void => { const editor = this._editorView!; @@ -1534,6 +1528,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + if (!this.props.isContentActive()) return; if ((e.nativeEvent as any).handledByInnerReactInstance) { e.stopPropagation(); return; @@ -1876,7 +1871,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this.isContentActive()) { + if (this.props.isContentActive()) { if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault(); e.stopPropagation(); } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index e79e7472a..3376c29a9 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1027,8 +1027,8 @@ export class PresBox extends ViewBoxBaseComponent() { removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => - Doc.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; + isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction); + //.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; /** * For sorting the array so that the order is maintained when it is dropped. diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index eb3087399..c5060a2c2 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -381,7 +381,6 @@ export class PDFViewer extends React.Component { const target = e.target as any; if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) { this._textSelecting = false; - document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called } else { // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee. setTimeout( @@ -391,7 +390,6 @@ export class PDFViewer extends React.Component { this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' }); document.addEventListener('pointerup', this.onSelectEnd); - document.addEventListener('pointermove', this.onSelectMove); } } }; @@ -401,12 +399,6 @@ export class PDFViewer extends React.Component { this.isAnnotating = false; this._marqueeing = undefined; this._textSelecting = true; - document.removeEventListener('pointermove', this.onSelectMove); - }; - - onSelectMove = (e: PointerEvent) => { - this.props.DocumentView?.().docView?.cancelMoveEvents(); - e.stopPropagation(); }; @action @@ -414,7 +406,6 @@ export class PDFViewer extends React.Component { this.isAnnotating = false; clearStyleSheetRules(PDFViewer._annotationStyle); this.props.select(false); - document.removeEventListener('pointermove', this.onSelectMove); document.removeEventListener('pointerup', this.onSelectEnd); const sel = window.getSelection(); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4d82551db..553c4525c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -31,7 +31,7 @@ export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); + return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { switch (typeof field) { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index feb419597..2b8750714 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -114,7 +114,7 @@ export class ScriptField extends ObjectField { } [ToScriptString]() { - return 'script field'; + return this.script.originalScript; } [ToString]() { return this.script.originalScript; -- cgit v1.2.3-70-g09d2 From c5c2c309cd88bbeb2f1b668cb040cffaac9c8470 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 4 Apr 2023 20:12:16 -0400 Subject: fixed using freeformview in sidebar of pdfs. fixed issues with dragging items out of schema and with selecting the schema view by clicking on headers. Fixed a lot of errors caused by using OmitKeys which masks type checking. fixed some pointerevent problems with treeview and freeformview --- src/client/documents/Documents.ts | 22 ++-- src/client/views/DocumentDecorations.tsx | 40 +++--- src/client/views/EditableView.tsx | 4 +- src/client/views/InkStrokeProperties.ts | 1 - src/client/views/InkingStroke.tsx | 13 +- src/client/views/MainView.tsx | 1 + src/client/views/PropertiesView.tsx | 73 ++--------- src/client/views/SidebarAnnos.tsx | 15 +-- src/client/views/StyleProvider.tsx | 2 +- src/client/views/TemplateMenu.tsx | 2 +- .../views/collections/CollectionCarousel3DView.tsx | 7 +- .../views/collections/CollectionCarouselView.tsx | 8 +- .../collections/CollectionStackedTimeline.tsx | 54 +++----- .../views/collections/CollectionTreeView.tsx | 24 ++-- src/client/views/collections/CollectionView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 139 +++++++++++--------- .../collections/collectionFreeForm/MarqueeView.tsx | 2 + .../collectionGrid/CollectionGridView.tsx | 9 +- .../collectionSchema/CollectionSchemaView.tsx | 99 +++------------ .../collectionSchema/SchemaColumnHeader.tsx | 10 +- src/client/views/nodes/AudioBox.tsx | 6 +- src/client/views/nodes/ComparisonBox.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 26 +--- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 12 +- src/client/views/nodes/MapBox/MapBox.tsx | 6 +- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 5 +- src/client/views/nodes/PDFBox.tsx | 141 ++++++++++++++++----- src/client/views/nodes/ScreenshotBox.tsx | 18 ++- src/client/views/nodes/VideoBox.tsx | 12 +- src/client/views/nodes/WebBox.tsx | 14 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 27 ++-- src/client/views/pdf/PDFViewer.tsx | 15 ++- src/fields/Doc.ts | 9 +- 34 files changed, 389 insertions(+), 437 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9b8b9c877..9cb480c4a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1420,41 +1420,39 @@ export namespace DocUtils { export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { let created: Doc | undefined; - let layout: ((fieldKey: string) => string) | undefined; const field = target[fieldKey]; - const resolved = options || {}; + const resolved = options ?? {}; if (field instanceof ImageField) { created = Docs.Create.ImageDocument(field.url.href, resolved); - layout = ImageBox.LayoutString; + created.layout = ImageBox.LayoutString(fieldKey); } else if (field instanceof Doc) { created = field; } else if (field instanceof VideoField) { created = Docs.Create.VideoDocument(field.url.href, resolved); - layout = VideoBox.LayoutString; + created.layout = VideoBox.LayoutString(fieldKey); } else if (field instanceof PdfField) { created = Docs.Create.PdfDocument(field.url.href, resolved); - layout = PDFBox.LayoutString; + created.layout = PDFBox.LayoutString(fieldKey); } else if (field instanceof AudioField) { created = Docs.Create.AudioDocument(field.url.href, resolved); - layout = AudioBox.LayoutString; + created.layout = AudioBox.LayoutString(fieldKey); } else if (field instanceof RecordingField) { created = Docs.Create.RecordingDocument(field.url.href, resolved); - layout = RecordingBox.LayoutString; + created.layout = RecordingBox.LayoutString(fieldKey); } else if (field instanceof InkField) { created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved); - layout = InkingStroke.LayoutString; + created.layout = InkingStroke.LayoutString(fieldKey); } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); - layout = CollectionView.LayoutString; + created.layout = CollectionView.LayoutString(fieldKey); } else if (field instanceof MapField) { created = Docs.Create.MapDocument(DocListCast(field), resolved); - layout = MapBox.LayoutString; + created.layout = MapBox.LayoutString(fieldKey); } else { created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved }); - layout = FormattedTextBox.LayoutString; + created.layout = FormattedTextBox.LayoutString(fieldKey); } if (created) { - created.layout = layout?.(fieldKey); created.title = fieldKey; proto && created.proto && (created.proto = Doc.GetProto(proto)); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 985e6f88f..9bc583ce5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -31,6 +31,7 @@ import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); +import { RichTextField } from '../../fields/RichTextField'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -127,19 +128,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } //@ts-ignore const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle; - Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true); - if (d.rootDoc.syncLayoutFieldWithTitle) { - const title = titleField.toString(); + if (titleField.toString().startsWith('')) { + const title = titleField.toString().replace(/\.?/, ''); const curKey = Doc.LayoutFieldKey(d.rootDoc); - if (curKey !== title && d.dataDoc[title] === undefined) { - d.rootDoc.layout = FormattedTextBox.LayoutString(title); - setTimeout(() => { - const val = d.dataDoc[curKey]; - d.dataDoc[curKey] = undefined; - d.dataDoc[title] = val; - }); + if (curKey !== title) { + if (title) { + if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') { + d.rootDoc.layoutKey = `layout_${title}`; + d.rootDoc[`layout_${title}`] = FormattedTextBox.LayoutString(title); + d.rootDoc[`${title}-nativeWidth`] = d.rootDoc[`${title}-nativeHeight`] = 0; + } + } else { + d.rootDoc.layoutKey = undefined; + } } + } else { + Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true); } }), 'title blur' @@ -453,17 +458,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action onPointerDown = (e: React.PointerEvent): void => { - const views = SelectionManager.Views().map(dv => dv.rootDoc); - this._inkDragDocs = views - .filter(doc => doc.type === DocumentType.INK) - .map(doc => { - if (InkStrokeProperties.Instance._lock) { - Doc.SetNativeHeight(doc, NumCast(doc._height)); - Doc.SetNativeWidth(doc, NumCast(doc._width)); - } - return { doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }; - }); - setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them this._resizeHdlId = e.currentTarget.className; @@ -487,10 +481,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (!first) return false; let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY }; var fixedAspect = Doc.NativeAspect(first.layoutDoc); - InkStrokeProperties.Instance._lock && - SelectionManager.Views() - .filter(dv => dv.rootDoc.type === DocumentType.INK) - .forEach(dv => (fixedAspect = Doc.NativeAspect(dv.rootDoc))); const resizeHdl = this._resizeHdlId.split(' ')[0]; if (fixedAspect && (resizeHdl === 'documentDecorations-bottomRightResizer' || resizeHdl === 'documentDecorations-topLeftResizer')) { diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index bb190e93b..164b6c57a 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -129,7 +129,7 @@ export class EditableView extends React.Component { this._editing = true; this.props.isEditingCallback?.(true); } - e.stopPropagation(); + // e.stopPropagation(); } }; @@ -215,7 +215,7 @@ export class EditableView extends React.Component { className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`} ref={this._ref} style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: 'nowrap', height: this.props.height || 'auto', maxHeight: this.props.maxHeight }} - onPointerDown={e => e.stopPropagation()} + //onPointerDown={this.stopPropagation} onClick={this.onClick} placeholder={this.props.placeholder}> {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 1d8d52425..e6df0801c 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -19,7 +19,6 @@ export class InkStrokeProperties { return this._Instance || new InkStrokeProperties(); } - @observable _lock = false; @observable _controlButton = false; @observable _currentPoint = -1; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index e3642fdaf..2fd6cc4d6 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -25,10 +25,11 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, HeightSym, WidthSym } from '../../fields/Doc'; import { InkData, InkField } from '../../fields/InkField'; -import { BoolCast, Cast, DocCast, NumCast, RTFCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; -import { DashColor, OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils'; import { CognitiveServices } from '../cognitive_services/CognitiveServices'; +import { Docs } from '../documents/Documents'; import { InteractionUtils } from '../util/InteractionUtils'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; @@ -41,13 +42,12 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; import './InkStroke.scss'; import { InkStrokeProperties } from './InkStrokeProperties'; import { InkTangentHandles } from './InkTangentHandles'; -import { DocComponentView, DocFocusOptions, DocumentView } from './nodes/DocumentView'; +import { DocComponentView } from './nodes/DocumentView'; import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { PinProps, PresBox } from './nodes/trails'; import { StyleProp } from './StyleProvider'; import Color = require('color'); -import { Docs } from '../documents/Documents'; -import { PinProps, PresBox } from './nodes/trails'; @observer export class InkingStroke extends ViewBoxBaseComponent() { @@ -464,7 +464,8 @@ export class InkingStroke extends ViewBoxBaseComponent() { //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2, }}> { @observable inOptions: boolean = false; @observable _controlButton: boolean = false; - @observable _lock: boolean = false; componentDidMount() { this.selectedDocListenerDisposer?.(); @@ -586,16 +585,6 @@ export class PropertiesView extends React.Component {
      - {InkStrokeProperties.Instance._lock ? 'Unlock ratio' : 'Lock ratio'}
      }> -
      (InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock))}> - -
      - - {'Rotate 90Ëš'}
      }> -
      this.rotate(Math.PI / 2))}> - -
      -
      ); } @@ -644,9 +633,6 @@ export class PropertiesView extends React.Component { @action upDownButtons = (dirs: string, field: string) => { switch (field) { - case 'rot': - this.rotate(dirs === 'up' ? 0.1 : -0.1); - break; case 'Xps': this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10)); break; @@ -662,7 +648,6 @@ export class PropertiesView extends React.Component { const oldX = NumCast(this.selectedDoc?.x); const oldY = NumCast(this.selectedDoc?.y); this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === 'up' ? 10 : -10)); - InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth) * NumCast(this.selectedDoc?._height)); const doc = this.selectedDoc; if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { const ink = Cast(doc.data, InkField)?.inkData; @@ -684,7 +669,6 @@ export class PropertiesView extends React.Component { const oX = NumCast(this.selectedDoc?.x); const oY = NumCast(this.selectedDoc?.y); this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === 'up' ? 10 : -10)); - InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight) * NumCast(this.selectedDoc?._width)); const docu = this.selectedDoc; if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { const ink = Cast(docu.data, InkField)?.inkData; @@ -704,11 +688,7 @@ export class PropertiesView extends React.Component { }; getField(key: string) { - //if (this.selectedDoc) { return Field.toString(this.selectedDoc?.[key] as Field); - // } else { - // return undefined as Opt; - // } } @computed get shapeXps() { @@ -717,9 +697,6 @@ export class PropertiesView extends React.Component { @computed get shapeYps() { return this.getField('y'); } - @computed get shapeRot() { - return this.getField('rotation'); - } @computed get shapeHgt() { return this.getField('_height'); } @@ -732,18 +709,11 @@ export class PropertiesView extends React.Component { set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); } - set shapeRot(value) { - this.selectedDoc && (this.selectedDoc.rotation = Number(value)); - } set shapeWid(value) { - const oldWidth = NumCast(this.selectedDoc?._width); this.selectedDoc && (this.selectedDoc._width = Number(value)); - InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth); } set shapeHgt(value) { - const oldHeight = NumCast(this.selectedDoc?._height); this.selectedDoc && (this.selectedDoc._height = Number(value)); - InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight); } @computed get hgtInput() { @@ -790,30 +760,6 @@ export class PropertiesView extends React.Component { 'Y:' ); } - @computed get rotInput() { - return this.inputBoxDuo( - 'rot', - this.shapeRot, - (val: string) => { - if (!isNaN(Number(val))) { - this.rotate(Number(val) - Number(this.shapeRot)); - this.shapeRot = val; - } - return true; - }, - '∠:', - 'rot', - this.shapeRot, - (val: string) => { - if (!isNaN(Number(val))) { - this.rotate(Number(val) - Number(this.shapeRot)); - this.shapeRot = val; - } - return true; - }, - '' - ); - } @observable private _fillBtn = false; @observable private _lineBtn = false; @@ -1080,10 +1026,9 @@ export class PropertiesView extends React.Component { @computed get transformEditor() { return (
      - {this.controlPointsButton} + {this.isInk ? this.controlPointsButton : null} {this.hgtInput} {this.XpsInput} - {this.rotInput}
      ); } @@ -1194,17 +1139,15 @@ export class PropertiesView extends React.Component {
      )} - {this.isInk ? ( -
      -
      (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}> - Transform -
      - -
      +
      +
      (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}> + Transform +
      +
      - {this.openTransform ?
      {this.transformEditor}
      : null}
      - ) : null} + {this.openTransform ?
      {this.transformEditor}
      : null} +
      ); } diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 02dd15960..48c0150e6 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -5,7 +5,7 @@ import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { DocCast, NumCast, StrCast } from '../../fields/Types'; -import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils'; +import { emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { LinkManager } from '../util/LinkManager'; @@ -26,6 +26,7 @@ interface ExtraProps { // usePanelWidth: boolean; showSidebar: boolean; nativeWidth: number; + usePanelWidth?: boolean; whenChildContentsActiveChanged: (isActive: boolean) => void; ScreenToLocalTransform: () => Transform; sidebarAddDocument: (doc: Doc | Doc[], suffix: string) => boolean; @@ -158,13 +159,10 @@ export class SidebarAnnos extends React.Component { .ScreenToLocalTransform() .translate(Doc.NativeWidth(this.props.dataDoc), 0) .scale(this.props.NativeDimScaling?.() || 1); - // panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : - // this.props.usePanelWidth ? this.props.PanelWidth() : - // (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth); panelWidth = () => !this.props.showSidebar ? 0 - : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP + : this.props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this.props.layoutDoc.type as any) ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth); panelHeight = () => this.props.PanelHeight() - this.filtersHeight(); @@ -224,20 +222,21 @@ export class SidebarAnnos extends React.Component {
      , props: Opt = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); if (docColor) return docColor; const docView = props?.DocumentView?.(); - const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, 'backgroundColor'); + const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor); if (!backColor) return undefined; return lightOrDark(backColor); case StyleProp.Hidden: diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 681ff66e0..45db240a9 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -79,7 +79,7 @@ export class TemplateMenu extends React.Component { }; componentDidMount() { !this._addedKeys && (this._addedKeys = new ObservableSet()); - Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))) + [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))), ...Array.from(Object.keys(this.props.docViews[0].props.Document))] .filter(key => key.startsWith('layout_')) .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); } diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 57ff1b292..a266c9207 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { Doc } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { OmitKeys, returnFalse, Utils } from '../../../Utils'; +import { returnFalse, returnZero, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { DocumentView } from '../nodes/DocumentView'; import { StyleProp } from '../StyleProvider'; @@ -43,7 +43,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { const displayDoc = (childPair: { layout: Doc; data: Doc }) => { return (
      this.currentTime; - @computed get renderDictation() { - const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); - return !dictation ? null : ( -
      - -
      - ); - } - // renders selection region on timeline @computed get selectionContainer() { const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd; @@ -638,7 +606,6 @@ export class CollectionStackedTimeline extends CollectionSubView )} - {/* {this.renderDictation} */}
      // renders anchor LabelBox renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); - const focusFunc = (doc: Doc, options: DocFocusOptions) => this.props.playLink(mark); + const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => { + this.props.playLink(mark); + return undefined; + }; return { anchor, view: ( (anchor.view = r))} Document={mark} DataDoc={undefined} + docViewPath={returnEmptyDoclist} pointerEvents={this.noEvents ? returnNone : undefined} styleProvider={this.props.styleProvider} renderDepth={this.props.renderDepth + 1} @@ -837,7 +810,16 @@ class StackedTimelineAnchor extends React.Component PanelHeight={height} fitWidth={returnTrue} ScreenToLocalTransform={screenXf} + addDocTab={returnFalse} + pinToPres={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} focus={focusFunc} + isContentActive={returnFalse} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + searchFilterDocs={returnEmptyDoclist} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} rootSelected={returnFalse} onClick={script} onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 553967b95..4a11e8f0b 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils'; +import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -384,12 +384,12 @@ export class CollectionTreeView extends CollectionSubView { + @computed get content() { const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined); const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar; - return [ -
      + return ( +
      {!this.buttonMenu && !this.noviceExplainer ? null : (
      r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}> {this.buttonMenu} @@ -428,9 +428,9 @@ export class CollectionTreeView extends CollectionSubView
      -
      , - ]; - }; +
      + ); + } render() { TraceMobx(); @@ -439,7 +439,11 @@ export class CollectionTreeView extends CollectionSubView {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? ( - {this.contentFunc} + {this.content} ) : ( - this.contentFunc() + this.content )}
      ); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 51624689e..bc25ad43a 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -45,7 +45,7 @@ interface CollectionViewProps_ extends FieldViewProps { // property overrides for child documents childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) - childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) + childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) childFitWidth?: (child: Doc) => boolean; childShowTitle?: () => string; childOpacity?: () => number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aed3683d4..d39668a5d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { DateField } from '../../../../fields/DateField'; @@ -53,10 +53,14 @@ import { MarqueeView } from './MarqueeView'; import React = require('react'); export type collectionFreeformViewProps = { + noPointerWheel?: () => boolean; // turn off pointerwheel interactions (see PDFViewer) + NativeWidth?: () => number; + NativeHeight?: () => number; + originTopLeft?: boolean; annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; childPointerEvents?: string; - scaleField?: string; + viewField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; getScrollHeight?: () => number | undefined; @@ -97,8 +101,14 @@ export class CollectionFreeFormView extends CollectionSubView (this.fitContentsToBox ? true : false); // 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)); + panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1)); + panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], 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)`; + this.props.isAnnotationOverlay && 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(); getContainerTransform = () => this.cachedGetContainerTransform.copy(); @@ -293,7 +301,7 @@ export class CollectionFreeFormView extends CollectionSubView { - options.docTransform = new Transform(-NumCast(this.rootDoc.panX) + NumCast(anchor.x), -NumCast(this.rootDoc.panY) + NumCast(anchor.y), 1); + options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1); const res = this.props.focus(this.rootDoc, options); options.docTransform = undefined; return res; @@ -301,7 +309,7 @@ export class CollectionFreeFormView extends CollectionSubView { const xfToCollection = options?.docTransform ?? Transform.Identity(); - const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; + const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined); @@ -772,7 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale); + this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.scrollTop) * safeScale) || -localTransform.TranslateY / safeScale); } }; @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom + if (this.props.noPointerWheel?.() || this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); @@ -995,7 +1003,7 @@ export class CollectionFreeFormView extends CollectionSubView= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds - else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2; - if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2; - else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2; + const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1); + const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1); + const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1); + const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1); + if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2); + else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2); + if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2); + else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2); } } if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) { @@ -1078,8 +1089,8 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) { this.setPan( - NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale - NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), + NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale + NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true ); @@ -1106,12 +1117,14 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).slice(); docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1; - if (zlast - docs.length > 100) { - for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; - zlast = docs.length + 1; + let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1; + if (docs.lastElement() !== doc) { + if (zlast - docs.length > 100) { + for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; + zlast = docs.length + 1; + } + doc.zIndex = zlast + 1; } - doc.zIndex = zlast + 1; } }; @@ -1134,8 +1147,8 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1158,8 +1171,8 @@ export class CollectionFreeFormView extends CollectionSubView screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0; @@ -1171,8 +1184,8 @@ export class CollectionFreeFormView extends CollectionSubView { if (cbounds) { const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; - const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; + const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])]; const pbounds = { x: cbounds.x - p[0] + c[0], y: cbounds.y - p[1] + c[1], @@ -1511,8 +1524,8 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc._height))) + 20; const dim = Math.ceil(Math.sqrt(docs.length)); docs.forEach((doc, i) => { - doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2; - doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2; + doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2; + doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2; }); }; @@ -1675,7 +1688,7 @@ export class CollectionFreeFormView extends CollectionSubView { - this.props.Document._panX = this.props.Document._panY = 0; + this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: 'compress-arrows-alt', @@ -1795,11 +1808,11 @@ export class CollectionFreeFormView extends CollectionSubView !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); - children = () => { + get children() { this.incrementalRender(); - const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : []; + const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : []; return [...children, ...this.views, ]; - }; + } @computed get placeholder() { return ( @@ -1843,6 +1856,7 @@ export class CollectionFreeFormView extends CollectionSubView this.nativeDimScaling; private groupDropDisposer?: DragManager.DragDropDisposer; protected createGroupEventsTarget = (ele: HTMLDivElement) => { @@ -1912,7 +1927,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.createDashEventsTarget(r); // prevent wheel events from passivly propagating up through containers - r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false }); + !this.props.isAnnotationOverlay && r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false }); }} onWheel={this.onPointerWheel} onClick={this.onClick} @@ -1985,7 +2000,8 @@ interface CollectionFreeFormViewPannableContentsProps { transform: () => string; zoomScaling: () => number; viewDefDivClick?: ScriptField; - children: () => JSX.Element[]; + children?: React.ReactNode | undefined; + //children: () => JSX.Element[]; transition?: string; presPaths: () => JSX.Element | null; presPinView?: boolean; @@ -2079,7 +2095,7 @@ class CollectionFreeFormViewPannableContents extends React.Component - {this.props.children()} + {this.props.children} {!this.props.brushView.width ? null : (
      number; PanelHeight: () => number; isAnnotationOverlay?: boolean; + nativeDimScaling: () => number; zoomScaling: () => number; layoutDoc: Doc; cachedCenteringShiftX: number; @@ -2124,10 +2141,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component { + // if (this.props.pointerEvents?.() === 'none') return; this._downX = this._lastX = e.clientX; this._downY = this._lastY = e.clientY; if (!(e.nativeEvent as any).marqueeHit) { @@ -345,6 +346,7 @@ export class MarqueeView extends React.Component { + if (this.props.pointerEvents?.() === 'none') return; if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { if (Doc.ActiveTool === InkTool.None) { if (!(e.nativeEvent as any).marqueeHit) { diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 9468c5f06..e8ae88ae5 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Doc, Opt } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; @@ -186,7 +186,10 @@ export class CollectionGridView extends CollectionSubView() { getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ( ); } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index d47c9762c..fd9bcf681 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,23 +1,20 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx'; +import { action, computed, observable, ObservableMap, untracked } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc'; +import { computedFn } from 'mobx-utils'; +import { Doc, Field, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; -import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { ImageField } from '../../../../fields/URLField'; import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; -import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; -import { ContextMenuProps } from '../../ContextMenuItem'; import { EditableView } from '../../EditableView'; import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -215,9 +212,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - addNewKey = (key: string, defaultVal: any) => { - this.childDocs.forEach(doc => (doc[key] = defaultVal)); - }; + addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal)); @undoBatch @action @@ -303,9 +298,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - addRowRef = (doc: Doc, ref: HTMLDivElement) => { - this._rowEles.set(doc, ref); - }; + addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref); @action setColRef = (index: number, ref: HTMLDivElement) => { @@ -405,68 +398,12 @@ export class CollectionSchemaView extends CollectionSubView() { menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); - const layoutItems: ContextMenuProps[] = []; - const docItems: ContextMenuProps[] = []; - const dataDoc = this.props.DataDoc || this.props.Document; - - DocUtils.addDocumentCreatorMenuItems( - doc => { - FormattedTextBox.SelectOnLoad = StrCast(doc[Id]); - return this.addRow(doc); - }, - this.addRow, - x, - y, - true - ); - Array.from(Object.keys(Doc.GetProto(dataDoc))) - .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string') - .map(fieldKey => - docItems.push({ - description: ':' + fieldKey, - event: () => { - const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); - if (created) { - if (this.props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.Document); - } - return this.addRow(created); - } - }, - icon: 'compress-arrows-alt', - }) - ); - Array.from(Object.keys(Doc.GetProto(dataDoc))) - .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length) - .map(fieldKey => - docItems.push({ - description: ':' + fieldKey, - event: () => { - const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); - if (created) { - const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; - if (container.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, container); - return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); - } - return this.addRow(created) || false; - } - }, - icon: 'compress-arrows-alt', - }) - ); - !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' }); - !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' }); + DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true); + ContextMenu.Instance.setDefaultItem('::', (name: string): void => { Doc.GetProto(this.props.Document)[name] = ''; - const created = Docs.Create.TextDocument('', { title: name, _autoHeight: true }); - if (created) { - if (this.props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this.props.Document); - } - this.addRow(created); - } + this.addRow(Docs.Create.TextDocument('', { title: name, _autoHeight: true })); }); ContextMenu.Instance.displayMenu(x, y, undefined, true); }; @@ -543,9 +480,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - closeColumnMenu = () => { - this._columnMenuIndex = undefined; - }; + closeColumnMenu = () => (this._columnMenuIndex = undefined); @action openFilterMenu = (index: number) => { @@ -596,19 +531,14 @@ export class CollectionSchemaView extends CollectionSubView() { getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field); removeFieldFilters = (field: string) => { - this.getFieldFilters(field).forEach(filter => { - Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'); - }); + this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove')); }; onFilterKeyDown = (e: React.KeyboardEvent) => { + //prettier-ignore switch (e.key) { - case 'Enter': - this.closeFilterMenu(true); - break; - case 'Escape': - this.closeFilterMenu(false); - break; + case 'Enter' : this.closeFilterMenu(true); break; + case 'Escape': this.closeFilterMenu(false);break; } }; @@ -903,6 +833,7 @@ interface CollectionSchemaViewDocsProps { class CollectionSchemaViewDocs extends React.Component { tableWidthFunc = () => this.props.schema.tableWidth; rowHeightFunc = () => CollectionSchemaView._rowHeight; + childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc())); render() { return (
      @@ -932,7 +863,7 @@ class CollectionSchemaViewDocs extends React.Component @action onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction); + setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false); }; render() { return ( -
      { - col && this.props.setColRef(this.props.columnIndex, col); - }}> +
      col && this.props.setColRef(this.props.columnIndex, col)}>
      this.props.resizeColumn(e, this.props.columnIndex)}>
      {this.fieldKey}
      diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 384975b45..a6acf882c 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -7,7 +7,7 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DateCast, NumCast } from '../../../fields/Types'; import { AudioField, nullAudio } from '../../../fields/URLField'; -import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; @@ -19,7 +19,6 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import './AudioBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; -import { SelectionManager } from '../../util/SelectionManager'; import { PinProps, PresBox } from './trails'; /** @@ -655,7 +654,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent (this._stackedTimeline = r))} - {...OmitKeys(this.props, ['CollectionFreeFormDocumentView']).omit} + {...this.props} + CollectionFreeFormDocumentView={undefined} fieldKey={this.annotationKey} dictationKey={this.fieldKey + '-dictation'} mediaPath={this.path} diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index ecffe6c4f..ace388c57 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -3,7 +3,7 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -122,7 +122,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { //whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); }} - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} + {...this.props} + NativeWidth={returnZero} + NativeHeight={returnZero} isContentActive={returnFalse} isDocumentActive={returnFalse} styleProvider={this.docStyleProvider} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 091a6b050..c7bf37a45 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -12,7 +12,7 @@ import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; -import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -512,7 +512,6 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); - !Doc.noviceMode && - appearanceItems.push({ - description: 'Add a Field', - event: () => { - const alias = Doc.MakeAlias(this.rootDoc); - alias.layout = FormattedTextBox.LayoutString('newfield'); - alias.title = 'newfield'; - alias._height = 35; - alias._width = 100; - alias.syncLayoutFieldWithTitle = true; - alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width); - alias.y = NumCast(this.rootDoc.y); - this.props.addDocument?.(alias); - }, - icon: 'eye', - }); LinkManager.Links(this.Document).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' }); !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); @@ -863,7 +846,7 @@ export class DocumentViewInternal extends DocComponent (this.disableClickScriptFunc ? undefined : this.onClickHandler); setHeight = (height: number) => (this.layoutDoc._height = height); setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); - isContentActive = (outsideReaction?: boolean) => { + isContentActive = (outsideReaction?: boolean): boolean | undefined => { return this.props.isContentActive() === false ? false : Doc.ActiveTool !== InkTool.None || @@ -1064,7 +1047,7 @@ export class DocumentViewInternal extends DocComponent, props: Opt, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption'); + captionStyleProvider = (doc: Opt, props: Opt, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption'); @computed get innards() { TraceMobx(); const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1; @@ -1079,7 +1062,7 @@ export class DocumentViewInternal extends DocComponent
      ); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index e53422ab7..8d3534a5c 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -20,7 +20,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { select: (isCtrlPressed: boolean) => void; isContentActive: (outsideReaction?: boolean) => boolean | undefined; - isDocumentActive?: () => boolean; + isDocumentActive?: () => boolean | undefined; isSelected: (outsideReaction?: boolean) => boolean; setHeight?: (height: number) => void; NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 48e54b722..b5193cd20 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; +import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -409,8 +409,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent [this.content]; - private _mainCont: React.RefObject = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); @observable _marqueeing: number[] | undefined; @@ -473,7 +471,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent - {this.contentFunc} + {this.content} {this.annotationLayer} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index ea8f47b39..36be7d257 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -8,7 +8,7 @@ import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; @@ -635,7 +635,8 @@ export class MapBox extends ViewBoxAnnotatableComponent ( (this._stack = r)} - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + {...this.props} + setContentView={emptyFunction} Document={this.props.place} DataDoc={undefined} fieldKey="data" diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5f207cc0d..a254edd87 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -5,16 +5,20 @@ import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { InkTool } from '../../../fields/InkField'; +import { ComputedField } from '../../../fields/ScriptField'; +import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { KeyCodes } from '../../util/KeyCodes'; +import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; +import { CollectionStackingView } from '../collections/CollectionStackingView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; @@ -41,7 +45,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent; private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); - private _selectReactionDisposer: IReactionDisposer | undefined; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _sidebarRef = React.createRef(); @observable private _searching: boolean = false; @@ -184,11 +188,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent disposer?.()); } componentDidMount() { this.props.setContentView?.(this); - this._selectReactionDisposer = reaction( + this._disposers.select = reaction( () => this.props.isSelected(), () => { document.removeEventListener('keydown', this.onKeyDown); @@ -196,6 +200,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.rootDoc.scrollTop, + () => { + if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '-panY'])) instanceof ComputedField)) { + this.props.Document[this.SidebarKey + '-panY'] = ComputedField.MakeFunction('this.scrollTop'); + } + this.props.Document[this.SidebarKey + '-viewScale'] = 1; + this.props.Document[this.SidebarKey + '-panX'] = 0; + } + ); } brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._pdfViewer?.brushView(view); @@ -303,7 +317,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); }; @@ -428,6 +442,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent { const funcs: ContextMenuProps[] = []; + funcs.push({ + description: 'Toggle Sidebar Type', + event: () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform), + icon: 'expand-arrows-alt', + }); funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' }); //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); @@ -467,12 +486,87 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.sidebarNativeWidth; + sidebarNativeHeightFunc = () => this.sidebarNativeHeight; + sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); + sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0); + @computed get sidebarCollection() { + const renderComponent = (tag: string) => { + const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView; + return ComponentTag === CollectionStackingView ? ( + + ) : ( +
      setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}> + +
      + ); + }; + return ( +
      + {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} +
      + ); + } isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath())); @computed get renderPdfView() { TraceMobx(); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const scale = previewScale * (this.props.NativeDimScaling?.() || 1); - return ( + return !this._pdf ? null : (
      -
      - -
      +
      {this.sidebarCollection}
      {this.settingsPanel()}
      ); @@ -535,21 +614,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent>(); render() { TraceMobx(); - if (this._pdf) { - if (!this.props.thumbShown?.()) { - return this.renderPdfView; - } - return null; - } + if (this.props.thumbShown?.()) return null; + const pdfView = this.renderPdfView; const href = this.pdfUrl?.url.href; - if (href) { + if (!pdfView && href) { if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href)))); else { if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf)))); } } - return this.renderTitleBox; + return pdfView ?? this.renderTitleBox; } } diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 61e4894f0..db11a7776 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { AudioField, VideoField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, OmitKeys, returnFalse, returnOne } from '../../../Utils'; +import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; @@ -277,7 +277,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent [this.threed, this.content]; videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth(); formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); render() { @@ -287,7 +286,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent
      - {this.contentFunc} + <> + {this.threed} + {this.content} +
      {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : ( )} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 0b88f5fe3..0570c7fcb 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -9,7 +9,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; @@ -894,8 +894,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._playing; - contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content]; - scaling = () => this.props.NativeDimScaling?.() || 1; panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100; @@ -1068,7 +1066,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent - {this.contentFunc} + {this.youtubeVideoId ? this.youtubeContent : this.content}
      {this.annotationLayer} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index d57518a8d..66d0fd385 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; +import { emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; @@ -305,7 +305,9 @@ export class WebBox extends ViewBoxAnnotatableComponent string[]) => ( + const renderAnnotations = (docFilters: () => string[]) => ( this.props.Document._fitContentsToBox; + fitContentsToBox = () => BoolCast(this.props.Document._fitContentsToBox); sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); - // console.log("printting allSideBarDocs"); - // console.log(this.allSidebarDocs); return this.addDocument(doc, sidebarKey); }; sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); @@ -1785,8 +1783,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent @@ -1800,33 +1798,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ) : (
      setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}> { return this.props.styleProvider?.(doc, props, property); }; - renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => ( + renderAnnotations = (docFilters: () => string[], mixBlendMode?: any, display?: string) => (
      { pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined, }}> { PanelHeight={this.panelHeight} PanelWidth={this.panelWidth} ScreenToLocalTransform={this.overlayTransform} + isAnyChildContentActive={returnFalse} + isAnnotationOverlayScrollable={true} dropAction={'alias'} docFilters={docFilters} select={emptyFunction} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 553c4525c..cc024d83a 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1213,7 +1213,7 @@ export namespace Doc { return doc[StrCast(doc.layoutKey, 'layout')]; } export function LayoutFieldKey(doc: Doc): string { - return StrCast(Doc.Layout(doc).layout).split("'")[1]; + return StrCast(Doc.Layout(doc)[StrCast(doc.layoutKey, 'layout')]).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layoutKey } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); @@ -1222,9 +1222,10 @@ export namespace Doc { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0)); } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { - const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0; - const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0; - return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight); + if (!doc) return 0; + const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym](); + const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0); + return NumCast(doc._nativeHeight, nheight || dheight); } export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width; -- cgit v1.2.3-70-g09d2 From e4b76f6c1dee63c49408c48d7b2bc98e62dc813a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Apr 2023 12:19:01 -0400 Subject: adjusted general context menu items to always appear last. cleaned up makeLink api, --- src/client/documents/Documents.ts | 32 +++++----- src/client/util/DragManager.ts | 1 - src/client/util/LinkFollower.ts | 2 - src/client/views/ContextMenu.tsx | 12 ++-- src/client/views/DocumentButtonBar.tsx | 4 +- src/client/views/MainView.tsx | 1 + src/client/views/SidebarAnnos.tsx | 2 +- .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 28 ++++----- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 26 ++------ .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 70 +++++++++++++++------- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 31 +++++----- src/client/views/nodes/VideoBox.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/search/SearchBox.tsx | 2 +- 21 files changed, 117 insertions(+), 118 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9cb480c4a..5623ca218 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -470,7 +470,7 @@ export namespace Docs { DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, forceReflow: true, nativeDimModifiable: true }, + options: { _height: 100, fitWidth: true, forceReflow: true, nativeDimModifiable: true }, }, ], [ @@ -934,13 +934,13 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } - export function LinkDocument(source: { doc: Doc; ctx?: Doc }, target: { doc: Doc; ctx?: Doc }, options: DocumentOptions = {}, id?: string) { + export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) { const linkDoc = InstanceFromProto( Prototypes.get(DocumentType.LINK), undefined, { - anchor1: source.doc, - anchor2: target.doc, + anchor1: source, + anchor2: target, ...options, }, id @@ -1294,16 +1294,14 @@ export namespace DocUtils { broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1)); return DocUtils.ActiveRecordings.map(audio => { const sourceDoc = getSourceDoc(); - const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor(true) || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline'); - link && (link.followLinkLocation = OpenWhere.addRight); - return link; + return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { linkDisplay: false, linkRelationship: 'recording annotation:linked recording', description: 'recording timeline' }); }); } - export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = '', description: string = '', id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { - if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; - const sv = DocumentManager.Instance.getDocumentView(source.doc); - if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return; + export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { + if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; + const sv = DocumentManager.Instance.getDocumentView(source); + if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return; if (target.doc === Doc.UserDoc()) return undefined; const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { @@ -1343,16 +1341,16 @@ export namespace DocUtils { target, { title: ComputedField.MakeFunction('generateLinkTitle(self)') as any, - 'anchor1-useLinkSmallAnchor': source.doc.useLinkSmallAnchor ? true : undefined, - 'anchor2-useLinkSmallAnchor': target.doc.useLinkSmallAnchor ? true : undefined, + 'anchor1-useLinkSmallAnchor': source.useLinkSmallAnchor ? true : undefined, + 'anchor2-useLinkSmallAnchor': target.useLinkSmallAnchor ? true : undefined, 'acl-Public': SharingPermissions.Augment, '_acl-Public': SharingPermissions.Augment, - linkDisplay: true, + linkDisplay: linkSettings.linkDisplay, _linkAutoMove: true, - linkRelationship, + linkRelationship: linkSettings.linkRelationship, _showCaption: 'description', _showTitle: 'linkRelationship', - description, + description: linkSettings.description, }, id ), @@ -1700,7 +1698,7 @@ export namespace DocUtils { _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); - const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, 'pushpin', ''); + const pushpinLink = DocUtils.MakeLink(pushpin, doc, { linkRelationship: 'pushpin' }, ''); doc._timecodeToShow = undefined; return pushpin; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index cc116fb46..7d2aa813f 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -168,7 +168,6 @@ export namespace DragManager { this.dropDocCreator = dropDocCreator; this.offset = [0, 0]; } - linkSourceDoc?: Doc; dropDocCreator: (annotationOn: Doc | undefined) => Doc; dropDocument?: Doc; offset: number[]; diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 785018990..df61ecece 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -9,8 +9,6 @@ import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; - -type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; /* * link doc: * - anchor1: doc diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 6a530e3ae..e4c3e864b 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -102,13 +102,11 @@ export class ContextMenu extends React.Component { } } @action - moveAfter(item: ContextMenuProps, after: ContextMenuProps) { - if (after && this.findByDescription(after.description)) { - const curInd = this._items.findIndex(i => i.description === item.description); - this._items.splice(curInd, 1); - const afterInd = this._items.findIndex(i => i.description === after.description); - this._items.splice(afterInd + 1, 0, item); - } + moveAfter(item: ContextMenuProps, after?: ContextMenuProps) { + const curInd = this._items.findIndex(i => i.description === item.description); + this._items.splice(curInd, 1); + const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length; + this._items.splice(afterInd, 0, item); } @action setDefaultItem(prefix: string, item: (name: string) => void) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 9389fed01..3e1a0f265 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -416,7 +416,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( {`Open Context Menu`}
      }> -
      this.openContextMenu(e)}> +
      @@ -569,7 +569,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc; const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true); if (trail !== anchor.presTrail) { - DocUtils.MakeLink({ doc: anchor }, { doc: trail }, 'link trail'); + DocUtils.MakeLink(anchor, trail, { linkRelationship: 'link trail' }); anchor.presTrail = trail; } Doc.ActivePresentation = trail; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index befb0200d..b0888df68 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -270,6 +270,7 @@ export class MainView extends React.Component { fa.faLaptopCode, fa.faMale, fa.faCopy, + fa.faHome, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 48c0150e6..2d2b0f83e 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -74,7 +74,7 @@ export class SidebarAnnos extends React.Component { }); FormattedTextBox.SelectOnLoad = target[Id]; FormattedTextBox.DontSelectInitialText = true; - const link = DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); + const link = DocUtils.MakeLink(anchor, target, { linkRelationship: 'inline comment:comment on' }); link && (link.linkDisplay = false); const taggedContent = this.docFilters() diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 121260680..cb5be990d 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -441,7 +441,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' }); this.props.addDocument?.(source); - de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed + de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed e.stopPropagation(); } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); return false; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 64ec419ec..1e02fc9d4 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -3,14 +3,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CursorProperty } from 'csstype'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, DocListCast, 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 { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; -import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -460,7 +460,7 @@ export class CollectionStackingView extends CollectionSubView { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped()) { - const subItems: ContextMenuProps[] = []; - subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' }); - subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); - subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' }); - ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' }); + const cm = ContextMenu.Instance; + const options = cm.findByDescription('Options...'); + const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; + optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' }); + optionItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); + optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' }); + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); } }; @@ -735,14 +737,6 @@ export class CollectionStackingView extends CollectionSubView
      )} - {/* {this.chromeHidden || !this.props.isSelected() ? (null) : - } */}
      diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5100d8d67..132ed6fb6 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -335,7 +335,7 @@ export function CollectionSubView(moreProps?: X) { const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; if (focusNode) { const anchor = srcWeb?.ComponentView?.getAnchor?.(true); - anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor }); + anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); } } } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 99b7549c0..75e76019e 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -359,7 +359,7 @@ export class TreeView extends React.Component { if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; - DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', ''); + DocUtils.MakeLink(sourceDoc, destDoc, { linkRelationship: 'tree link' }); e.stopPropagation(); } const docDragData = de.complete.docDragData; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d39668a5d..fb0bd2a19 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -397,7 +397,7 @@ export class CollectionFreeFormView extends CollectionSubView (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); appearanceItems.push({ description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`, @@ -1700,7 +1704,7 @@ export class CollectionFreeFormView extends CollectionSubView TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); - //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); + !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' }); @@ -1730,27 +1734,9 @@ export class CollectionFreeFormView extends CollectionSubView this.importDocument(e.clientX, e.clientY) }); !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' }); }; - importDocument = (x: number, y: number) => { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.zip'; - input.onchange = _e => { - input.files && - Doc.importDocument(input.files[0]).then(doc => { - if (doc instanceof Doc) { - const [xx, yy] = this.getTransform().transformPoint(x, y); - (doc.x = xx), (doc.y = yy); - this.props.addDocument?.(doc); - } - }); - }; - input.click(); - }; - @undoBatch @action transcribeStrokes = (math: boolean) => { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 043fe0bcf..0b4f76fa1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -527,7 +527,7 @@ export class MarqueeView extends React.Component { + drop = (e: Event, de: DragManager.DropEvent) => { if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return; if (this.props.Document === Doc.ActiveDashboard) { alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.'); return; } const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData; - if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor(); - if (linkdrag?.linkSourceDoc) { - e.stopPropagation(); - if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) { - de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); - } - if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) { - const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.props.Document; - de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]); + if (linkdrag) { + linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor(); + if (linkdrag.linkSourceDoc) { + e.stopPropagation(); + if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) { + de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); + } + if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) { + const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc; + de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, undefined, [de.x, de.y - 50]); + } } } }; @@ -643,9 +645,9 @@ export class DocumentViewInternal extends DocComponent d.anchor1 === this.props.Document && d.linkRelationship === 'portal to:portal from'); if (!portalLink) { DocUtils.MakeLink( - { doc: this.props.Document }, - { doc: Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }) }, - 'portal to:portal from' + this.props.Document, + Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }), + { linkRelationship: 'portal to:portal from' } ); } this.Document.followLinkLocation = OpenWhere.lightbox; @@ -653,6 +655,24 @@ export class DocumentViewInternal extends DocComponent { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.zip'; + input.onchange = _e => { + if (input.files) { + const batch = UndoManager.StartBatch('importing'); + Doc.importDocument(input.files[0]).then(doc => { + if (doc instanceof Doc) { + this.props.addDocTab(doc, OpenWhere.addRight); + batch.end(); + } + }); + } + }; + input.click(); + }; + @action onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => { if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) { @@ -704,9 +724,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); - LinkManager.Links(this.Document).length && - appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' }); - !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); + !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { const existingOnClick = cm.findByDescription('OnClick...'); @@ -774,16 +792,21 @@ export class DocumentViewInternal extends DocComponent Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' }); } - moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); - - (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); } - if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && Doc.ActiveDashboard !== this.props.Document) { + !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' }); + } + const constantItems: ContextMenuProps[] = []; + + if (!Doc.IsSystem(this.rootDoc)) { + constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); + constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() }); + (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); + if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) - moreItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); + constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); } - !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' }); + cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' }); } const help = cm.findByDescription('Help...'); const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : []; @@ -834,7 +857,8 @@ export class DocumentViewInternal extends DocComponent !isClick && batch.end(), () => { - this.toggleSidebar(); + onButton && this.toggleSidebar(); batch.end(); } ); @@ -440,17 +440,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform); specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ - description: 'Toggle Sidebar Type', - event: () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform), - icon: 'expand-arrows-alt', - }); - funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); - funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' }); - //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); - ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); + const cm = ContextMenu.Instance; + const options = cm.findByDescription('Options...'); + const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; + optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' }); + !Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' }); + //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); + !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' }); + const help = cm.findByDescription('Help...'); + const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : []; + helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); + !help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' }); }; @computed get renderTitleBox() { @@ -523,8 +526,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent ) : (
      setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}> @@ -568,7 +569,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true)); }; @@ -1031,7 +1031,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || - DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!); + DocUtils.MakeLink(this.props.Document, target, { linkRelationship: LinkManager.AutoKeywords })!); newAutoLinks.add(alink); const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); @@ -1273,7 +1273,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { makeLink = action((linkTo: Doc) => { const linkFrom = this.props.linkCreateAnchor?.(); if (linkFrom) { - const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); + const link = DocUtils.MakeLink(linkFrom, linkTo, {}); link && this.props.linkCreated?.(link); } }); -- cgit v1.2.3-70-g09d2 From 34d63367302d8158c71361dbcefca9e528c03333 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Apr 2023 09:58:21 -0400 Subject: fixed explore mode to not wait for double clicks --- src/client/views/MainView.tsx | 1 + src/client/views/collections/TabDocView.tsx | 1 + .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 1 + 3 files changed, 3 insertions(+) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b0888df68..60459cf30 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -568,6 +568,7 @@ export class MainView extends React.Component { @computed get exploreMode() { return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } + waitForDoubleClick = () => (this._exploreMode ? 'never' : undefined); headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1); mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1); @computed get headerBarDocView() { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4ad09628f..458712999 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -428,6 +428,7 @@ export class TabDocView extends React.Component { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} onBrowseClick={MainView.Instance.exploreMode} + waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick} isContentActive={returnTrue} isDocumentActive={returnFalse} PanelWidth={this.PanelWidth} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9d4a788db..1ecc69174 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1237,6 +1237,7 @@ export class CollectionFreeFormView extends CollectionSubView Date: Thu, 13 Apr 2023 22:22:07 -0400 Subject: lots of changes to get rid of ContainingCollectionDoc and ContainingCollectionView props. --- src/client/documents/Documents.ts | 8 +-- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DocumentManager.ts | 83 ++++++++++------------ src/client/util/DragManager.ts | 4 +- .../util/Import & Export/DirectoryImportBox.tsx | 6 +- src/client/util/ReplayMovements.ts | 64 ++++++----------- src/client/util/SelectionManager.ts | 6 +- src/client/util/SharingManager.tsx | 5 +- src/client/util/TrackMovements.ts | 36 +++++----- src/client/views/DashboardView.tsx | 17 ++--- src/client/views/DocComponent.tsx | 1 - src/client/views/DocumentDecorations.tsx | 8 +-- src/client/views/GestureOverlay.tsx | 4 +- src/client/views/InkStrokeProperties.ts | 3 +- src/client/views/LightboxView.tsx | 4 +- src/client/views/MainView.tsx | 20 ++---- src/client/views/OverlayView.tsx | 10 +-- src/client/views/Palette.tsx | 31 ++++---- src/client/views/PropertiesDocContextSelector.tsx | 22 ++++-- src/client/views/PropertiesView.tsx | 2 - src/client/views/StyleProvider.tsx | 11 ++- src/client/views/TemplateMenu.tsx | 2 - .../views/collections/CollectionCarouselView.tsx | 1 - src/client/views/collections/CollectionMenu.tsx | 6 +- .../views/collections/CollectionNoteTakingView.tsx | 4 -- .../views/collections/CollectionPileView.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 2 - .../views/collections/CollectionStackingView.tsx | 4 -- .../views/collections/CollectionTreeView.tsx | 10 +-- src/client/views/collections/CollectionView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 6 +- src/client/views/collections/TreeView.tsx | 54 ++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 31 ++++---- .../collectionLinear/CollectionLinearView.tsx | 5 +- .../CollectionMulticolumnView.tsx | 2 - .../CollectionMultirowView.tsx | 2 - .../collectionSchema/CollectionSchemaView.tsx | 6 +- .../collections/collectionSchema/SchemaRowBox.tsx | 2 +- .../collectionSchema/SchemaTableCell.tsx | 2 - src/client/views/linking/LinkMenuItem.tsx | 21 +----- src/client/views/linking/LinkPopup.tsx | 4 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 7 +- src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 12 ++-- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/KeyValuePair.tsx | 2 - src/client/views/nodes/LinkDocPreview.tsx | 4 +- src/client/views/nodes/ScreenshotBox.tsx | 5 +- src/client/views/nodes/button/FontIconBox.tsx | 4 +- .../views/nodes/formattedText/DashDocView.tsx | 7 +- .../views/nodes/formattedText/DashFieldView.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 20 ++++-- src/client/views/nodes/trails/PresBox.tsx | 1 - src/client/views/nodes/trails/PresElementBox.tsx | 27 ++++--- src/client/views/topbar/TopBar.tsx | 51 ++----------- src/mobile/AudioUpload.tsx | 76 ++++++++++---------- src/mobile/MobileInterface.tsx | 4 +- 58 files changed, 291 insertions(+), 456 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8d97ce5af..09b45a481 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1285,10 +1285,6 @@ export namespace DocUtils { return rangeFilteredDocs; } - export function DefaultFocus(doc: Doc, options: DocFocusOptions) { - return undefined; - } - export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { @@ -1299,10 +1295,9 @@ export namespace DocUtils { }); } - export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { + export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, showPopup?: number[]) { if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; const sv = DocumentManager.Instance.getDocumentView(source); - if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return; if (target.doc === Doc.UserDoc()) return undefined; const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { @@ -1371,7 +1366,6 @@ export namespace DocUtils { value: 'any', _readOnly_: 'boolean', scriptContext: 'any', - thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 76ea3e3ea..abf7313a4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -110,7 +110,7 @@ export class CurrentUserUtils { const tempClicks = DocCast(doc[field]); const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ - { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"}, + { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"}, { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ccf370662..ad89e8653 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,15 +1,11 @@ -import { loadAsync } from 'jszip'; -import { action, observable, ObservableSet, runInAction } from 'mobx'; +import { action, observable, ObservableSet } from 'mobx'; import { AnimationSym, Doc, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../fields/Types'; import { AudioField } from '../../fields/URLField'; -import { emptyFunction } from '../../Utils'; import { CollectionViewType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; -import { CollectionView } from '../views/collections/CollectionView'; import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; import { MainView } from '../views/MainView'; @@ -39,7 +35,7 @@ export class DocumentManager { private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => { if (doc) { - const dv = this.getDocumentViewById(doc[Id]); + const dv = this.getDocumentView(doc); this._viewRenderedCbs.push({ doc, func }); if (dv) { this.callAddViewFuncs(dv); @@ -129,42 +125,24 @@ export class DocumentManager { return this.getDocumentViewsById(doc[Id]); } - public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined { - if (!id) return undefined; - let toReturn: DocumentView | undefined; - const passes = preferredCollection ? [preferredCollection, undefined] : [undefined]; - - for (const pass of passes) { - Array.from(DocumentManager.Instance.DocumentViews).map(view => { - if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { - toReturn = view; - return; - } - }); - if (!toReturn) { - Array.from(DocumentManager.Instance.DocumentViews).map(view => { - const doc = view.rootDoc.proto; - if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { - toReturn = view; - } - }); - } else { - break; - } - } - - return toReturn; - } - - public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined { - const found = + public getDocumentView(toFind: Doc | undefined, preferredCollection?: DocumentView): DocumentView | undefined { + const doc = + // bcz: this was temporary code used to match documents by data url instead of by id. intended only for repairing the DB // Array.from(DocumentManager.Instance.DocumentViews).find( // dv => // ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFind.data as any)?.url?.href) || // ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href) // )?.rootDoc ?? toFind; - return this.getDocumentViewById(found[Id], preferredCollection); + const docViewArray = Array.from(DocumentManager.Instance.DocumentViews); + const passes = !doc ? [] : preferredCollection ? [preferredCollection, undefined] : [undefined]; + return passes.reduce( + (pass, toReturn) => + toReturn ?? + docViewArray.filter(view => view.rootDoc === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection) ?? + docViewArray.filter(view => Doc.GetProto(view.rootDoc) === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection), + undefined as Opt + ); } public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { @@ -247,7 +225,8 @@ export class DocumentManager { public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => { const docViewPath = targetDocView.docViewPath.slice(); let rootContextView = docViewPath.shift(); - return rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })); + await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }))); + if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden; }; // shows a document by first: @@ -318,15 +297,25 @@ export class DocumentManager { } } } -export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { - const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc); - const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView); - if (dv) { - DocumentManager.Instance.showDocumentView(dv, { willZoomCentered: true }); - } else { - const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null); - const showDoc = context || doc; - DocumentManager.Instance.showDocument(Doc.BestAlias(showDoc), { openLocation: OpenWhere.addRight }, () => DocumentManager.Instance.showDocument(doc, { willZoomCentered: true })); - } +export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) { + const func = () => { + const cv = DocumentManager.Instance.getDocumentView(containingDoc); + const dv = DocumentManager.Instance.getDocumentView(doc, cv); + if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) { + DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc)); + } else { + const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc)); + DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => { + const cv = DocumentManager.Instance.getDocumentView(containingDoc); + const dv = DocumentManager.Instance.getDocumentView(doc, cv); + dv && Doc.linkFollowHighlight(dv.rootDoc); + }); + } + }; + if (doc.hidden) { + doc.hidden = false; + options.toggleTarget = false; + setTimeout(func); + } else func(); } ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 7d2aa813f..7e6de5e67 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -3,10 +3,8 @@ import { DateField } from '../../fields/DateField'; import { Doc, Field, Opt, StrListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; -import { listSpec } from '../../fields/Schema'; -import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ScriptField } from '../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; import { emptyFunction, Utils } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import * as globalCssVariables from '../views/global/globalCssVariables.scss'; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 559958c2b..76b1323c4 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -14,12 +14,14 @@ import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; import { Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; import { DocumentManager } from '../DocumentManager'; import './DirectoryImportBox.scss'; import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; import React = require('react'); +import _ = require('lodash'); const unsupported = ['text/html', 'text/plain']; @@ -155,8 +157,8 @@ export class DirectoryImportBox extends React.Component { x: NumCast(doc.x), y: NumCast(doc.y) + offset, }; - const parent = this.props.ContainingCollectionView; - if (parent) { + const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it. + if (parent?.rootDoc.type === DocumentType.COL) { let importContainer: Doc; if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index d5bffc5e2..40261985a 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -7,6 +7,7 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie import { DocServer } from '../DocServer'; import { Movement, Presentation } from './TrackMovements'; import { OpenWhereMod } from '../views/nodes/DocumentView'; +import { returnTransparent } from '../../Utils'; export class ReplayMovements { private timers: NodeJS.Timeout[] | null; @@ -60,17 +61,11 @@ export class ReplayMovements { return; } - let docIdtoDoc: Map = new Map(); - try { - docIdtoDoc = await this.loadPresentation(presentation); - } catch { - console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements'); - throw 'error loading docs from server'; - } + const docIdtoDoc = this.loadPresentation(presentation); this.videoBoxDisposeFunc = reaction( () => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), - ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements()) + ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, timeViewed) : this.pauseMovements()) ); this.videoBox = videoBox; }; @@ -93,45 +88,33 @@ export class ReplayMovements { this.pauseMovements(); }; - loadPresentation = async (presentation: Presentation) => { + loadPresentation = (presentation: Presentation) => { const { movements } = presentation; if (movements === null) { throw '[recordingApi.ts] followMovements() failed: no presentation data'; } // generate a set of all unique docIds - const docIds = new Set(); - for (const { docId } of movements) { - if (!docIds.has(docId)) docIds.add(docId); - } - - const docIdtoDoc = new Map(); - - let refFields = await DocServer.GetRefFields([...docIds.keys()]); - for (const docId in refFields) { - if (!refFields[docId]) { - throw `one field was undefined`; - } - docIdtoDoc.set(docId, refFields[docId] as Doc); + const docs = new Set(); + for (const { doc } of movements) { + if (!docs.has(doc)) docs.add(doc); } - // console.info('loadPresentation refFields', refFields, docIdtoDoc); - return docIdtoDoc; + return docs; }; // returns undefined if the docView isn't open on the screen - getCollectionFFView = (docId: string) => { - const isInView = DocumentManager.Instance.getDocumentViewById(docId); + getCollectionFFView = (doc: Doc) => { + const isInView = DocumentManager.Instance.getDocumentView(doc); if (isInView) { return isInView.ComponentView as CollectionFreeFormView; } }; // will open the doc in a tab then return the CollectionFFView that holds it - openTab = (docId: string, docIdtoDoc: Map) => { - const doc = docIdtoDoc.get(docId); - if (doc == undefined) { - console.error(`docIdtoDoc did not contain docId ${docId}`); + openTab = (doc: Doc) => { + if (doc === undefined) { + console.error(`doc undefined`); return undefined; } // console.log('openTab', docId, doc); @@ -149,13 +132,12 @@ export class ReplayMovements { document.Document._panY = panY; }; - getFirstMovements = (movements: Movement[]): Map => { + getFirstMovements = (movements: Movement[]): Map => { if (movements === null) return new Map(); // generate a set of all unique docIds - const docIdtoFirstMove = new Map(); + const docIdtoFirstMove = new Map(); for (const move of movements) { - const { docId } = move; - if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move); + if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move); } return docIdtoFirstMove; }; @@ -165,7 +147,7 @@ export class ReplayMovements { Doc.UserDoc().presentationMode = 'none'; }; - public playMovements = (presentation: Presentation, docIdtoDoc: Map, timeViewed: number = 0) => { + public playMovements = (presentation: Presentation, timeViewed: number = 0) => { // console.info('playMovements', presentation, timeViewed, docIdtoDoc); if (presentation.movements === null || presentation.movements.length === 0) { @@ -183,13 +165,13 @@ export class ReplayMovements { const handleFirstMovements = () => { // if the first movement is a closed tab, open it const firstMovement = filteredMovements[0]; - const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined; - if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc); + const isClosed = this.getCollectionFFView(firstMovement.doc) === undefined; + if (isClosed) this.openTab(firstMovement.doc); // for the open tabs, set it to the first move const docIdtoFirstMove = this.getFirstMovements(filteredMovements); - for (const [docId, firstMove] of docIdtoFirstMove) { - const colFFView = this.getCollectionFFView(docId); + for (const [doc, firstMove] of docIdtoFirstMove) { + const colFFView = this.getCollectionFFView(doc); if (colFFView) this.zoomAndPan(firstMove, colFFView); } }; @@ -200,12 +182,12 @@ export class ReplayMovements { const timeDiff = movement.time - timeViewed * 1000; return setTimeout(() => { - const collectionFFView = this.getCollectionFFView(movement.docId); + const collectionFFView = this.getCollectionFFView(movement.doc); if (collectionFFView) { this.zoomAndPan(movement, collectionFFView); } else { // tab wasn't open - open it and play the movement - const openedColFFView = this.openTab(movement.docId, docIdtoDoc); + const openedColFFView = this.openTab(movement.doc); console.log('openedColFFView', openedColFFView); openedColFFView && this.zoomAndPan(movement, openedColFFView); } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 313c255a0..bfad93334 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,4 +1,3 @@ -import { ModalManager } from '@material-ui/core'; import { action, observable, ObservableMap } from 'mobx'; import { computedFn } from 'mobx-utils'; import { Doc, Opt } from '../../fields/Doc'; @@ -64,8 +63,9 @@ export namespace SelectionManager { export function DeselectView(docView?: DocumentView): void { manager.DeselectView(docView); } - export function SelectView(docView: DocumentView, ctrlPressed: boolean): void { - manager.SelectView(docView, ctrlPressed); + export function SelectView(docView: DocumentView | undefined, ctrlPressed: boolean): void { + if (!docView) DeselectAll(); + else manager.SelectView(docView, ctrlPressed); } export function SelectSchemaViewDoc(document: Opt, deselectAllFirst?: boolean): void { if (deselectAllFirst) manager.DeselectAll(); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 3c05af4bb..a73eda04c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -370,11 +370,10 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc]; return ( { - let context: Opt; - if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) { + if (this.targetDoc && this.targetDocView && docs.length === 1) { DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true }); } }} diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index 4a2ccd706..2f16307b3 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -9,7 +9,7 @@ export type Movement = { panX: number; panY: number; scale: number; - docId: string; + doc: Doc; }; export type Presentation = { @@ -28,7 +28,7 @@ export class TrackMovements { private tracking: boolean; private absoluteStart: number; // instance variable for holding the FFViews and their disposers - private recordingFFViews: Map | null; + private recordingFFViews: Map | null; private tabChangeDisposeFunc: IReactionDisposer | null; // create static instance and getter for global use @@ -55,33 +55,33 @@ export class TrackMovements { return this.currentPresentation.movements === null; } - private addRecordingFFView(doc: Doc, key: string = doc[Id]): void { + private addRecordingFFView(doc: Doc): void { // console.info('adding dispose func : docId', key, 'doc', doc); if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; } - if (this.recordingFFViews.has(key)) { - console.warn('addFFView : key already in map'); + if (this.recordingFFViews.has(doc)) { + console.warn('addFFView : doc already in map'); return; } const disposeFunc = reaction( () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }), - res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale) + res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, doc, res.scale) ); - this.recordingFFViews?.set(key, disposeFunc); + this.recordingFFViews?.set(doc, disposeFunc); } - private removeRecordingFFView = (key: string) => { + private removeRecordingFFView = (doc: Doc) => { // console.info('removing dispose func : docId', key); if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } - this.recordingFFViews.get(key)?.(); - this.recordingFFViews.delete(key); + this.recordingFFViews.get(doc)?.(); + this.recordingFFViews.delete(doc); }; // in the case where only one tab was changed (updates not across dashboards), set only one to true @@ -90,15 +90,15 @@ export class TrackMovements { // so that the size comparisons are correct, we must filter to only the FFViews const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform'; - const tabbedFFViews = new Set(); + const tabbedFFViews = new Set(); for (const DashDoc of tabbedDocs) { - if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); + if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc); } // new tab was added - need to add it if (tabbedFFViews.size > this.recordingFFViews.size) { for (const DashDoc of tabbedDocs) { - if (!this.recordingFFViews.has(DashDoc[Id])) { + if (!this.recordingFFViews.has(DashDoc)) { if (isFFView(DashDoc)) { this.addRecordingFFView(DashDoc); @@ -110,9 +110,9 @@ export class TrackMovements { } // tab was removed - need to remove it from recordingFFViews else if (tabbedFFViews.size < this.recordingFFViews.size) { - for (const [key] of this.recordingFFViews) { - if (!tabbedFFViews.has(key)) { - this.removeRecordingFFView(key); + for (const [doc] of this.recordingFFViews) { + if (!tabbedFFViews.has(doc)) { + this.removeRecordingFFView(doc); if (onlyOne) return; } } @@ -214,7 +214,7 @@ export class TrackMovements { } }; - private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => { + private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => { // ensure we are recording to track if (!this.tracking) { console.error('[recordingApi.ts] trackMovements(): tracking is false'); @@ -231,7 +231,7 @@ export class TrackMovements { // get the time const time = new Date().getTime() - this.absoluteStart; // make new movement object - const movement: Movement = { time, panX, panY, scale, docId }; + const movement: Movement = { time, panX, panY, scale, doc }; // add that movement to the current presentation data's movement array this.currentPresentation.movements && this.currentPresentation.movements.push(movement); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 2b586b0e2..dee161931 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -109,17 +109,8 @@ export class DashboardView extends React.Component { />
      -
      ); @@ -163,7 +154,7 @@ export class DashboardView extends React.Component {
      -
      this.selectDashboardGroup(DashboardGroup.MyDashboards)}> My Dashboards @@ -196,7 +187,7 @@ export class DashboardView extends React.Component { e.stopPropagation(); this.onContextMenu(dashboard, e); }}> - } /> +
      diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 9fc1487a0..a59189fd2 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -52,7 +52,6 @@ export function DocComponent

      () { interface ViewBoxBaseProps { Document: Doc; DataDoc?: Doc; - ContainingCollectionDoc: Opt; DocumentView?: () => DocumentView; fieldKey: string; isSelected: (outsideReaction?: boolean) => boolean; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9ffbe083f..9a4aaf00e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -33,6 +33,7 @@ import { ImageBox } from './nodes/ImageBox'; import React = require('react'); import { RichTextField } from '../../fields/RichTextField'; import { LinkFollower } from '../util/LinkFollower'; +import _ = require('lodash'); @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -303,8 +304,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P SelectionManager.DeselectAll(); }; - onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false); - + onSelectorClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false); /** * Handles setting up events when user clicks on the border radius editor * @param e PointerEvent @@ -744,7 +744,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P seldocview.props.hideDeleteButton || seldocview.rootDoc.hideDeleteButton || SelectionManager.Views().some(docView => { - const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; + const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DataSym]) : AclEdit; return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); }); @@ -865,7 +865,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P

      e.preventDefault()} />
      e.preventDefault()} /> - {seldocview.props.renderDepth <= 1 || !seldocview.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} + {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} )} {useRounding && ( diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0feccb742..13faae783 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1010,14 +1010,12 @@ export class GestureOverlay extends Touchable { renderDepth={0} styleProvider={returnEmptyString} docViewPath={returnEmptyDoclist} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docRangeFilters={returnEmptyFilter} docFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> ); }; diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index e6df0801c..156825f41 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -373,10 +373,11 @@ export class InkStrokeProperties { snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => { const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const containingDocView = inkView.props.CollectionFreeFormDocumentView?.().props.DocumentView?.(); containingCollection?.childDocs .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { - const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView); + const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView); const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []); if (snapped && snapped.distance < snapData.distance) { const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 976c8763e..69eec8456 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -266,8 +266,6 @@ export class LightboxView extends React.Component { docFilters={this.docFilters} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} @@ -275,7 +273,7 @@ export class LightboxView extends React.Component { pinToPres={TabDocView.PinDoc} bringToFront={emptyFunction} onBrowseClick={MainView.Instance.exploreMode} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} />
      diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 60459cf30..0384d925e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -595,14 +595,12 @@ export class MainView extends React.Component { PanelWidth={this.headerBarDocWidth} PanelHeight={this.headerBarDocHeight} renderDepth={0} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
      ); @@ -626,14 +624,12 @@ export class MainView extends React.Component { ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity} PanelWidth={this.mainDocViewWidth} PanelHeight={this.mainDocViewHeight} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} suppressSetHeight={true} renderDepth={this._hideUI ? 0 : -1} /> @@ -727,14 +723,12 @@ export class MainView extends React.Component { renderDepth={0} isContentActive={returnTrue} scriptContext={CollectionDockingView.Instance?.props.Document} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
      {this.docButtons} @@ -758,7 +752,7 @@ export class MainView extends React.Component { PanelHeight={this.leftMenuHeight} renderDepth={0} docViewPath={returnEmptyDoclist} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={DefaultStyleProvider} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} @@ -766,8 +760,6 @@ export class MainView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} scriptContext={this} />
      @@ -900,13 +892,11 @@ export class MainView extends React.Component { PanelWidth={this.leftMenuFlyoutWidth} PanelHeight={this.leftMenuFlyoutHeight} renderDepth={0} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ?
      {StrCast(this.userDoc?.presentationMode)}
      : <>}
      diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index ec22128d4..bdc48d03a 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, trace } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; @@ -10,15 +10,13 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, retu import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; import { LightboxView } from './LightboxView'; import { MainView } from './MainView'; import { DocumentView } from './nodes/DocumentView'; import './OverlayView.scss'; -import { ScriptingRepl } from './ScriptingRepl'; -import { DefaultStyleProvider, testDocProps } from './StyleProvider'; +import { DefaultStyleProvider } from './StyleProvider'; export type OverlayDisposer = () => void; @@ -223,7 +221,7 @@ export class OverlayView extends React.Component { isDocumentActive={returnTrue} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} addDocTab={d.type === DocumentType.PRES ? MainView.addDocTabFunc : returnFalse} @@ -231,8 +229,6 @@ export class OverlayView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
      ); diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 954529bc9..3ad28c418 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -1,12 +1,12 @@ -import { IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc } from "../../fields/Doc"; -import { NumCast } from "../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { DocumentView } from "./nodes/DocumentView"; -import "./Palette.scss"; +import { IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc } from '../../fields/Doc'; +import { NumCast } from '../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils'; +import { Transform } from '../util/Transform'; +import { DocumentView } from './nodes/DocumentView'; +import './Palette.scss'; export interface PaletteProps { x: number; @@ -23,20 +23,20 @@ export default class Palette extends React.Component { componentDidMount = () => { this._selectedDisposer = reaction( () => NumCast(this.props.thumbDoc.selectedIndex), - (i) => this._selectedIndex = i, + i => (this._selectedIndex = i), { fireImmediately: true } ); - } + }; componentWillUnmount = () => { this._selectedDisposer?.(); - } + }; render() { return (
      -
      +
      { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> + />
      ); } -} \ No newline at end of file +} diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 2c7da5931..93eec61f3 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -22,9 +22,9 @@ export class PropertiesDocContextSelector extends React.Component alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set()); + const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set()); const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases)); const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); @@ -42,6 +42,7 @@ export class PropertiesDocContextSelector extends React.Component !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document)) .filter(doc => !Doc.IsSystem(doc)) + .filter(doc => doc !== targetContext) .map(doc => ({ col: doc, target })); } @@ -53,8 +54,21 @@ export class PropertiesDocContextSelector extends React.Component DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100); + //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere); + setTimeout( + () => + this.props.DocView && + DocFocusOrOpen( + Doc.GetProto(this.props.DocView.props.Document), + { + // + willZoomCentered: true, + openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere, + }, + col + ), + 100 + ); }; render() { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index fbc7d7696..6582c3f2a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -341,8 +341,6 @@ export class PropertiesView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} addDocument={returnFalse} moveDocument={undefined} removeDocument={returnFalse} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index b950b4860..739d6d819 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -18,7 +18,7 @@ import { TreeSort } from './collections/TreeView'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; -import { DocumentViewProps } from './nodes/DocumentView'; +import { DocumentViewProps, OpenWhere } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { SliderBox } from './nodes/SliderBox'; @@ -295,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt { - if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) { + if (props?.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform) { return doc?.pointerEvents !== 'none' ? null : (
      toggleLockedPosition(doc)}> @@ -383,10 +383,7 @@ export function DashboardStyleProvider(doc: Opt, props: Opt {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => { - doc.hidden = doc.hidden ? undefined : true; - if (!doc.hidden) { - DocFocusOrOpen(doc, props?.ContainingCollectionDoc); - } + DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc); })} ); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 45db240a9..c5a501aa6 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -114,8 +114,6 @@ export class TemplateMenu extends React.Component { { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
      ); @@ -642,8 +640,8 @@ export class CollectionViewBaseChrome extends React.Component this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index aec0734b4..80e81bc1c 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -255,8 +255,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { hideTitle={this.props.childHideTitle?.()} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} @@ -632,8 +630,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
      ); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index ba90ed8cd..fd9b0c0ce 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -30,7 +30,7 @@ export class CollectionPileView extends CollectionSubView() { // pileups are designed to go away when they are empty. this._disposers.selected = reaction( () => this.childDocs.length, - num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document) + num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document) ); } componentWillUnmount() { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index bbd81d06d..22a575989 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -815,8 +815,6 @@ class StackedTimelineAnchor extends React.Component whenChildContentsActiveChanged={emptyFunction} focus={focusFunc} isContentActive={returnFalse} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} searchFilterDocs={returnEmptyDoclist} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index a85ee0e02..67f5dc9f4 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -346,8 +346,6 @@ export class CollectionStackingView extends CollectionSubView
      ); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4a11e8f0b..92932fb61 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView 0) { FormattedTextBox.SelectOnLoad = prev[Id]; - DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView)?.select(false); + DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false); } return true; } @@ -242,8 +242,6 @@ export class CollectionTreeView extends CollectionSubView
      ); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index bc25ad43a..53fbcc3cc 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -279,7 +279,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent +
      {this.showIsTagged()} {this.renderSubView(this.collectionViewType, props)}
      diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 458712999..ef8f395c5 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -425,8 +425,6 @@ export class TabDocView extends React.Component { hideTitle={this.props.keyValue} Document={this._document} DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} onBrowseClick={MainView.Instance.exploreMode} waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick} isContentActive={returnTrue} @@ -580,8 +578,6 @@ export class TabMinimapView extends React.Component { { ScreenToLocalTransform={Transform.Identity} renderDepth={0} whenChildContentsActiveChanged={emptyFunction} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={TabMinimapView.miniStyleProvider} addDocTab={this.props.addDocTab} pinToPres={TabDocView.PinDoc} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 75e76019e..8b1dfc767 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -42,7 +42,7 @@ export interface TreeViewProps { prevSibling?: Doc; document: Doc; dataDoc?: Doc; - containerCollection: Doc; + treeViewParent: Doc; renderDepth: number; dropAction: dropActionType; addDocTab: (doc: Doc, where: OpenWhere) => boolean; @@ -143,7 +143,7 @@ export class TreeView extends React.Component { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; } @computed get MAX_EMBED_HEIGHT() { - return NumCast(this.props.containerCollection.maxEmbedHeight, 200); + return NumCast(this.props.treeViewParent.maxEmbedHeight, 200); } @computed get dataDoc() { return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; @@ -194,7 +194,7 @@ export class TreeView extends React.Component { const ind = this.dataDoc[key].indexOf(doc); const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); - res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false); + res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.DocumentView?.())?.select(false); return res; }; @@ -385,7 +385,7 @@ export class TreeView extends React.Component { }; const addDoc = inside ? localAdd : parentAddDoc; const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument; - const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd; + const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.freezeChildren).includes('add')) || forceAdd; if (canAdd) { this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true); const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false)); @@ -406,7 +406,7 @@ export class TreeView extends React.Component { getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { - const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( layoutDoc[HeightSym](), this.MAX_EMBED_HEIGHT, @@ -416,7 +416,7 @@ export class TreeView extends React.Component { return layoutDoc._fitWidth ? !Doc.NativeHeight(layoutDoc) ? NumCast(layoutDoc._height) - : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) + : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height))) : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym](); })() ); @@ -452,7 +452,7 @@ export class TreeView extends React.Component { this, doc, undefined, - this.props.containerCollection, + this.props.treeViewParent, this.props.prevSibling, addDoc, remDoc, @@ -596,7 +596,7 @@ export class TreeView extends React.Component { this, this.layoutDoc, this.dataDoc, - this.props.containerCollection, + this.props.treeViewParent, this.props.prevSibling, addDoc, remDoc, @@ -656,7 +656,7 @@ export class TreeView extends React.Component { this.onCheckedClick?.script.run( { this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.containerCollection.title, + heading: this.props.treeViewParent.title, checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check', containingTreeView: this.props.treeView.props.Document, }, @@ -724,9 +724,11 @@ export class TreeView extends React.Component { }; @observable headerEleWidth = 0; - @computed get headerElements() { + @computed get titleButtons() { + const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations); return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( <> + {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */} {this.doc.hideContextMenu ? null : ( { return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; - onChildClick = () => { - return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); - }; + onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); @@ -906,7 +906,7 @@ export class TreeView extends React.Component { styleProvider={this.titleStyleProvider} enableDragWhenActive={true} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. - docViewPath={returnEmptyDoclist} + docViewPath={this.props.treeView.props.docViewPath} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} addDocTab={this.props.addDocTab} @@ -937,12 +937,8 @@ export class TreeView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={this.props.treeView.props.CollectionView} - ContainingCollectionDoc={this.props.treeView.props.Document} /> ); - - const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : '')); return ( <>
      { {view}
      r && (this.headerEleWidth = r.getBoundingClientRect().width))}> - {buttons} {/* hide and lock buttons */} - {this.headerElements} + {this.titleButtons}
      ); @@ -1017,8 +1012,6 @@ export class TreeView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containerCollection} - ContainingCollectionView={undefined} addDocument={this.props.addDocument} moveDocument={this.move} removeDocument={this.props.removeDoc} @@ -1131,7 +1124,7 @@ export class TreeView extends React.Component { childDocs: Doc[], treeView: CollectionTreeView, parentTreeView: CollectionTreeView | TreeView | undefined, - containerCollection: Doc, + treeViewParent: Doc, dataDoc: Doc | undefined, parentCollectionDoc: Doc | undefined, containerPrevSibling: Doc | undefined, @@ -1162,19 +1155,19 @@ export class TreeView extends React.Component { hierarchyIndex?: number[], renderCount?: number ) { - const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); + const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField); if (viewSpecScript) { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); + const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1); const treeViewRefs = new Map(); return docs .filter(child => child instanceof Doc) .map((child, i) => { if (renderCount && i > renderCount) return null; - const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); + const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; } @@ -1192,10 +1185,7 @@ export class TreeView extends React.Component { } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); - const outdent = - parentCollectionDoc?._viewType !== CollectionViewType.Tree - ? undefined - : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); + const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); const rowHeight = () => { @@ -1208,7 +1198,7 @@ export class TreeView extends React.Component { ref={r => treeViewRefs.set(child, r ? r : undefined)} document={pair.layout} dataDoc={pair.data} - containerCollection={containerCollection} + treeViewParent={treeViewParent} prevSibling={docs[i]} // TODO: [AL] add these hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} @@ -1220,7 +1210,7 @@ export class TreeView extends React.Component { onCheckedClick={onCheckedClick} onChildClick={onChildClick} renderDepth={renderDepth} - removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove} + removeDoc={StrCast(treeViewParent.freezeChildren).includes('remove') ? undefined : remove} addDocument={addDocument} styleProvider={styleProvider} panelWidth={rowWidth} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7ae7be3c8..a3f5e73fb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -262,7 +262,7 @@ export class CollectionFreeFormView extends CollectionSubView { SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true)); }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; @@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0; const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0; let lastx = NumCast(refDoc.x); let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height)); - setTimeout( - action(() => - sorted.slice(1).forEach((pair, i) => { - lastx = pair.layout.x = lastx + deltax; - lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); - }) - ) + runInAction(() => + sorted.slice(1).forEach((pair, i) => { + lastx = pair.layout.x = lastx + deltax; + lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); + }) ); } } @@ -464,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster); - const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); + const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!); const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined); de.moveDocument = this.props.moveDocument; @@ -872,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)) + .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) .filter( @@ -966,7 +964,7 @@ export class CollectionFreeFormView extends CollectionSubView doc.type === DocumentType.INK && !doc.dontIntersect) .forEach(doc => { - const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke; + const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke; const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt)); @@ -1121,7 +1119,8 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) { + const collectionDoc = this.props.docViewPath().lastElement().rootDoc; + if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._panX !== undefined) { this.setPan( NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), @@ -1256,8 +1255,6 @@ export class CollectionFreeFormView extends CollectionSubView
      @@ -251,8 +249,7 @@ export class CollectionLinearView extends CollectionSubView() { self: this.rootDoc, _readOnly_: false, scriptContext: this.props.scriptContext, - thisContainer: this.props.ContainingCollectionDoc, - documentView: this.props.docViewPath().lastElement(), + documentView: this.props.DocumentView?.(), }); this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked; })} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b73b1d779..78d3d1b6e 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -262,8 +262,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} 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} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 0cca83803..4d61dc272 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -261,8 +261,6 @@ export class CollectionMultirowView extends CollectionSubView() { docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} 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} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index fd9bcf681..6d5a73e55 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -793,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() { fitContentsToBox={returnTrue} dontCenter={'y'} onClickScriptDisable="always" - focus={DocUtils.DefaultFocus} + focus={emptyFunction} renderDepth={this.props.renderDepth + 1} rootSelected={this.rootSelected} PanelWidth={this.previewWidthFunc} @@ -806,8 +806,6 @@ export class CollectionSchemaView extends CollectionSubView() { searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} moveDocument={this.props.moveDocument} addDocument={this.addRow} removeDocument={this.props.removeDocument} @@ -849,8 +847,6 @@ class CollectionSchemaViewDocs extends React.Component() { } @computed get schemaDoc() { - return this.props.ContainingCollectionDoc!; + return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc; } @computed get rowIndex() { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 13e45963e..5f8ffe8b0 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -28,8 +28,6 @@ export class SchemaTableCell extends React.Component { searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, - ContainingCollectionView: undefined, - ContainingCollectionDoc: undefined, fieldKey: this.props.fieldKey, rootSelected: returnFalse, isSelected: returnFalse, diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 29e7cd3ad..d703c9595 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -44,25 +44,10 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume } const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); - dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - docView.props.removeDocument?.(doc); - addDocument(doc); - return true; - }; - const containingView = docView.props.ContainingCollectionView; - const finishDrag = (e: DragManager.DragCompleteEvent) => - e.docDragData && - (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => { - const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); - if (dvs.length) { - dvs.forEach(dv => droppedDocs.push(dv.props.Document)); - } else { - droppedDocs.push(Doc.MakeAlias(d)); - } - return droppedDocs; - }, [] as Doc[])); + dragData.canEmbed = true; + dragData.moveDocument = (docView.props.docViewPath().lastElement()?.ComponentView as any)?.props.CollectionView?.moveDocument; // this is equal to docView.props.moveDocument, but moveDocument is not a defined prop of a DocumentView .. but maybe it should be? - DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); + DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined); } } diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index 7bdace2b6..5f2d4a7b6 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -85,15 +85,13 @@ export class LinkPopup extends React.Component { PanelWidth={this.getPWidth} PanelHeight={this.getPHeight} renderDepth={0} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
      diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9bdb2cee7..24b9f3b25 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -160,8 +160,9 @@ export class CollectionFreeFormDocumentView extends DocComponent { - const { Document: topDoc, ContainingCollectionView: container } = this.props; - const screenXf = container?.screenToLocalTransform(); + const topDoc = this.rootDoc; + const containerDocView = this.props.docViewPath().lastElement(); + const screenXf = containerDocView?.screenToLocalTransform(); if (screenXf) { SelectionManager.DeselectAll(); if (topDoc.z) { @@ -178,7 +179,7 @@ export class CollectionFreeFormDocumentView extends DocComponent SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0); + setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0); } }; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index dbcfe43cf..76a5ce7b3 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -154,7 +154,6 @@ export class DocumentContentsView extends React.Component< // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews 'hideResizeHandles', 'hideTitle', - 'treeViewDoc', 'contentPointerEvents', 'radialMenu', 'LayoutTemplateString', diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index df3299eef..47705d53d 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -142,7 +142,7 @@ export class DocumentLinksButton extends React.Component undefined | { x: number; y: number; r: number; b: number }; 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; thumbShown?: () => boolean; setContentView?: (view: DocComponentView) => any; @@ -179,6 +177,7 @@ export interface DocumentViewSharedProps { ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; canEmbedOnDrag?: boolean; + treeViewDoc?: Doc; xPadding?: number; yPadding?: number; dropAction?: dropActionType; @@ -210,7 +209,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { hideOpenButton?: boolean; hideDeleteButton?: boolean; hideLinkAnchors?: boolean; - treeViewDoc?: Doc; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events isContentActive: () => boolean | undefined; // whether document contents should handle pointer events contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents @@ -429,7 +427,6 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); - if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { + if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Tree) { const existingOnClick = cm.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; @@ -1446,7 +1442,7 @@ export class DocumentView extends React.Component { scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale; docViewPathFunc = () => this.docViewPath; isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); - select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection); + select = (extendSelection: boolean) => SelectionManager.SelectView(this, extendSelection); NativeWidth = () => this.effectiveNativeWidth; NativeHeight = () => this.effectiveNativeHeight; PanelWidth = () => this.panelWidth; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 8d3534a5c..86779e0dd 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -32,6 +32,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields backgroundColor?: string; + treeViewDoc?: Doc; color?: string; fontSize?: number; height?: number; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 94434dce7..7ea6d42ff 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -61,8 +61,6 @@ export class KeyValuePair extends React.Component { searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, - ContainingCollectionView: undefined, - ContainingCollectionDoc: undefined, fieldKey: this.props.keyName, rootSelected: returnFalse, isSelected: returnFalse, diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index fcc5b6975..c58b5dd8c 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -271,14 +271,12 @@ export class LinkDocPreview extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} renderDepth={0} suppressSetHeight={true} PanelWidth={this.width} PanelHeight={this.height} pointerEvents={returnNone} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={returnFalse} ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size. bringToFront={returnFalse} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index db11a7776..e015024fd 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -305,8 +305,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent + renderDepth={this.props.renderDepth + 1}> <> {this.threed} {this.content} @@ -330,7 +329,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent + /> )}
      diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 8eacfbc51..1a75a7e76 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -568,12 +568,12 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); - const layoutFrameNumber = Cast(selView.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber).backgroundColor ?? 'transparent'; } selectedViews.forEach(dv => { - const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed if (contentFrameNumber !== undefined) { CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color }); diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index c00ab6a7e..b31fc01ff 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -9,10 +9,9 @@ import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { ColorScheme } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; -import { DocumentView } from '../DocumentView'; +import { DocFocusOptions, DocumentView } from '../DocumentView'; import { FormattedTextBox } from './FormattedTextBox'; import React = require('react'); -import { SelectionManager } from '../../../util/SelectionManager'; export class DashDocView { dom: HTMLSpanElement; // container for label and value @@ -151,7 +150,7 @@ export class DashDocViewInternal extends React.Component { const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target + outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target onKeyDown = (e: any) => { e.stopPropagation(); @@ -212,8 +211,6 @@ export class DashDocViewInternal extends React.Component { docFilters={this.props.tbox?.props.docFilters} docRangeFilters={this.props.tbox?.props.docRangeFilters} searchFilterDocs={this.props.tbox?.props.searchFilterDocs} - ContainingCollectionView={this._textBox.props.ContainingCollectionView} - ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} />
      ); diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index f23426bb3..c43206629 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -235,10 +235,7 @@ export class DashFieldViewInternal extends React.Component { - let container = this.props.tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } + let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement(); if (container) { const alias = Doc.MakeAlias(container.props.Document); alias._viewType = CollectionViewType.Time; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 677c4662b..bc2a5d797 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -541,7 +541,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' }); + const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true }); return this.addDocument(tanch) ? tanch : undefined; }; const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement(); @@ -956,7 +957,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const examinedNode = findAnchorNode(node, editor); - if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { + if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { nodes.push(examinedNode.node); !hadStart && (start = index + examinedNode.start); hadStart = true; @@ -971,9 +972,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent mark.type === editor.state.schema.marks.linkAnchor); @@ -987,7 +994,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) { + if ((ret.frag.size || (content?.length && content[0].type === this._editorView.state.schema.nodes.dashDoc) || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) { !options.instant && (this._focusSpeed = focusSpeed); let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { @@ -998,6 +1005,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this._focusSpeed = undefined), this._focusSpeed); setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000)); + return focusSpeed; + } else { + return this.props.focus(this.rootDoc, options); } } }; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 3589a9065..807a19771 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -2457,7 +2457,6 @@ export class PresBox extends ViewBoxBaseComponent() { {mode !== CollectionViewType.Invalid ? ( () { // Idea: this boolean will determine whether to automatically show the video when this preselement is selected. // @observable static showVideo: boolean = false; @computed get indexInPres() { - return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc); + 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 expandViewHeight() { return 100; @@ -51,11 +51,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { return this.presBoxView?.selectedArray; } @computed get presBoxView() { - const vpath = this.props.docViewPath(); - return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined; + return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox; } @computed get presBox() { - return this.props.ContainingCollectionDoc!; + return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc; } @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; @@ -110,14 +109,12 @@ export class PresElementBox extends ViewBoxBaseComponent() { docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} rootSelected={returnTrue} addDocument={returnFalse} removeDocument={returnFalse} fitContentsToBox={returnTrue} moveDocument={this.props.moveDocument!} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} @@ -195,7 +192,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []); if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc); dragData.dropAction = 'move'; - dragData.treeViewDoc = this.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; + dragData.treeViewDoc = this.presBox?._viewType === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; dragData.moveDocument = this.props.moveDocument; const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { @@ -269,7 +266,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @undoBatch removeItem = action((e: React.MouseEvent) => { e.stopPropagation(); - if (this.indexInPres < (this.presBoxView?.itemIndex || 0)) { + if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) { this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1; } this.props.removeDocument?.(this.rootDoc); @@ -406,15 +403,15 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get toolbarWidth(): number { const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox); - let width: number = NumCast(this.presBox._width); + let width: number = NumCast(this.presBox?._width); if (presBoxDocView) width = presBoxDocView.props.PanelWidth(); if (width === 0) width = 300; return width; } @computed get presButtons() { - const presBox: Doc = this.presBox; //presBox - const presBoxColor: string = StrCast(presBox._backgroundColor); + const presBox = 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; @@ -494,10 +491,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get mainItem() { const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false; - const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres; + const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres; const miniView: boolean = this.toolbarWidth <= 110; - const presBox: Doc = this.presBox; //presBox - const presBoxColor: string = StrCast(presBox._backgroundColor); + const presBox = this.presBox; //presBox + const presBoxColor: string = StrCast(presBox?._backgroundColor); const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; const activeItem: Doc = this.rootDoc; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index f2e9be61d..d63c25dbe 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -46,7 +46,7 @@ export class TopBar extends React.Component { return (
      {Doc.ActiveDashboard ? ( - } isCircle={true} hoverStyle="gray" color={this.textColor} /> + } color={this.textColor} /> ) : (
      dash logo @@ -55,18 +55,7 @@ export class TopBar extends React.Component {
      )} {Doc.ActiveDashboard && ( -
      ); @@ -94,13 +83,8 @@ export class TopBar extends React.Component {
); diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx index 64baa351c..a0b437354 100644 --- a/src/mobile/AudioUpload.tsx +++ b/src/mobile/AudioUpload.tsx @@ -13,13 +13,18 @@ import { listSpec } from '../fields/Schema'; import { Cast, FieldValue } from '../fields/Types'; import { nullAudio } from '../fields/URLField'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyPath } from '../Utils'; -import "./ImageUpload.scss"; +import './ImageUpload.scss'; import { MobileInterface } from './MobileInterface'; 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, _fitContentsToBox: 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 @@ -28,25 +33,36 @@ export class AudioUpload extends React.Component { @action clearUpload = () => { for (let i = 1; i < 8; i++) { - this.setOpacity(i, "0.2"); + this.setOpacity(i, '0.2'); } - this._audioCol = 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; - } + this._audioCol = 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 of the 'Close' button * Reset upload interface and toggle audio */ closeUpload = () => { this.clearUpload(); MobileInterface.Instance.toggleAudio(); - } + }; /** * Handles the on click of the 'Upload' button. @@ -57,14 +73,14 @@ export class AudioUpload extends React.Component { const audioDoc = this._audioCol; const data = Cast(audioRightSidebar.data, listSpec(Doc)); for (let i = 1; i < 8; i++) { - setTimeout(() => this.setOpacity(i, "1"), i * 200); + setTimeout(() => this.setOpacity(i, '1'), i * 200); } if (data) { data.push(audioDoc); } // Resets uploader after 3 seconds setTimeout(this.clearUpload, 3000); - } + }; // Returns the upload audio menu private get uploadInterface() { @@ -72,11 +88,13 @@ export class AudioUpload extends React.Component { <> -
+
+ +
this.closeUpload()}> - +
- +
"rgba(0,0,0,0)"} + styleProvider={() => 'rgba(0,0,0,0)'} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
@@ -125,28 +141,16 @@ export class AudioUpload extends React.Component { // Handles the setting of the loading bar setOpacity = (index: number, opacity: string) => { - const slab = document.getElementById("slab0" + index); + const slab = document.getElementById('slab0' + index); if (slab) { slab.style.opacity = opacity; } - } + }; @observable private dialogueBoxOpacity = 1; @observable private overlayOpacity = 0.4; render() { - return ( - - ); + return ; } - } - - diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 2ae597b0b..a543c2f70 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -397,7 +397,7 @@ export class MobileInterface extends React.Component { renderDepth={0} isDocumentActive={returnTrue} isContentActive={emptyFunction} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={this.whitebackground} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} @@ -405,8 +405,6 @@ export class MobileInterface extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
); -- cgit v1.2.3-70-g09d2 From 650cc00d18c97f89aef77f50703aa9b525dd0368 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 14 Apr 2023 11:12:49 -0400 Subject: got rid of CollectionView prop. fixed DocFocusOrOpen to toggle targets and zoom correctly. --- src/client/util/DocumentManager.ts | 12 +++++++----- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/util/LinkFollower.ts | 2 +- src/client/views/MainView.tsx | 1 - src/client/views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesDocContextSelector.tsx | 21 +-------------------- src/client/views/SidebarAnnos.tsx | 1 - src/client/views/StyleProvider.tsx | 4 ++-- src/client/views/TemplateMenu.tsx | 1 - src/client/views/collections/CollectionMenu.tsx | 1 - .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 6 +++--- src/client/views/collections/CollectionTreeView.tsx | 6 +++--- src/client/views/collections/CollectionView.tsx | 1 - src/client/views/collections/TabDocView.tsx | 1 - src/client/views/collections/TreeView.tsx | 6 +++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 +++++++---- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 14 +++++++------- src/client/views/nodes/ImageBox.tsx | 1 - src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 1 - src/client/views/nodes/PDFBox.tsx | 1 - src/client/views/nodes/ScreenshotBox.tsx | 1 - src/client/views/nodes/VideoBox.tsx | 2 -- src/client/views/nodes/WebBox.tsx | 1 - .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/client/views/pdf/PDFViewer.tsx | 1 - 29 files changed, 39 insertions(+), 69 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ad89e8653..e01457b4f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -227,6 +227,7 @@ export class DocumentManager { let rootContextView = docViewPath.shift(); await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }))); if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden; + else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation); }; // shows a document by first: @@ -246,10 +247,9 @@ export class DocumentManager { const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc)); if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!); options.didMove = true; - docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere); + docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); }); - docContextPath.shift(); const childViewIterator = async (docView: DocumentView) => { const innerDoc = docContextPath.shift(); @@ -297,15 +297,17 @@ export class DocumentManager { } } } -export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) { +export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { const func = () => { const cv = DocumentManager.Instance.getDocumentView(containingDoc); const dv = DocumentManager.Instance.getDocumentView(doc, cv); if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) { DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc)); } else { - const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc)); - DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => { + const container = DocCast(containingDoc ?? doc.context); + const showDoc = !Doc.IsSystem(container) ? container : doc; + options.toggleTarget = undefined; + DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => { const cv = DocumentManager.Instance.getDocumentView(containingDoc); const dv = DocumentManager.Instance.getDocumentView(doc, cv); dv && Doc.linkFollowHighlight(dv.rootDoc); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 76b1323c4..b9bb22564 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -157,7 +157,7 @@ export class DirectoryImportBox extends React.Component { x: NumCast(doc.x), y: NumCast(doc.y) + offset, }; - const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it. + const parent = this.props.DocumentView?.().props.docViewPath().lastElement(); if (parent?.rootDoc.type === DocumentType.COL) { let importContainer: Doc; if (docs.length < 50) { diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index d4d7c66f5..ba2edb65c 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -75,7 +75,7 @@ export class LinkFollower { zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500), zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, - openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox), + openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere, effect: sourceDoc, zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText), }; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0384d925e..ccc9a7215 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -883,7 +883,6 @@ export class MainView extends React.Component { isSelected={returnFalse} docViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} - CollectionView={undefined} addDocument={this.addButtonDoc} addDocTab={MainView.addDocTabFunc} pinToPres={emptyFunction} diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 00c3400cb..935d551c6 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component { if (!this.props.DocView) return; col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (col._viewType === CollectionViewType.Freeform) { - col._panX = NumCast(target.x) + NumCast(target._width) / 2; - col._panY = NumCast(target.y) + NumCast(target._height) / 2; - } - col.hidden = false; - //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere); - setTimeout( - () => - this.props.DocView && - DocFocusOrOpen( - Doc.GetProto(this.props.DocView.props.Document), - { - // - willZoomCentered: true, - openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere, - }, - col - ), - 100 - ); + DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col); }; render() { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 2d2b0f83e..b0aab968e 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -243,7 +243,6 @@ export class SidebarAnnos extends React.Component { removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.addDocument} - CollectionView={undefined} ScreenToLocalTransform={this.screenToLocalTransform} renderDepth={this.props.renderDepth + 1} viewType={CollectionViewType.Stacking} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 739d6d819..5f16e0ebd 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -5,7 +5,7 @@ import { Shadows } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; @@ -383,7 +383,7 @@ export function DashboardStyleProvider(doc: Opt, props: Opt {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => { - DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc); + DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.context ?? doc?.annotationOn)); })} ); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index c5a501aa6..1c816aa05 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -113,7 +113,6 @@ export class TemplateMenu extends React.Component { {templateMenu} { isSelected={returnFalse} docViewPath={returnEmptyDoclist} moveDocument={returnFalse} - CollectionView={undefined} addDocument={returnFalse} addDocTab={returnFalse} pinToPres={emptyFunction} diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 80e81bc1c..99d4d0bee 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -162,7 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } @action - moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; }; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 67f5dc9f4..bdad325d5 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -230,7 +230,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isAnyChildContentActive(); @action - moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; }; createRef = (ele: HTMLDivElement | null) => { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 132ed6fb6..5581ac8fe 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -19,7 +19,6 @@ import { DocComponent } from '../DocComponent'; import React = require('react'); export interface SubCollectionViewProps extends CollectionViewProps { - CollectionView: Opt; isAnyChildContentActive: () => boolean; } @@ -201,8 +200,9 @@ export function CollectionSubView(moreProps?: X) { } } - addDocument = (doc: Doc | Doc[]) => this.props.addDocument?.(doc) || false; - + addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false; + removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument); @action protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 92932fb61..f81c17a7b 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -374,10 +374,10 @@ export class CollectionTreeView extends CollectionSubView this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1)); - addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; - remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; + addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; + remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => - this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; + this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; @observable _headerHeight = 0; @computed get content() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 53fbcc3cc..d7a889362 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -276,7 +276,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent {
boolean; panelWidth: () => number; panelHeight: () => number; - addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; + addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean; removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; moveDocument: DragManager.MoveFunction; isContentActive: (outsideReaction?: boolean) => boolean; @@ -373,7 +373,7 @@ export class TreeView extends React.Component { dropping: boolean = false; dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { - const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); + const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before); const localAdd = (doc: Doc | Doc[]) => { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; @@ -1186,7 +1186,7 @@ export class TreeView extends React.Component { }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); - const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); + const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); const rowHeight = () => { const aspect = Doc.NativeAspect(childLayout); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a3f5e73fb..33fc2ddf3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -311,7 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView boolean; // whether the root of a template has been selected addDocTab: (doc: Doc, where: OpenWhere) => boolean; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) - addDocument?: (doc: Doc | Doc[]) => boolean; - removeDocument?: (doc: Doc | Doc[]) => boolean; - moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; + addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; + removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; + moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean; pinToPres: (document: Doc, pinProps: PinProps) => void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 22ecaa299..7c98aa6e4 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -478,7 +478,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent <> diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b144c9318..7a7d4fe37 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -963,7 +963,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent { renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '-annotations'} - CollectionView={undefined} getScrollHeight={this.getScrollHeight} setPreviewCursor={this.setPreviewCursor} setBrushViewer={this.setBrushViewer} -- cgit v1.2.3-70-g09d2