From bcada17befd639516862163831b9adb823791a93 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Aug 2023 15:50:47 -0400 Subject: changed default color picker --- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 1 + src/fields/DocSymbols.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') 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() { this.colorBatch?.end(); this.colorBatch = undefined; }} + defaultPickerType="Classic" selectedColor={curColor} type={Type.PRIM} color={color} 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'; -- cgit v1.2.3-70-g09d2 From 354fb5c69aecb6378de0204168c96f81c94577b5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Aug 2023 18:22:55 -0400 Subject: fixed drawing links between embeddings as used in DataViz docs. --- src/fields/Doc.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') 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() { -- cgit v1.2.3-70-g09d2 From 571538ba1173aab673f856f6c22b188d0ab7876a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Aug 2023 18:40:43 -0400 Subject: minor fixes. --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/collections/CollectionTreeView.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af43ef16a..52ada3246 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -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, _lockedPosition: 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", 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 e.stopPropagation()} + onClick={e => (!this.layoutDoc.forceActive ? this.props.select(false) : SelectionManager.DeselectAll())} onDrop={this.onTreeDrop}>
    {this.treeViewElements}
-- cgit v1.2.3-70-g09d2 From 06debb1314e4cadb00c29bd70a747971d1d9e98a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Aug 2023 19:32:34 -0400 Subject: normalized some field names data_dictation, etc. added hover info for field names in keyvalue pane. --- src/client/documents/Documents.ts | 13 +++++++------ src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/util/DocumentManager.ts | 2 +- src/client/views/StyleProvider.tsx | 4 ++-- .../views/collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 10 +++++----- src/client/views/nodes/DocumentView.tsx | 10 +++++----- src/client/views/nodes/KeyValuePair.tsx | 14 +++++++++----- src/client/views/nodes/ScreenshotBox.tsx | 16 ++++++++-------- src/client/views/nodes/VideoBox.tsx | 4 ++-- 10 files changed, 42 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 96a983153..e7d3e7efc 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 52ada3246..114249b24 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, _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", 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, props: Opt { 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 ( - {StrListCast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations-text']).lastElement()}}> + {StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}}>
DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}>
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 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/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6558d215a..4480de938 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -73,7 +73,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { 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); } @@ -360,7 +360,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { 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).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}_recordingStart"]`); Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); if (Doc.IsInMyOverlay(this.rootDoc)) { newDoc.overlayX = this.rootDoc.x; @@ -658,7 +658,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent(['']); + else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); DictationManager.Controls.listen({ interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), continuous: { indefinite: false }, @@ -1044,9 +1044,9 @@ export class DocumentViewInternal extends DocComponent { 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/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 { X -
- {'('.repeat(parenCount)} - {props.fieldKey} - {')'.repeat(parenCount)} -
+ pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description}> +
+ {'('.repeat(parenCount)} + {props.fieldKey} + {')'.repeat(parenCount)} +
+
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 271ff3cf8..8e8a59fd9 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -117,7 +117,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { 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,14 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { - 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)); dictationText._layout_autoHeight = false; const dictationTextProto = Doc.GetProto(dictationText); dictationTextProto.recordingSource = this.dataDoc; - dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`); + dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}_recordingStart"]`); dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); - this.dataDoc[this.fieldKey + '-dictation'] = dictationText; + 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 +314,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent
- {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : ( + {!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : ( StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])} renderDepth={this.props.renderDepth + 1} -- cgit v1.2.3-70-g09d2 From f0cca82b98e766474f1df49bacec2258b67aae4e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Aug 2023 19:58:08 -0400 Subject: updated fieldNames for recording dictation --- src/client/views/nodes/AudioBox.tsx | 7 ++++--- src/client/views/nodes/ScreenshotBox.tsx | 7 ++++--- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 ++---- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 4480de938..7c409c38c 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -359,9 +359,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent { 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); diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 8e8a59fd9..0a33a336f 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -272,11 +272,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { 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'); + 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(); 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 this._recording, () => { this.stopDictation(true); - if (this._recording) { - this.recordDictation(); - } + this._recording && this.recordDictation(); } ); if (this._recording) setTimeout(this.recordDictation); -- cgit v1.2.3-70-g09d2 From 9dfca50488241998fa1a99f660b12f43ad803d38 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Aug 2023 22:31:42 -0400 Subject: Added the beginning of an A/V recording button --- src/client/util/CurrentUserUtils.ts | 9 ++-- .../views/nodes/RecordingBox/RecordingBox.tsx | 63 ++++++++++++++++++---- .../views/nodes/RecordingBox/RecordingView.tsx | 44 ++++++++++----- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 6 +-- src/server/DashUploadUtils.ts | 2 +- 6 files changed, 95 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 114249b24..b148a5e26 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -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/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() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); } private _ref: React.RefObject = 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 (
- {!this.result && } + {!this.result && ( + + )}
); } + 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 (
@@ -227,7 +230,19 @@ export function RecordingView(props: IRecordingViewProps) {
-
{recording ?
+
+ {recording ? ( +
{!recording && (videos.length > 0 ? ( @@ -236,7 +251,12 @@ export function RecordingView(props: IRecordingViewProps) { - + { + e.stopPropagation(); + finish(); + }} + />
) : ( @@ -244,7 +264,7 @@ export function RecordingView(props: IRecordingViewProps) {