diff options
Diffstat (limited to 'src/client/views')
| -rw-r--r-- | src/client/views/DocumentDecorations.tsx | 50 | ||||
| -rw-r--r-- | src/client/views/InkingControl.scss | 8 | ||||
| -rw-r--r-- | src/client/views/InkingControl.tsx | 35 | ||||
| -rw-r--r-- | src/client/views/Main.scss | 30 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 31 | ||||
| -rw-r--r-- | src/client/views/PresentationView.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 56 |
8 files changed, 149 insertions, 65 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 1111cd2f5..ceca940b6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,6 +24,7 @@ import { LinkMenu } from "./nodes/LinkMenu"; import { TemplateMenu } from "./TemplateMenu"; import { Template, Templates } from "./Templates"; import React = require("react"); +import { URLField } from '../../new_fields/URLField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -41,6 +42,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> private _titleHeight = 20; private _linkButton = React.createRef<HTMLDivElement>(); private _linkerButton = React.createRef<HTMLDivElement>(); + private _embedButton = React.createRef<HTMLDivElement>(); private _downX = 0; private _downY = 0; private _iconDoc?: Doc = undefined; @@ -329,12 +331,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onLinkerButtonUp); document.addEventListener("pointerup", this.onLinkerButtonUp); } + + onEmbedButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.addEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + document.addEventListener("pointerup", this.onEmbedButtonUp); + } + onLinkerButtonUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onLinkerButtonMoved); document.removeEventListener("pointerup", this.onLinkerButtonUp); e.stopPropagation(); } + onEmbedButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + e.stopPropagation(); + } + @action onLinkerButtonMoved = (e: PointerEvent): void => { if (this._linkerButton.current !== null) { @@ -354,6 +371,25 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); } + @action + onEmbedButtonMoved = (e: PointerEvent): void => { + if (this._embedButton.current !== null) { + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + + let dragDocView = SelectionManager.SelectedDocuments()[0]; + let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + + DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + e.stopPropagation(); + } + onLinkButtonDown = (e: React.PointerEvent): void => { e.stopPropagation(); document.removeEventListener("pointermove", this.onLinkButtonMoved); @@ -511,6 +547,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> // e.stopPropagation(); // } + considerEmbed = () => { + let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; + let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; + if (!canEmbed) return (null); + return ( + <div className="linkButtonWrapper"> + <div style={{ paddingTop: 3, marginLeft: 30 }} title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}> + <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> + </div> + </div> + ); + } + render() { var bounds = this.Bounds; let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; @@ -605,6 +654,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> </div> </div> <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} /> + {this.considerEmbed()} </div> </div > </div> diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss index 2c53dc031..465e14d07 100644 --- a/src/client/views/InkingControl.scss +++ b/src/client/views/InkingControl.scss @@ -1,7 +1,5 @@ @import "globalCssVariables"; .inking-control { - position: absolute; - right: 0px; bottom: 20px; margin: 0; padding: 0; @@ -63,10 +61,9 @@ margin-top: 4px; } .ink-panel { - margin: 6px 12px 6px 0; - height: 30px; + height: 24px; vertical-align: middle; - line-height: 36px; + line-height: 28px; padding: 0 10px; color: $intermediate-color; &:first { @@ -114,7 +111,6 @@ border-radius: 11px; width: 22px; height: 22px; - margin-top: 6px; cursor: pointer; text-align: center; // span { // color: $light-color; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index d1a6eb7fd..b98132c23 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -4,7 +4,6 @@ import React = require("react"); import { observer } from "mobx-react"; import "./InkingControl.scss"; import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons'; import { SelectionManager } from "../util/SelectionManager"; import { InkTool } from "../../new_fields/InkField"; @@ -19,7 +18,6 @@ export class InkingControl extends React.Component { @observable private _selectedColor: string = "rgb(244, 67, 54)"; @observable private _selectedWidth: string = "25"; @observable private _open: boolean = false; - @observable private _colorPickerDisplay = false; constructor(props: Readonly<{}>) { super(props); @@ -57,45 +55,14 @@ export class InkingControl extends React.Component { return this._selectedWidth; } - selected = (tool: InkTool) => { - if (this._selectedTool === tool) { - return { color: "#61aaa3" }; - } - return {}; - } - @action toggleDisplay = () => { this._open = !this._open; + this.switchTool(this._open ? InkTool.Pen : InkTool.None); } - - - @action - toggleColorPicker = () => { - this._colorPickerDisplay = !this._colorPickerDisplay; - } - render() { return ( <ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}> - <li className="ink-tools ink-panel"> - <div className="ink-tool-buttons"> - <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button> - <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button> - <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button> - <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button> - </div> - </li> - <li className="ink-color ink-panel"> - <label>COLOR: </label> - <div className="ink-color-display" style={{ backgroundColor: this._selectedColor }} - onClick={() => this.toggleColorPicker()}> - {/* {this._colorPickerDisplay ? <span>▼</span> : <span>▲</span>} */} - </div> - <div className="ink-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}> - <CirclePicker onChange={this.switchColor} circleSize={22} width={"220"} /> - </div> - </li> <li className="ink-size ink-panel"> <label htmlFor="stroke-width">SIZE: </label> <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width" diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 57a53c999..690139341 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -126,6 +126,26 @@ button:hover { margin-bottom: 10px; } } +.toolbar-color-picker { + background-color: $light-color; + border-radius: 5px; + padding: 12px; + position: absolute; + bottom: 36px; + left: -3px; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; +} +.toolbar-color-button { + border-radius: 11px; + width: 22px; + height: 22px; + cursor: pointer; + text-align: center; // span { + // color: $light-color; + // font-size: 8px; + // user-select: none; + // } +} // add nodes menu. Note that the + button is actually an input label, not an actual button. #add-nodes-menu { @@ -133,7 +153,7 @@ button:hover { bottom: 22px; left: 24px; - label { + > label { background: $dark-color; color: $light-color; display: inline-block; @@ -155,15 +175,15 @@ button:hover { transform: scale(1.15); } - input { + > input { display: none; } - input:not(:checked)~#add-options-content { + > input:not(:checked)~#add-options-content { display: none; } - input:checked~label { + > input:checked~label { transform: rotate(45deg); transition: transform 0.5s; cursor: pointer; @@ -207,7 +227,7 @@ ul#add-options-list { list-style: none; padding: 5 0 0 0; - li { + > li { display: inline-block; padding: 0; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4b328f286..879c2aca0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,6 +3,7 @@ import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; +import { CirclePicker } from 'react-color'; import "normalize.css"; import * as React from 'react'; import Measure from 'react-measure'; @@ -32,6 +33,7 @@ import { listSpec } from '../../new_fields/Schema'; import { Id } from '../../new_fields/FieldSymbols'; import { HistoryUtil } from '../util/History'; import { CollectionBaseView } from './collections/CollectionBaseView'; +import { InkTool } from '../../new_fields/InkField'; @observer @@ -217,6 +219,15 @@ export class MainView extends React.Component { </Measure>; } + selected = (tool: InkTool) => { + if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" }; + if (InkingControl.Instance.selectedTool === tool) { + return { color: "#61aaa3", fontSize: "50%" }; + } + return { fontSize: "50%" }; + } + + @observable private _colorPickerDisplay = false; /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */ nodesMenu() { @@ -241,18 +252,35 @@ export class MainView extends React.Component { <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li> <li key="undo"><button className="add-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li> <li key="redo"><button className="add-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li> + <li key="color"><button className="add-button round-button" title="Redo" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} > + + <div className="toolbar-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}> + <CirclePicker onChange={InkingControl.Instance.switchColor} circleSize={22} width={"220"} /> + </div> + </div></button></li> {btns.map(btn => <li key={btn[1]} ><div ref={btn[0]}> <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}> <FontAwesomeIcon icon={btn[1]} size="sm" /> </button> </div></li>)} - <li key="ink"><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button></li> + <li key="ink" style={{ paddingRight: "6px" }}><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button></li> + <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button></li> + <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Pen" /></button></li> + <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Pen" /></button></li> + <li key="inkControls"><InkingControl /></li> </ul> </div> </div >; } + + + @action + toggleColorPicker = () => { + this._colorPickerDisplay = !this._colorPickerDisplay; + } + /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ @computed get miscButtons() { @@ -295,7 +323,6 @@ export class MainView extends React.Component { <ContextMenu /> {this.nodesMenu()} {this.miscButtons} - <InkingControl /> <MainOverlayTextBox /> </div> ); diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx index b0c93ee26..d2d41a4ba 100644 --- a/src/client/views/PresentationView.tsx +++ b/src/client/views/PresentationView.tsx @@ -18,7 +18,6 @@ interface PresListProps extends PresViewProps { gotoDocument(index: number): void; } - @observer /** * Component that takes in a document prop and a boolean whether it's collapsed or not. diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 051940cc4..6fe01963a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -269,7 +269,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab"); - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], linkedFwdContextDocs[altKey ? 1 : 0]); + let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext); } } } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 6a490109f..e5a43c60a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -5,34 +5,31 @@ import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; +import { NodeType } from 'prosemirror-model'; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; import { Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; -import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; -import buildKeymap from "../../util/ProsemirrorKeymap"; +import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { SummarizedView, ImageResizeView, schema } from "../../util/RichTextSchema"; +import { ImageResizeView, schema, SummarizedView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { ContextMenu } from "../../views/ContextMenu"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { DocUtils } from '../../documents/Documents'; -import { start } from 'repl'; -import { ContextMenuProps } from '../ContextMenuItem'; library.add(faEdit); library.add(faSmile); @@ -60,13 +57,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return FieldView.LayoutString(FormattedTextBox, fieldStr); } private _ref: React.RefObject<HTMLDivElement>; - private _proseRef: React.RefObject<HTMLDivElement>; + private _proseRef?: HTMLDivElement; private _editorView: Opt<EditorView>; private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; private _applyingChange: boolean = false; private _linkClicked = ""; private _reactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; + private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @observable _entered = false; @@ -98,7 +96,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe super(props); this._ref = React.createRef(); - this._proseRef = React.createRef(); if (this.props.isOverlay) { DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } @@ -122,6 +119,29 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + protected createDropTarget = (ele: HTMLDivElement) => { + this._proseRef = ele; + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + + @undoBatch + @action + drop = async (e: Event, de: DragManager.DropEvent) => { + // We're dealing with a link to a document + if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) { + // We're dealing with an internal document drop + let url = de.data.urlField.url.href; + let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image; + this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url }))); + e.stopPropagation(); + } + } + componentDidMount() { const config = { schema, @@ -172,8 +192,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!startup && !field && doc) { startup = StrCast(doc[fieldKey]); } - if (this._proseRef.current) { - this._editorView = new EditorView(this._proseRef.current, { + if (this._proseRef) { + this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction, nodeViews: { @@ -261,7 +281,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onClick = (e: React.MouseEvent): void => { - this._proseRef.current!.focus(); + this._proseRef!.focus(); + if (this._linkClicked) { + e.preventDefault(); + e.stopPropagation(); + } } onMouseDown = (e: React.MouseEvent): void => { if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself @@ -336,7 +360,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let subitems: ContextMenuProps[] = []; subitems.push({ description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height", - event: action(() => this.props.Document.autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt" + event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems }); } @@ -353,7 +377,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", }} - onKeyPress={this.onKeyPress} + onKeyDown={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} onContextMenu={this.specificContextMenu} @@ -366,7 +390,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > - <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} /> + <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} /> </div> ); } |
