From 8fa3c03f1fae853e3c626e748aef76f9b7bbc875 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Tue, 20 Aug 2019 22:42:25 -0400 Subject: initial commit --- src/client/util/DictationManager.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 488a146bf..758482eae 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -301,11 +301,16 @@ export namespace DictationManager { } }], - ["create bulleted note", { + ["new outline", { action: (target: DocumentView) => { let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" }); + newBox.autoHeight = true; let proto = newBox.proto!; - let proseMirrorState = '"{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":""}]}]}]}]},"selection":{"type":"text","anchor":1,"head":1}}"'; + proto.page = -1; + let prompt = "Press alt + r to start dictating here..."; + let head = 3; + let anchor = head + prompt.length; + let proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`; proto.data = new RichTextField(proseMirrorState); proto.backgroundColor = "#eeffff"; target.props.addDocTab(newBox, proto, "onRight"); @@ -323,6 +328,9 @@ export namespace DictationManager { let what = matches[2]; let dataDoc = Doc.GetProto(target.props.Document); let fieldKey = "data"; + if (isNaN(count)) { + return; + } for (let i = 0; i < count; i++) { let created: Doc | undefined; switch (what) { -- cgit v1.2.3-70-g09d2 From d260d9abc13ae60c3206c3110f2d7f23ceeb2449 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 05:29:07 -0400 Subject: prosemirror bulleted list dictation integration --- src/client/util/DictationManager.ts | 48 ++++++++++-------- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/nodes/FormattedTextBox.tsx | 76 ++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 34 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 758482eae..781e5e465 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -45,7 +45,7 @@ export namespace DictationManager { export namespace Controls { - const infringe = "unable to process: dictation manager still involved in previous session"; + export const Infringed = "unable to process: dictation manager still involved in previous session"; const intraSession = ". "; const interSession = " ... "; @@ -69,30 +69,32 @@ export namespace DictationManager { delimiters: DelimiterArgs; interimHandler: InterimResultHandler; tryExecute: boolean; + terminators: string[]; } export const listen = async (options?: Partial) => { let results: string | undefined; - let main = MainView.Instance; + // let main = MainView.Instance; - main.dictationOverlayVisible = true; - main.isListening = { interim: false }; + // main.dictationOverlayVisible = true; + // main.isListening = { interim: false }; try { results = await listenImpl(options); if (results) { Utils.CopyText(results); - main.isListening = false; - let execute = options && options.tryExecute; - main.dictatedPhrase = execute ? results.toLowerCase() : results; - main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + // main.isListening = false; + // let execute = options && options.tryExecute; + // main.dictatedPhrase = execute ? results.toLowerCase() : results; + // main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + options && options.tryExecute && await DictationManager.Commands.execute(results); } } catch (e) { - main.isListening = false; - main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; - main.dictationSuccess = false; + // main.isListening = false; + // main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + // main.dictationSuccess = false; } finally { - main.initiateDictationFade(); + // main.initiateDictationFade(); } return results; @@ -100,7 +102,7 @@ export namespace DictationManager { const listenImpl = (options?: Partial) => { if (isListening) { - return infringe; + return Infringed; } isListening = true; @@ -128,6 +130,12 @@ export namespace DictationManager { recognizer.onresult = (e: SpeechRecognitionEvent) => { current = synthesize(e, intra); + let matchedTerminator: string | undefined; + if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) { + current = matchedTerminator; + recognizer.abort(); + return complete(); + } handler && handler(current); isManuallyStopped && complete(); }; @@ -163,13 +171,13 @@ export namespace DictationManager { } isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); - let main = MainView.Instance; - if (main.dictationOverlayVisible) { - main.cancelDictationFade(); - main.dictationOverlayVisible = false; - main.dictationSuccess = undefined; - setTimeout(() => main.dictatedPhrase = placeholder, 500); - } + // let main = MainView.Instance; + // if (main.dictationOverlayVisible) { + // main.cancelDictationFade(); + // main.dictationOverlayVisible = false; + // main.dictationSuccess = undefined; + // setTimeout(() => main.dictatedPhrase = placeholder, 500); + // } }; const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 891fd7847..cc8e57af9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -730,7 +730,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerEnter={e => e.ctrlKey && runInAction(() => this.openHover = true)} onPointerLeave={() => runInAction(() => this.openHover = false)} onClick={e => { - if (e.ctrlKey) { + if (e.altKey) { + e.preventDefault(); window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); } else { this.clearPullColor(); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index e460e3e50..222ebed8f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -6,7 +6,7 @@ import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; -import { EditorState, Plugin, Transaction } from "prosemirror-state"; +import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; @@ -36,6 +36,7 @@ import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/Goog import { DocumentDecorations } from '../DocumentDecorations'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; import { DictationManager } from '../../util/DictationManager'; +import { ReplaceStep } from 'prosemirror-transform'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -181,6 +182,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const marks = tx.storedMarks; if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); } } + this._applyingChange = true; const fieldkey = "preview"; if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"); @@ -268,23 +270,70 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe setCurrentBulletContent = (value: string) => { if (this._editorView) { - this._editorView.state; + // let next = value.endsWith("bullet"); + // if (next) { + // value = value.split("bullet")[0]; + // } + let state = this._editorView.state; + let from = state.selection.from; + let to = state.selection.to; + this._editorView.dispatch(state.tr.insertText(value, from, to)); + state = this._editorView.state; + let updated = TextSelection.create(state.doc, from, from + value.length); + this._editorView.dispatch(state.tr.setSelection(updated)); + // if (next) { + // this.nextBullet(this._editorView.state.selection.to); + // } } } - considerRecordingSession = (e: KeyboardEvent) => { - if (e.which === 82 && e.altKey) { - let continuous = { indefinite: true }; - DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous - }); + nextBullet = (pos: number) => { + if (this._editorView) { + // DictationManager.Controls.stop(false); + let frag = Fragment.fromArray(this.newListItems(2)); + let slice = new Slice(frag, 2, 2); + let state = this._editorView.state; + this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); + pos += 4; + state = this._editorView.state; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + // this.recordSession(); + } + } + + private newListItems = (count: number) => { + let listItems: any[] = []; + for (let i = 0; i < count; i++) { + listItems.push(schema.nodes.list_item.create(null, schema.nodes.paragraph.create(null))); } + return listItems; + } + + recordSession = async () => { + let completedCue = "end session"; + let results = await DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + terminators: [completedCue, "bullet", "next"] + }); + if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { + DictationManager.Controls.stop(); + return; + } + this.nextBullet(this._editorView!.state.selection.to); + setTimeout(this.recordSession, 2000); + } + + recordKeyHandler = (e: KeyboardEvent) => { + if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) { + return; + } + e.stopPropagation(); + e.preventDefault(); + e.which === 174 && e.altKey && this.recordSession(); } componentDidMount() { - document.removeEventListener("keypress", this.considerRecordingSession); - document.addEventListener("keypress", this.considerRecordingSession); const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -335,7 +384,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe updatedState.selection = { type: "text", anchor: end, head: end }; this._editorView.updateState(EditorState.fromJSON(config, updatedState)); } - this.tryUpdateHeight(); } } ); @@ -595,7 +643,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { - document.removeEventListener("keypress", this.considerRecordingSession); this._editorView && this._editorView.destroy(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); @@ -678,6 +725,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action onFocused = (e: React.FocusEvent): void => { + document.removeEventListener("keypress", this.recordKeyHandler); + document.addEventListener("keypress", this.recordKeyHandler); if (!this.props.isOverlay) { FormattedTextBox.InputBoxOverlay = this; } else { @@ -727,6 +776,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } onBlur = (e: any) => { + document.removeEventListener("keypress", this.recordKeyHandler); if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; -- cgit v1.2.3-70-g09d2 From 8cffe031fe364e19897cfb638cffa9e4056c4343 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 22 Aug 2019 11:07:11 -0400 Subject: fixed textheight with titles. fixed marquee with floating docs. other tweaks. --- src/client/util/DocumentManager.ts | 4 +++- src/client/views/MainOverlayTextBox.tsx | 1 + .../views/collections/CollectionStackingView.tsx | 13 ++++++++----- .../views/collections/CollectionViewChromes.tsx | 14 +++++++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 20 ++++++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 10 ++++++++-- src/client/views/nodes/FormattedTextBox.tsx | 6 ++++-- 7 files changed, 51 insertions(+), 17 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 7f526b247..124faf266 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -9,6 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView'; import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { undoBatch, UndoManager } from './UndoManager'; +import { Scripting } from './Scripting'; export class DocumentManager { @@ -202,4 +203,5 @@ export class DocumentManager { return 1; } } -} \ No newline at end of file +} +Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) }) \ No newline at end of file diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 9fe435bc5..0839e1114 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -144,6 +144,7 @@ export class MainOverlayTextBox extends React.Component Document={FormattedTextBox.InputBoxOverlay.props.Document} DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} onClick={undefined} + ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index be6ee1756..4ab656744 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -287,15 +287,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { masonryChildren(docs: Doc[]) { this._docXfs.length = 0; return docs.map((d, i) => { + const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d); + if (!pair.layout || pair.data instanceof Promise) { + return (null); + } let dref = React.createRef(); - let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); - let height = () => this.getDocHeight(layoutDoc); - let dxf = () => this.getDocTransform(layoutDoc!, dref.current!); + let height = () => this.getDocHeight(pair.layout); + let dxf = () => this.getDocTransform(pair.layout!, dref.current!); let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); this._docXfs.push({ dxf: dxf, width: width, height: height }); - return !layoutDoc ? (null) :
- {this.getDisplayDoc(layoutDoc, d, dxf, width)} + return !pair.layout ? (null) :
+ {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
; }); } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 25b152d4e..74e57611d 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -297,11 +297,15 @@ export class CollectionViewBaseChrome extends React.Component { if (node) { - this._picker = datepicker("#" + node.id, { - disabler: (date: Date) => date > new Date(), - onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), - dateSelected: new Date() - }); + try { + this._picker = datepicker("#" + node.id, { + disabler: (date: Date) => date > new Date(), + onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), + dateSelected: new Date() + }); + } catch (e) { + console.log("date picker exception:" + e); + } } } render() { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index aad26efa0..221237365 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -373,7 +373,7 @@ export class MarqueeView extends React.Component marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; let selection: Doc[] = []; - this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => { + this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { var x = NumCast(doc.x); var y = NumCast(doc.y); var w = NumCast(doc.width); @@ -383,7 +383,7 @@ export class MarqueeView extends React.Component } }); if (!selection.length && selectBackgrounds) { - this.props.activeDocuments().map(doc => { + this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { var x = NumCast(doc.x); var y = NumCast(doc.y); var w = NumCast(doc.width); @@ -393,6 +393,22 @@ export class MarqueeView extends React.Component } }); } + if (!selection.length) { + let left = this._downX < this._lastX ? this._downX : this._lastX; + let top = this._downY < this._lastY ? this._downY : this._lastY; + let topLeft = this.props.getContainerTransform().transformPoint(left, top); + let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; + this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { + var x = NumCast(doc.x); + var y = NumCast(doc.y); + var w = NumCast(doc.width); + var h = NumCast(doc.height); + if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { + selection.push(doc); + } + }); + } return selection; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8dadf2bef..0c577af86 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -715,7 +715,13 @@ export class DocumentView extends DocComponent(Docu chromeHeight = () => { let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - return showTitle ? 25 : 0; + let templates = Cast(this.layoutDoc.templates, listSpec("string")); + if (!showOverlays && templates instanceof List) { + templates.map(str => { + if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title"; + }); + } + return (showTitle ? 25 : 0) + 1;// bcz: why 8?? } get layoutDoc() { @@ -769,7 +775,7 @@ export class DocumentView extends DocComponent(Docu {!showTitle && !showCaption ? this.contents :
-
+
{this.contents}
{!showTitle ? (null) : diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 849f942b6..94d9f2f66 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -374,6 +374,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.unhighlightSearchTerms(); } }, { fireImmediately: true }); + this.tryUpdateHeight(); } pushToGoogleDoc = async () => { @@ -735,11 +736,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { + const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; + console.log(this.props.Document.title + " " + sh + " " + (ChromeHeight && ChromeHeight())); if (this.props.Document.autoHeight && sh !== 0) { let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); - const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight; this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? sh : undefined; } @@ -775,7 +777,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
Date: Thu, 22 Aug 2019 12:00:47 -0400 Subject: cleanup --- src/client/util/DictationManager.ts | 30 ++++++++++++++++++----------- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/new_fields/RichTextField.ts | 6 ++++++ 5 files changed, 27 insertions(+), 14 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 781e5e465..fb3c15cea 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -64,6 +64,7 @@ export namespace DictationManager { export type ListeningUIStatus = { interim: boolean } | false; export interface ListeningOptions { + useOverlay: boolean; language: string; continuous: ContinuityArgs; delimiters: DelimiterArgs; @@ -74,27 +75,34 @@ export namespace DictationManager { export const listen = async (options?: Partial) => { let results: string | undefined; - // let main = MainView.Instance; + let main = MainView.Instance; - // main.dictationOverlayVisible = true; - // main.isListening = { interim: false }; + let overlay = options !== undefined && options.useOverlay; + if (overlay) { + main.dictationOverlayVisible = true; + main.isListening = { interim: false }; + } try { results = await listenImpl(options); if (results) { Utils.CopyText(results); - // main.isListening = false; - // let execute = options && options.tryExecute; - // main.dictatedPhrase = execute ? results.toLowerCase() : results; - // main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + if (overlay) { + main.isListening = false; + let execute = options && options.tryExecute; + main.dictatedPhrase = execute ? results.toLowerCase() : results; + main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + } options && options.tryExecute && await DictationManager.Commands.execute(results); } } catch (e) { - // main.isListening = false; - // main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; - // main.dictationSuccess = false; + if (overlay) { + main.isListening = false; + main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + main.dictationSuccess = false; + } } finally { - // main.initiateDictationFade(); + overlay && main.initiateDictationFade(); } return results; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 8a7295c65..d0464bd5f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -105,7 +105,7 @@ export default class KeyManager { switch (keyname) { case " ": - DictationManager.Controls.listen({ tryExecute: true }); + DictationManager.Controls.listen({ useOverlay: true, tryExecute: true }); stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0286a201b..f28844009 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,7 +40,6 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; -import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils'; @observer export class MainView extends React.Component { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d9043c739..5492a457d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, autorun } from "mobx"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index cae5623e6..1b52e6f82 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -28,6 +28,12 @@ export class RichTextField extends ObjectField { return `new RichTextField("${this.Data}")`; } + public static Initialize = (initial: string) => { + !initial.length && (initial = " "); + let pos = initial.length + 1; + return `{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${initial}"}]}]},"selection":{"type":"text","anchor":${pos},"head":${pos}}}`; + } + [ToPlainText]() { // Because we're working with plain text, just concatenate all paragraphs let content = JSON.parse(this.Data).doc.content; -- cgit v1.2.3-70-g09d2 From 224d58da7c8bf8a7eb27cb616100473afcad4f7b Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 22 Aug 2019 12:22:54 -0400 Subject: cleaned up more context menu stuff. fixed buttons to follow links by default. --- src/client/util/SelectionManager.ts | 1 - .../views/collections/CollectionTreeView.tsx | 5 +++ .../collectionFreeForm/CollectionFreeFormView.tsx | 47 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 24 +++++++---- 4 files changed, 46 insertions(+), 31 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index ee623d082..9efef888d 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,7 +4,6 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; import { InkingControl } from "../views/InkingControl"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; export namespace SelectionManager { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b61894b29..7e1aacd5d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -28,6 +28,7 @@ import "./CollectionTreeView.scss"; import React = require("react"); import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; +import { ContextMenuProps } from '../ContextMenuItem'; export interface TreeViewProps { @@ -541,6 +542,10 @@ export class CollectionTreeView extends CollectionSubView(Document) { e.stopPropagation(); e.preventDefault(); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + } else { + let layoutItems: ContextMenuProps[] = []; + layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" }); + ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" }); } } outerXf = () => Utils.GetScreenTransform(this._mainEle!); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cbc123c61..224e8047d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -345,9 +345,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, -1); if (cluster !== -1) { let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + + // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view + let prevSelected = SelectionManager.SelectedDocuments(); this.selectDocuments(eles); let clusterDocs = SelectionManager.SelectedDocuments(); SelectionManager.DeselectAll(); + prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); + let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); @@ -818,30 +823,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); - layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - layoutItems.push({ - description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, - event: async () => { - Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes - Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; - this.props.Document.useClusters = !this.props.Document.useClusters; - }, - icon: !this.props.Document.useClusters ? "braille" : "braille" - }); layoutItems.push({ - description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, - event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, - icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" - }); - layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); - ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); - - let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); - let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; - analyzers.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); - ContextMenu.Instance.addItem({ description: "Import document", icon: "upload", event: () => { const input = document.createElement("input"); input.type = "file"; @@ -871,6 +853,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { input.click(); } }); + layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); + layoutItems.push({ + description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, + event: async () => { + Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes + Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; + this.props.Document.useClusters = !this.props.Document.useClusters; + }, + icon: !this.props.Document.useClusters ? "braille" : "braille" + }); + layoutItems.push({ + description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, + event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, + icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" + }); + layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); + layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); + ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0c577af86..6c944c6b2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,6 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { DocumentType } from '../../documents/DocumentTypes'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -294,8 +295,8 @@ export class DocumentView extends DocComponent(Docu onClick = async (e: React.MouseEvent) => { if (e.nativeEvent.cancelBubble) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing. - e.stopPropagation(); if (this.onClickHandler && this.onClickHandler.script) { + e.stopPropagation(); this.onClickHandler.script.run({ this: this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }); e.preventDefault(); return; @@ -303,6 +304,7 @@ export class DocumentView extends DocComponent(Docu let altKey = e.altKey; let ctrlKey = e.ctrlKey; if (this._doubleTap && this.props.renderDepth) { + e.stopPropagation(); let fullScreenAlias = Doc.MakeAlias(this.props.Document); fullScreenAlias.templates = new List(); Doc.UseDetailLayout(fullScreenAlias); @@ -314,10 +316,13 @@ export class DocumentView extends DocComponent(Docu else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { + if (BoolCast(this.props.Document.ignoreClick)) { + return; + } + e.stopPropagation(); SelectionManager.SelectDoc(this, e.ctrlKey); let isExpander = (e.target as any).id === "isExpander"; - if (BoolCast(this.props.Document.isButton) || isExpander) { - SelectionManager.DeselectAll(); + if (BoolCast(this.props.Document.isButton) || this.props.Document.type === DocumentType.BUTTON || isExpander) { let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs); let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs); let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs); @@ -328,6 +333,7 @@ export class DocumentView extends DocComponent(Docu expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents + SelectionManager.DeselectAll(); let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); if (altKey || ctrlKey) { @@ -356,6 +362,7 @@ export class DocumentView extends DocComponent(Docu } } else if (linkedDocs.length) { + SelectionManager.DeselectAll(); let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document)); let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; @@ -393,13 +400,15 @@ export class DocumentView extends DocComponent(Docu } if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - // } } onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble && this.active) { + if (e.cancelBubble && this.active) { + document.removeEventListener("pointermove", this.onPointerMove); + } + else if (!e.cancelBubble && this.active) { if (!this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) { if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) { document.removeEventListener("pointermove", this.onPointerMove); @@ -582,7 +591,7 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); let existingMake = ContextMenu.Instance.findByDescription("Make..."); let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; - makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); makes.push({ @@ -592,6 +601,7 @@ export class DocumentView extends DocComponent(Docu this.makeBtnClicked(); }, icon: "window-restore" }); + makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" }) !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; -- cgit v1.2.3-70-g09d2 From 20f7d2dca1c115c84f6ac89981ef1e3c7c9a2757 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 22 Aug 2019 23:18:42 -0400 Subject: added start of collection script building UI --- src/client/util/DragManager.ts | 4 +- .../views/collections/CollectionViewChromes.scss | 73 +++++++++++++++++- .../views/collections/CollectionViewChromes.tsx | 87 ++++++++++++++++++++-- src/client/views/nodes/ButtonBox.tsx | 27 ++++++- src/new_fields/Doc.ts | 3 +- 5 files changed, 184 insertions(+), 10 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 894b366ef..24c093213 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -140,6 +140,8 @@ export namespace DragManager { withoutShiftDrag?: boolean; + finishDrag?: (dropData: { [id: string]: any }) => void; + offsetX?: number; offsetY?: number; @@ -234,7 +236,7 @@ export namespace DragManager { export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { runInAction(() => StartDragFunctions.map(func => func())); - StartDrag(eles, dragData, downX, downY, options, + StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { (dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) : diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index f39bd877a..64411b5fe 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -64,7 +64,7 @@ font-size: 75%; background: rgb(238, 238, 238); height: 100%; - width: 150px; + width: 75px; } .collectionViewBaseChrome-viewSpecsMenu { @@ -234,4 +234,75 @@ margin-left: 50px; } } +} + + +.commandEntry-outerDiv { + display: flex; + flex-direction: column; + width: 165px; + height: 40px; +} +.commandEntry-inputArea { + display:flex; + flex-direction: row; + width: 150px; + margin: auto 0 auto auto; +} + +.react-autosuggest__container { + position: relative; + width: 100%; + margin-left: 5px; + margin-right: 5px; +} + +.react-autosuggest__input { + border: 1px solid #aaa; + border-radius: 4px; + width: 100%; +} + +.react-autosuggest__input--focused { + outline: none; +} + +.react-autosuggest__input--open { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.react-autosuggest__suggestions-container { + display: none; +} + +.react-autosuggest__suggestions-container--open { + display: block; + position: fixed; + overflow-y: auto; + max-height: 400px; + width: 180px; + border: 1px solid #aaa; + background-color: #fff; + font-family: Helvetica, sans-serif; + font-weight: 300; + font-size: 16px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + z-index: 2; +} + +.react-autosuggest__suggestions-list { + margin: 0; + padding: 0; + list-style-type: none; +} + +.react-autosuggest__suggestion { + cursor: pointer; + padding: 10px 20px; +} + +.react-autosuggest__suggestion--highlighted { + background-color: #ddd; } \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 74e57611d..352bcd4a6 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -8,7 +8,7 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils } from "../../../Utils"; +import { Utils, emptyFunction } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; import { undoBatch } from "../../util/UndoManager"; @@ -18,7 +18,9 @@ import { DocLike } from "../MetadataEntryMenu"; import { CollectionViewType } from "./CollectionBaseView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; +import * as Autosuggest from 'react-autosuggest'; import KeyRestrictionRow from "./KeyRestrictionRow"; +import { Docs } from "../../documents/Documents"; const datepicker = require('js-datepicker'); interface CollectionViewChromeProps { @@ -43,6 +45,8 @@ export class CollectionViewBaseChrome extends React.Component(); @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); } private _picker: any; @@ -287,7 +291,12 @@ export class CollectionViewBaseChrome extends React.Component(de.data.draggedDocuments); + } e.stopPropagation(); return true; } @@ -308,6 +317,63 @@ export class CollectionViewBaseChrome extends React.Component(); + + renderSuggestion = (suggestion: string) => { + return

{suggestion}

; + } + getSuggestionValue = (suggestion: string) => suggestion; + + @action + onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + this._currentKey = newValue; + } + onSuggestionFetch = async ({ value }: { value: string }) => { + const sugg = await this.getKeySuggestions(value); + runInAction(() => this.suggestions = sugg); + } + @action + onSuggestionClear = () => { + this.suggestions = []; + } + getKeySuggestions = async (value: string): Promise => { + return this._allCommands.filter(c => c.indexOf(value) !== -1); + } + + autoSuggestDown = (e: React.PointerEvent) => { + e.stopPropagation(); + } + dragCommandDown = (e: React.PointerEvent) => { + let de = new DragManager.DocumentDragData([this.props.CollectionView.props.Document], [undefined]); + DragManager.StartDocumentDrag([this._commandRef.current!], de, e.clientX, e.clientY, { + finishDrag: (dropData: { [id: string]: any }) => { + let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: this._currentKey }); + let script = `getProto(target).data = copyField(this.source);`; + let compiled = CompileScript(script, { + params: { doc: Doc.name }, + capturedVariables: { target: this.props.CollectionView.props.Document }, + typecheck: false, + editable: true + }); + if (compiled.compiled) { + let scriptField = new ScriptField(compiled); + bd.onClick = scriptField; + } + dropData.droppedDocuments = [bd]; + }, + handlers: { + dragComplete: action(() => { + }), + }, + hideSource: false + }); + e.preventDefault(); + e.stopPropagation(); + } + render() { let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled"; return ( @@ -337,7 +403,7 @@ export class CollectionViewBaseChrome extends React.Component
{ }} onPointerDown={this.openViewSpecs} @@ -388,8 +454,19 @@ export class CollectionViewBaseChrome extends React.Component
-
- TEMPLATE +
+
+
+ +
+
{this.subChrome()} diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 8b6f11aac..ca5f0acc2 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -15,7 +15,11 @@ import { Doc } from '../../../new_fields/Doc'; import './ButtonBox.scss'; import { observer } from 'mobx-react'; import { DocumentIconContainer } from './DocumentIcon'; -import { StrCast } from '../../../new_fields/Types'; +import { StrCast, BoolCast } from '../../../new_fields/Types'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; +import { action, computed } from 'mobx'; +import { List } from '../../../new_fields/List'; library.add(faEdit as any); @@ -30,10 +34,29 @@ const ButtonDocument = makeInterface(ButtonSchema); @observer export class ButtonBox extends DocComponent(ButtonDocument) { public static LayoutString() { return FieldView.LayoutString(ButtonBox); } + private dropDisposer?: DragManager.DragDropDisposer; + @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + + + protected createDropTarget = (ele: HTMLDivElement) => { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + Doc.GetProto(this.dataDoc).source = new List(de.data.droppedDocuments); + } + } render() { return ( -
+
{this.Document.text || this.Document.title}
); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e5b609966..f6114d476 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -626,4 +626,5 @@ export namespace Doc { } } Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; }); -Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); \ No newline at end of file +Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); +Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2