diff options
-rw-r--r-- | package-lock.json | 97 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 13 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 13 | ||||
-rw-r--r-- | src/client/util/DocumentManager.ts | 2 | ||||
-rw-r--r-- | src/client/views/StyleProvider.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 15 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 10 | ||||
-rw-r--r-- | src/client/views/nodes/FontIconBox/FontIconBox.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 14 | ||||
-rw-r--r-- | src/client/views/nodes/RecordingBox/RecordingBox.tsx | 63 | ||||
-rw-r--r-- | src/client/views/nodes/RecordingBox/RecordingView.tsx | 44 | ||||
-rw-r--r-- | src/client/views/nodes/ScreenshotBox.tsx | 23 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 10 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 | ||||
-rw-r--r-- | src/fields/Doc.ts | 9 | ||||
-rw-r--r-- | src/fields/DocSymbols.ts | 2 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 2 |
20 files changed, 247 insertions, 86 deletions
diff --git a/package-lock.json b/package-lock.json index eb2a0bd68..e6985cf65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6507,9 +6507,9 @@ } }, "browndash-components": { - "version": "0.0.96", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.96.tgz", - "integrity": "sha512-HX05U7oIwnwZuQDPBgdASLm5LPE8pF7l8L2bIdS7YOmS4HisesXGU8GZeOBc+wqHWKMSYCMRe9gFhRvJK+FaPA==", + "version": "0.0.97", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.97.tgz", + "integrity": "sha512-UO8NQrOJoAq+UQGR8TXCRzr6jX9qSnEot9FHP7zdwlKH1sGntbQpyM5BXdwfkyXo+oh1qstTGkyR4s20CY7Yrw==", "requires": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -7063,12 +7063,97 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "strip-ansi": { "version": "7.1.0", "bundled": true, "requires": { "ansi-regex": "^6.0.1" } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } } } }, @@ -8573,7 +8658,7 @@ } }, "string-width-cjs": { - "version": "npm:string-width@4.2.3", + "version": "npm:string-width-cjs@4.2.3", "bundled": true, "requires": { "emoji-regex": "^8.0.0", @@ -8596,7 +8681,7 @@ } }, "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", + "version": "npm:strip-ansi-cjs@6.0.1", "bundled": true, "requires": { "ansi-regex": "^5.0.1" @@ -8755,7 +8840,7 @@ } }, "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", + "version": "npm:wrap-ansi-cjs@7.0.0", "bundled": true, "requires": { "ansi-styles": "^4.0.0", diff --git a/package.json b/package.json index bcd3ce037..ea4e6a5e6 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,7 @@ "body-parser": "^1.19.2", "bootstrap": "^4.6.1", "brotli": "^1.3.3", - "browndash-components": "^0.0.96", + "browndash-components": "^0.0.97", "browser-assert": "^1.2.1", "bson": "^4.6.1", "canvas": "^2.9.3", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 54f2b7132..f5fa38ec6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -169,7 +169,7 @@ export class DocumentOptions { _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers'); - 'acl-Guest'?: string; // public permissions + 'acl-Guest'?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions '_acl-Guest'?: string; // public permissions type?: DTYPEt = new DTypeInfo('type of document', true); type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection @@ -179,6 +179,7 @@ export class DocumentOptions { author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document'); + embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument'); color?: STRt = new StrInfo('foreground color data doc'); hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection'); backgroundColor?: STRt = new StrInfo('background color for data doc'); @@ -304,7 +305,7 @@ export class DocumentOptions { noteType?: string; // freeform properties - _freeform_backgroundGrid?: boolean; + _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections'); _freeform_scale?: NUMt = new NumInfo('how much a freeform view has been scaled (zoomed)'); _freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view'); _freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view'); @@ -335,8 +336,8 @@ export class DocumentOptions { link_relationship?: string; // type of relatinoship a link represents link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors'); link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead"); - link_anchor_1?: Doc; - link_anchor_2?: Doc; + link_anchor_1?: DOCt = new DocInfo('start anchor of a link'); + link_anchor_2?: DOCt = new DocInfo('end anchor of a link'); link_autoMoveAnchors?: BOOLt = new BoolInfo('whether link endpoint should move around the edges of a document to make shortest path to other link endpoint'); link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)'); link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)'); @@ -373,8 +374,8 @@ export class DocumentOptions { dragWhenActive?: BOOLt = new BoolInfo('should document drag when it is active - e.g., pileView, group'); dragAction?: DROPt = new DAInfo('how to drag document when it is active (e.g., tree, groups)'); dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true); - dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script - clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script + dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script'); + clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script'); onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af43ef16a..b148a5e26 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -375,7 +375,7 @@ export class CurrentUserUtils { const reqdStackOpts:DocumentOptions ={ title: "menuItemPanel", childDragAction: "same", layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, - _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true, + _chromeHidden: true, _gridGap: 0, _yMargin: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); } @@ -528,7 +528,7 @@ export class CurrentUserUtils { const newFolderScript = { onClick: newFolder}; const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); - const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, + const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true, title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true, isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed", @@ -701,10 +701,10 @@ export class CurrentUserUtils { 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, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected - { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'return { toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'return { 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: 30, 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: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, @@ -713,7 +713,8 @@ export class CurrentUserUtils { { 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)`, linearView_IsOpen: `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)`, linearView_IsOpen: `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)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected - { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected + { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected + { title: "Audio", icon: "microphone", toolTip: "Dictate", btnType: ButtonType.ToggleButton, expertMode: false, ignoreClick: true, scripts: { onClick: 'return toggleRecording(_readOnly_)'}, funcs: { }} ]; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9779639f4..7c3b5be05 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -205,7 +205,7 @@ export class DocumentManager { } static playAudioAnno(doc: Doc) { - const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); + const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement(); if (anno) { if (anno instanceof AudioField) { new Howl({ diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a757a0715..c2dcf071d 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -296,11 +296,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps }; const audio = () => { const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped'); - const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations']).length; + const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length; if (!doc || props?.renderDepth === -1 || (!audioAnnosCount(doc) && audioAnnoState(doc) === 'stopped')) return null; const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' }; return ( - <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations-text']).lastElement()}</div>}> + <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}> <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}> <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" /> </div> diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index ad84d859d..de58e1fe7 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -716,7 +716,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> this._disposer = reaction( () => this.props.currentTimecode(), time => { - const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null); + const dictationDoc = Cast(this.props.layoutDoc.data_dictation, Doc, null); const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); if ( !LightboxView.LightboxDoc && diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b5c7d3f5d..eed04b3ee 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -426,6 +426,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree minHeight: '100%', }} onWheel={e => e.stopPropagation()} + onClick={e => (!this.layoutDoc.forceActive ? this.props.select(false) : SelectionManager.DeselectAll())} onDrop={this.onTreeDrop}> <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> </div> diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6558d215a..7c409c38c 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -73,7 +73,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @observable _paused: boolean = false; // is recording paused // @observable rawDuration: number = 0; // computed from the length of the audio element when loaded @computed get recordingStart() { - return DateCast(this.dataDoc[this.fieldKey + '-recordingStart'])?.date.getTime(); + return DateCast(this.dataDoc[this.fieldKey + '_recordingStart'])?.date.getTime(); } @computed get rawDuration() { return NumCast(this.dataDoc[`${this.fieldKey}_duration`]); @@ -230,10 +230,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp recordAudioAnnotation = async () => { this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); this._recorder = new MediaRecorder(this._stream); - this.dataDoc[this.fieldKey + '-recordingStart'] = new DateField(); + this.dataDoc[this.fieldKey + '_recordingStart'] = new DateField(); DocUtils.ActiveRecordings.push(this); this._recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer({file: e.data}); + const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); if (!(result instanceof Error)) { this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client); } @@ -359,9 +359,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp returnFalse, action(() => { const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); - Doc.GetProto(newDoc).recordingSource = this.dataDoc; - Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`); - Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); + const textField = Doc.LayoutFieldKey(newDoc); + Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc; + Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`); + Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`); if (Doc.IsInMyOverlay(this.rootDoc)) { newDoc.overlayX = this.rootDoc.x; newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); @@ -658,7 +659,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp CollectionFreeFormDocumentView={undefined} dataFieldKey={this.fieldKey} fieldKey={this.annotationKey} - dictationKey={this.fieldKey + '-dictation'} + dictationKey={this.fieldKey + '_dictation'} mediaPath={this.path} renderDepth={this.props.renderDepth + 1} startTag={'_timecodeToShow' /* audioStart */} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c5dead708..39c8d3348 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1025,9 +1025,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps audio: true, }) .then(function (stream) { - let audioTextAnnos = Cast(dataDoc[field + '-audioAnnotations-text'], listSpec('string'), null); + let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); if (audioTextAnnos) audioTextAnnos.push(''); - else audioTextAnnos = dataDoc[field + '-audioAnnotations-text'] = new List<string>(['']); + else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']); DictationManager.Controls.listen({ interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), continuous: { indefinite: false }, @@ -1044,9 +1044,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); if (!(result instanceof Error)) { const audioField = new AudioField(result.accessPaths.agnostic.client); - const audioAnnos = Cast(dataDoc[field + '-audioAnnotations'], listSpec(AudioField), null); + const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); if (audioAnnos === undefined) { - dataDoc[field + '-audioAnnotations'] = new List([audioField]); + dataDoc[field + '_audioAnnotations'] = new List([audioField]); } else { audioAnnos.push(audioField); } @@ -1067,7 +1067,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps playAnnotation = () => { const self = this; const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped'; - const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null); + const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null); const anno = audioAnnos?.lastElement(); if (anno instanceof AudioField && audioAnnoState === 'stopped') { new Howl({ diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index bf3c79cf9..5ff5f7bfa 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -287,6 +287,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { this.colorBatch?.end(); this.colorBatch = undefined; }} + defaultPickerType="Classic" selectedColor={curColor} type={Type.PRIM} color={color} diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 01acdccb7..b0d041bdd 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -14,6 +14,8 @@ import './KeyValueBox.scss'; import './KeyValuePair.scss'; import React = require('react'); import { DocCast } from '../../../fields/Types'; +import { Tooltip } from '@material-ui/core'; +import { DocumentOptions, FInfo } from '../../documents/Documents'; // Represents one row in a key value plane @@ -109,11 +111,13 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { X </button> <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} /> - <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}> - {'('.repeat(parenCount)} - {props.fieldKey} - {')'.repeat(parenCount)} - </div> + <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description}> + <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}> + {'('.repeat(parenCount)} + {props.fieldKey} + {')'.repeat(parenCount)} + </div> + </Tooltip> </div> </td> <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }} onContextMenu={this.onContextMenu}> diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 04f11a5df..8fa2861b6 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -4,28 +4,28 @@ import * as React from 'react'; import { VideoField } from '../../../../fields/URLField'; import { Upload } from '../../../../server/SharedMediaTypes'; import { ViewBoxBaseComponent } from '../../DocComponent'; -import { FieldView } from '../FieldView'; +import { FieldView, FieldViewProps } from '../FieldView'; import { VideoBox } from '../VideoBox'; import { RecordingView } from './RecordingView'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Presentation } from '../../../util/TrackMovements'; import { Doc } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; -import { DocCast } from '../../../../fields/Types'; +import { BoolCast, DocCast } from '../../../../fields/Types'; +import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { Docs } from '../../../documents/Documents'; @observer -export class RecordingBox extends ViewBoxBaseComponent() { +export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); } private _ref: React.RefObject<HTMLDivElement> = React.createRef(); - constructor(props: any) { - super(props); - } - componentDidMount() { + this.props.setContentView?.(this); Doc.SetNativeWidth(this.dataDoc, 1280); Doc.SetNativeHeight(this.dataDoc, 720); } @@ -46,20 +46,63 @@ export class RecordingBox extends ViewBoxBaseComponent() { this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); - this.dataDoc[this.fieldKey + '-recorded'] = true; + this.dataDoc[this.fieldKey + '_recorded'] = true; // stringify the presentation and store it if (presentation?.movements) { const presCopy = { ...presentation }; presCopy.movements = presentation.movements.map(movement => ({ ...movement, doc: movement.doc[Id] })) as any; - this.dataDoc[this.fieldKey + '-presentation'] = JSON.stringify(presCopy); + this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy); } }; + Record: undefined | (() => void); + Pause: undefined | (() => void); + Finish: undefined | (() => void); + getControls = (record: () => void, pause: () => void, finish: () => void) => { + this.Record = record; + this.Pause = pause; + this.Finish = finish; + }; + render() { return ( <div className="recordingBox" ref={this._ref}> - {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={DocCast(this.rootDoc.proto)?.[Id] || ''} />} + {!this.result && ( + <RecordingView + forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} + getControls={this.getControls} + setResult={this.setResult} + setDuration={this.setVideoDuration} + id={DocCast(this.rootDoc.proto)?.[Id] || ''} + /> + )} </div> ); } + static screengrabber: RecordingBox | undefined; } +ScriptingGlobals.add(function toggleRecording(_readOnly_: boolean) { + if (_readOnly_) return RecordingBox.screengrabber ? true : false; + if (RecordingBox.screengrabber) { + RecordingBox.screengrabber.Pause?.(); + setTimeout(() => { + RecordingBox.screengrabber?.Finish?.(); + RecordingBox.screengrabber!.rootDoc.overlayX = 100; + RecordingBox.screengrabber!.rootDoc.overlayY = 100; + RecordingBox.screengrabber = undefined; + }, 100); + } else { + const screengrabber = Docs.Create.WebCamDocument('', { + _width: 384, + _height: 216, + }); + screengrabber.overlayX = -400; + screengrabber.overlayY = 0; + screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true; + Doc.AddToMyOverlay(screengrabber); + DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => { + RecordingBox.screengrabber = docView.ComponentView as RecordingBox; + RecordingBox.screengrabber.Record?.(); + }); + } +}, 'toggle recording'); diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 51eb774e2..0e386b093 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -21,6 +21,8 @@ interface IRecordingViewProps { setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void; setDuration: (seconds: number) => void; id: string; + getControls: (record: () => void, pause: () => void, finish: () => void) => void; + forceTrackScreen: boolean; } const MAXTIME = 100000; @@ -60,14 +62,14 @@ export function RecordingView(props: IRecordingViewProps) { useEffect(() => { if (finished) { // make the total presentation that'll match the concatted video - let concatPres = trackScreen && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); + let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); // this async function uses the server to create the concatted video and then sets the result to it's accessPaths (async () => { const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); // upload the segments to the server and get their server access paths - const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({file})))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server)); + const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({ file })))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server)); // concat the segments together using post call const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); @@ -132,7 +134,7 @@ export function RecordingView(props: IRecordingViewProps) { videoRecorder.current.onstart = (event: any) => { setRecording(true); // start the recording api when the video recorder starts - trackScreen && TrackMovements.Instance.start(); + (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start(); }; videoRecorder.current.onstop = () => { @@ -147,7 +149,7 @@ export function RecordingView(props: IRecordingViewProps) { // depending on if a presenation exists, add it to the video const presentation = TrackMovements.Instance.yieldPresentation(); - setVideos(videos => [...videos, presentation != null && trackScreen ? { ...nextVideo, presentation } : nextVideo]); + setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]); } // reset the temporary chunks @@ -159,9 +161,7 @@ export function RecordingView(props: IRecordingViewProps) { }; // if this is called, then we're done recording all the segments - const finish = (e: React.PointerEvent) => { - e.stopPropagation(); - + const finish = () => { // call stop on the video recorder if active videoRecorder.current?.state !== 'inactive' && videoRecorder.current?.stop(); @@ -176,8 +176,7 @@ export function RecordingView(props: IRecordingViewProps) { setFinished(true); }; - const pause = (e: React.PointerEvent) => { - e.stopPropagation(); + const pause = () => { // if recording, then this is just a new segment videoRecorder.current?.state === 'recording' && videoRecorder.current.stop(); }; @@ -217,6 +216,10 @@ export function RecordingView(props: IRecordingViewProps) { return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds); }; + useEffect(() => { + props.getControls(record, pause, finish); + }, []); + return ( <div className="recording-container"> <div className="video-wrapper"> @@ -227,7 +230,19 @@ export function RecordingView(props: IRecordingViewProps) { </div> <div className="controls"> <div className="controls-inner-container"> - <div className="record-button-wrapper">{recording ? <button className="stop-button" onPointerDown={pause} /> : <button className="record-button" onPointerDown={start} />}</div> + <div className="record-button-wrapper"> + {recording ? ( + <button + className="stop-button" + onPointerDown={e => { + e.stopPropagation(); + pause(); + }} + /> + ) : ( + <button className="record-button" onPointerDown={start} /> + )} + </div> {!recording && (videos.length > 0 ? ( @@ -236,7 +251,12 @@ export function RecordingView(props: IRecordingViewProps) { <MdBackspace onPointerDown={undoPrevious} /> </IconContext.Provider> <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}> - <FaCheckCircle onPointerDown={finish} /> + <FaCheckCircle + onPointerDown={e => { + e.stopPropagation(); + finish(); + }} + /> </IconContext.Provider> </div> ) : ( @@ -244,7 +264,7 @@ export function RecordingView(props: IRecordingViewProps) { <label className="track-screen"> <input type="checkbox" - checked={trackScreen} + checked={trackScreen || props.forceTrackScreen} onChange={e => { setTrackScreen(e.target.checked); }} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 271ff3cf8..83a29f071 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -17,6 +17,7 @@ import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { CaptureManager } from '../../util/CaptureManager'; +import { SettingsManager } from '../../util/SettingsManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; @@ -25,7 +26,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import './ScreenshotBox.scss'; import { VideoBox } from './VideoBox'; -import { SettingsManager } from '../../util/SettingsManager'; declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has @@ -117,7 +117,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl @observable private _videoRef: HTMLVideoElement | null = null; @observable _screenCapture = false; @computed get recordingStart() { - return Cast(this.dataDoc[this.props.fieldKey + '-recordingStart'], DateField)?.date.getTime(); + return Cast(this.dataDoc[this.props.fieldKey + '_recordingStart'], DateField)?.date.getTime(); } constructor(props: any) { @@ -227,13 +227,13 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl this._audioRec.onstop = async (e: any) => { const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file }))); if (!(result instanceof Error)) { - this.dataDoc[this.props.fieldKey + '-audio'] = new AudioField(result.accessPaths.agnostic.client); + this.dataDoc[this.props.fieldKey + '_audio'] = new AudioField(result.accessPaths.agnostic.client); } }; this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); this._videoRec = new MediaRecorder(this._videoRef!.srcObject); const vid_chunks: any = []; - this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '-recordingStart'] = new DateField(new Date())); + this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '_recordingStart'] = new DateField(new Date())); this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data); this._videoRec.onstop = async (e: any) => { const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() }); @@ -270,14 +270,15 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl }; setupDictation = () => { - if (this.dataDoc[this.fieldKey + '-dictation']) return; + if (this.dataDoc[this.fieldKey + '_dictation']) return; const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); + const textField = Doc.LayoutFieldKey(dictationText); dictationText._layout_autoHeight = false; const dictationTextProto = Doc.GetProto(dictationText); - dictationTextProto.recordingSource = this.dataDoc; - dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`); - dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); - this.dataDoc[this.fieldKey + '-dictation'] = dictationText; + dictationTextProto[`${textField}_recordingSource`] = this.dataDoc; + dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`); + dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`); + this.dataDoc[this.fieldKey + '_dictation'] = dictationText; }; videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth(); formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); @@ -314,10 +315,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl </CollectionFreeFormView> </div> <div style={{ background: SettingsManager.Instance.userColor, position: 'relative', height: this.formattedPanelHeight() }}> - {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : ( + {!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : ( <FormattedTextBox {...this.props} - Document={DocCast(this.dataDoc[this.fieldKey + '-dictation'])} + Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])} fieldKey={'text'} PanelHeight={this.formattedPanelHeight} select={emptyFunction} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1f52c2d92..2177adeff 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -103,14 +103,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // returns the path of the audio file @computed get audiopath() { - const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null); + const field = Cast(this.props.Document[this.props.fieldKey + '_audio'], AudioField, null); const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null); return field?.url.href ?? vfield?.url.href ?? ''; } // returns the presentation data if it exists, null otherwise @computed get presentation() { - const data = this.dataDoc[this.fieldKey + '-presentation']; + const data = this.dataDoc[this.fieldKey + '_presentation']; return data ? JSON.parse(StrCast(data)) : null; } @@ -524,7 +524,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp icon: 'expand-arrows-alt', }); // if the videobox was turned from a recording box - if (this.dataDoc[this.fieldKey + '-recorded'] === true) { + if (this.dataDoc[this.fieldKey + '_recorded'] === true) { subitems.push({ description: 'Recreate recording', event: () => { @@ -533,7 +533,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this.dataDoc[this.props.fieldKey] = ''; this.dataDoc[this.fieldKey + '_duration'] = ''; // delete assoicated presentation data - this.dataDoc[this.fieldKey + '-presentation'] = ''; + this.dataDoc[this.fieldKey + '_presentation'] = ''; }, icon: 'expand-arrows-alt', }); @@ -959,7 +959,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp {...this.props} dataFieldKey={this.fieldKey} fieldKey={this.annotationKey} - dictationKey={this.fieldKey + '-dictation'} + dictationKey={this.fieldKey + '_dictation'} mediaPath={this.audiopath} thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])} renderDepth={this.props.renderDepth + 1} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index eea4f513e..2afbbb457 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -156,7 +156,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return this.dataDoc?.mediaState === 'recording'; } set _recording(value) { - !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined); + !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? 'recording' : undefined); } @computed get config() { this._keymap = buildKeymap(schema, this.props); @@ -1262,9 +1262,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps () => this._recording, () => { this.stopDictation(true); - if (this._recording) { - this.recordDictation(); - } + this._recording && this.recordDictation(); } ); if (this._recording) setTimeout(this.recordDictation); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index d3912b8a0..ceacb8a08 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1344,8 +1344,13 @@ export namespace Doc { } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { - if (Doc.AreProtosEqual(linkDoc.link_anchor_2 as Doc, anchorDoc) || Doc.AreProtosEqual((linkDoc.link_anchor_2 as Doc).annotationOn as Doc, anchorDoc)) return '2'; - return Doc.AreProtosEqual(anchorDoc, (linkDoc.link_anchor_1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.link_anchor_1 as Doc) ? '1' : '2'; + const linkAnchor2 = DocCast(linkDoc.link_anchor_2); + const linkAnchor1 = DocCast(linkDoc.link_anchor_1); + if (linkDoc.link_matchEmbeddings) { + return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1'; + } + if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2'; + return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2'; } export function linkFollowUnhighlight() { diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index ac13fa27a..088903082 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -24,4 +24,4 @@ export const Initializing = Symbol('DocInitializing'); export const ForceServerWrite = Symbol('DocForceServerWrite'); export const CachedUpdates = Symbol('DocCachedUpdates'); -export const DashVersion = 'v0.5.5'; +export const DashVersion = 'v0.5.6'; diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 337bb812f..117981a7c 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -99,7 +99,7 @@ export namespace DashUploadUtils { merge .input(textFilePath) .inputOptions(['-f concat', '-safe 0']) - .outputOptions('-c copy') + // .outputOptions('-c copy') //.videoCodec("copy") .save(outputFilePath) .on('error', (err: any) => { |