diff options
| author | bob <bcz@cs.brown.edu> | 2020-02-10 10:42:00 -0500 |
|---|---|---|
| committer | bob <bcz@cs.brown.edu> | 2020-02-10 10:42:00 -0500 |
| commit | 4bf20cf3d17989f7e5aa99c405bad96227f16fde (patch) | |
| tree | 8e22e1e85c1c17425f14953ee64311b9488312a0 /src/client/views/nodes | |
| parent | 79d701589ff4bee4d7534b7112b4f959af743769 (diff) | |
| parent | a411e92f6e7b486108f5d06564a2cdbbe91ae5ad (diff) | |
Merge branch 'master' into new_importer
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.scss | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 41 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValueBox.scss | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/SliderBox-components.tsx | 256 | ||||
| -rw-r--r-- | src/client/views/nodes/SliderBox-tooltip.css | 33 | ||||
| -rw-r--r-- | src/client/views/nodes/SliderBox.scss | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/SliderBox.tsx | 125 |
9 files changed, 457 insertions, 28 deletions
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 3b9015994..5df430411 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,7 +1,6 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, StrCast } from "../../../new_fields/Types"; import { OmitKeys, Without } from "../../../Utils"; import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; @@ -14,6 +13,7 @@ import { LinkFollowBox } from "../linking/LinkFollowBox"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; import { ButtonBox } from "./ButtonBox"; +import { SliderBox } from "./SliderBox"; import { DocumentBox } from "./DocumentBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; @@ -33,6 +33,8 @@ import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import { InkingStroke } from "../InkingStroke"; import React = require("react"); +import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; + import { TraceMobx } from "../../../new_fields/util"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -104,10 +106,10 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { <ObserverJsxParser blacklistedAttrs={[]} components={{ - FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView, + FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, - ColorBox, DocuLinkBox, InkingStroke, DocumentBox + ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox }} bindings={this.CreateBindings()} jsx={this.layout} diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 2ce56c73d..b9045b11e 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -42,6 +42,14 @@ z-index: 1; } + .documentView-contentBlocker { + pointer-events: all; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + } .documentView-styleWrapper { position: absolute; display: inline-block; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dcdce5fce..1199ed7ee 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,56 +1,51 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction, trace } from "mobx"; +import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { Document, PositionDocument } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; +import { InkTool } from '../../../new_fields/InkField'; +import { List } from '../../../new_fields/List'; +import { RichTextField } from '../../../new_fields/RichTextField'; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { ImageField, PdfField, VideoField, AudioField } from '../../../new_fields/URLField'; +import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField'; +import { TraceMobx } from '../../../new_fields/util'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; -import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents"; +import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; +import { InteractionUtils } from '../../util/InteractionUtils'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionViewType } from '../collections/CollectionView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionView } from "../collections/CollectionView"; +import { CollectionView, CollectionViewType } from '../collections/CollectionView'; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; +import { InkingControl } from '../InkingControl'; import { OverlayView } from '../OverlayView'; import { ScriptBox } from '../ScriptBox'; import { ScriptingRepl } from '../ScriptingRepl'; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; -import React = require("react"); -import { InteractionUtils } from '../../util/InteractionUtils'; -import { InkingControl } from '../InkingControl'; -import { InkTool } from '../../../new_fields/InkField'; -import { TraceMobx } from '../../../new_fields/util'; -import { List } from '../../../new_fields/List'; import { FormattedTextBoxComment } from './FormattedTextBoxComment'; -import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { RadialMenu } from './RadialMenu'; -import { RadialMenuProps } from './RadialMenuItem'; +import React = require("react"); -import { CollectionStackingView } from '../collections/CollectionStackingView'; -import { RichTextField } from '../../../new_fields/RichTextField'; -import { HistoryUtil } from '../../util/History'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -269,12 +264,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } else if (this.onClickHandler && this.onClickHandler.script) { + SelectionManager.DeselectAll(); this.onClickHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log); } else if (this.Document.type === DocumentType.BUTTON) { ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); - } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script - FormattedTextBoxComment.Hide(); - this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)])); } else if (this.Document.isButton) { SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. this.buttonClick(e.altKey, e.ctrlKey); @@ -983,7 +976,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu height: "100%", opacity: this.Document.opacity }}> - {this.innards} + {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <> + {this.innards} + <div className="documentView-contentBlocker" /> + </> : + this.innards} </div>; } } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9370d3745..449dca3a1 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -574,7 +574,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (ret.frag.size > 2 && ret.start >= 0) { let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected } editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss index 6e8a36c6a..a26880c9e 100644 --- a/src/client/views/nodes/KeyValueBox.scss +++ b/src/client/views/nodes/KeyValueBox.scss @@ -74,7 +74,7 @@ $header-height: 30px; .keyValueBox-evenRow { position: relative; - display: inline-block; + display: flex; width:100%; height:$header-height; background: $light-color; @@ -114,7 +114,7 @@ $header-height: 30px; .keyValueBox-oddRow { position: relative; - display: inline-block; + display: flex; width:100%; height:30px; background: $light-color-secondary; diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx new file mode 100644 index 000000000..a38cad459 --- /dev/null +++ b/src/client/views/nodes/SliderBox-components.tsx @@ -0,0 +1,256 @@ +import * as React from "react"; +import { SliderItem } from "react-compound-slider"; +import "./SliderBox-tooltip.css"; + +const { Component, Fragment } = React; + +// ******************************************************* +// TOOLTIP RAIL +// ******************************************************* +const railStyle: React.CSSProperties = { + position: "absolute", + width: "100%", + height: 40, + top: -13, + borderRadius: 7, + cursor: "pointer", + opacity: 0.3, + zIndex: 300, + border: "1px solid grey" +}; + +const railCenterStyle: React.CSSProperties = { + position: "absolute", + width: "100%", + height: 14, + borderRadius: 7, + cursor: "pointer", + pointerEvents: "none", + backgroundColor: "rgb(155,155,155)" +}; + +interface TooltipRailProps { + activeHandleID: string; + getRailProps: (props: object) => object; + getEventData: (e: Event) => object; +} + +export class TooltipRail extends Component<TooltipRailProps> { + state = { + value: null, + percent: null + }; + + static defaultProps = { + disabled: false + }; + + onMouseEnter = () => { + document.addEventListener("mousemove", this.onMouseMove); + }; + + onMouseLeave = () => { + this.setState({ value: null, percent: null }); + document.removeEventListener("mousemove", this.onMouseMove); + }; + + onMouseMove = (e: Event) => { + const { activeHandleID, getEventData } = this.props; + + if (activeHandleID) { + this.setState({ value: null, percent: null }); + } else { + this.setState(getEventData(e)); + } + }; + + render() { + const { value, percent } = this.state; + const { activeHandleID, getRailProps } = this.props; + + return ( + <Fragment> + {!activeHandleID && value ? ( + <div + style={{ + left: `${percent}%`, + position: "absolute", + marginLeft: "-11px", + marginTop: "-35px" + }} + > + <div className="tooltip"> + <span className="tooltiptext">Value: {value}</span> + </div> + </div> + ) : null} + <div + style={railStyle} + {...getRailProps({ + onMouseEnter: this.onMouseEnter, + onMouseLeave: this.onMouseLeave + })} + /> + <div style={railCenterStyle} /> + </Fragment> + ); + } +} + +// ******************************************************* +// HANDLE COMPONENT +// ******************************************************* +interface HandleProps { + key: string; + handle: SliderItem; + isActive: Boolean; + disabled?: Boolean; + domain: number[]; + getHandleProps: (id: string, config: object) => object; +} + +export class Handle extends Component<HandleProps> { + static defaultProps = { + disabled: false + }; + + state = { + mouseOver: false + }; + + onMouseEnter = () => { + this.setState({ mouseOver: true }); + }; + + onMouseLeave = () => { + this.setState({ mouseOver: false }); + }; + + render() { + const { + domain: [min, max], + handle: { id, value, percent }, + isActive, + disabled, + getHandleProps + } = this.props; + const { mouseOver } = this.state; + + return ( + <Fragment> + {(mouseOver || isActive) && !disabled ? ( + <div + style={{ + left: `${percent}%`, + position: "absolute", + marginLeft: "-11px", + marginTop: "-35px" + }} + > + <div className="tooltip"> + <span className="tooltiptext">Value: {value}</span> + </div> + </div> + ) : null} + <div + role="slider" + aria-valuemin={min} + aria-valuemax={max} + aria-valuenow={value} + style={{ + left: `${percent}%`, + position: "absolute", + marginLeft: "-11px", + marginTop: "-6px", + zIndex: 400, + width: 24, + height: 24, + cursor: "pointer", + border: 0, + borderRadius: "50%", + boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)", + backgroundColor: disabled ? "#666" : "#3e1db3" + }} + {...getHandleProps(id, { + onMouseEnter: this.onMouseEnter, + onMouseLeave: this.onMouseLeave + })} + /> + </Fragment> + ); + } +} + +// ******************************************************* +// TRACK COMPONENT +// ******************************************************* +interface TrackProps { + source: SliderItem; + target: SliderItem; + disabled: Boolean; + getTrackProps: () => object; +} + +export function Track({ + source, + target, + getTrackProps, + disabled = false +}: TrackProps) { + return ( + <div + style={{ + position: "absolute", + height: 14, + zIndex: 1, + backgroundColor: disabled ? "#999" : "#3e1db3", + borderRadius: 7, + cursor: "pointer", + left: `${source.percent}%`, + width: `${target.percent - source.percent}%` + }} + {...getTrackProps()} + /> + ); +} + +// ******************************************************* +// TICK COMPONENT +// ******************************************************* +interface TickProps { + tick: SliderItem; + count: number; + format: (val: number) => string; +} + +const defaultFormat = (d: number) => `d`; + +export function Tick({ tick, count, format = defaultFormat }: TickProps) { + return ( + <div> + <div + style={{ + position: "absolute", + marginTop: 17, + width: 1, + height: 5, + backgroundColor: "rgb(200,200,200)", + left: `${tick.percent}%` + }} + /> + <div + style={{ + position: "absolute", + marginTop: 25, + fontSize: 10, + textAlign: "center", + marginLeft: `${-(100 / count) / 2}%`, + width: `${100 / count}%`, + left: `${tick.percent}%` + }} + > + {format(tick.value)} + </div> + </div> + ); +} diff --git a/src/client/views/nodes/SliderBox-tooltip.css b/src/client/views/nodes/SliderBox-tooltip.css new file mode 100644 index 000000000..8afde8eb5 --- /dev/null +++ b/src/client/views/nodes/SliderBox-tooltip.css @@ -0,0 +1,33 @@ +.tooltip { + position: relative; + display: inline-block; + border-bottom: 1px dotted #222; + margin-left: 22px; + } + + .tooltip .tooltiptext { + width: 100px; + background-color: #222; + color: #fff; + opacity: 0.8; + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + z-index: 1; + bottom: 150%; + left: 50%; + margin-left: -60px; + } + + .tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #222 transparent transparent transparent; + } +
\ No newline at end of file diff --git a/src/client/views/nodes/SliderBox.scss b/src/client/views/nodes/SliderBox.scss new file mode 100644 index 000000000..4ef277d8c --- /dev/null +++ b/src/client/views/nodes/SliderBox.scss @@ -0,0 +1,8 @@ +.sliderBox-outerDiv { + width: 100%; + height: 100%; + pointer-events: all; + border-radius: inherit; + display: flex; + flex-direction: column; +}
\ No newline at end of file diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx new file mode 100644 index 000000000..2c9effe24 --- /dev/null +++ b/src/client/views/nodes/SliderBox.tsx @@ -0,0 +1,125 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit } from '@fortawesome/free-regular-svg-icons'; +import { computed, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Handles, Rail, Slider, Tracks, Ticks } from 'react-compound-slider'; +import { Doc } from '../../../new_fields/Doc'; +import { documentSchema } from '../../../new_fields/documentSchemas'; +import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; +import { ScriptField } from '../../../new_fields/ScriptField'; +import { BoolCast, FieldValue, StrCast, NumCast, Cast } from '../../../new_fields/Types'; +import { DragManager } from '../../util/DragManager'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { DocComponent } from '../DocComponent'; +import './SliderBox.scss'; +import { Handle, TooltipRail, Track, Tick } from './SliderBox-components'; +import { FieldView, FieldViewProps } from './FieldView'; +import { ScriptBox } from '../ScriptBox'; + + +library.add(faEdit as any); + +const SliderSchema = createSchema({ + _sliderMin: "number", + _sliderMax: "number", + _sliderMinThumb: "number", + _sliderMaxThumb: "number", +}); + +type SliderDocument = makeInterface<[typeof SliderSchema, typeof documentSchema]>; +const SliderDocument = makeInterface(SliderSchema, documentSchema); + +@observer +export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(SliderDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); } + private dropDisposer?: DragManager.DragDropDisposer; + + @computed get dataDoc() { + return this.props.DataDoc && + (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) || + this.props.DataDoc.layout === this.Document) ? this.props.DataDoc : Doc.GetProto(this.Document); + } + + specificContextMenu = (e: React.MouseEvent): void => { + const funcs: ContextMenuProps[] = []; + funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) }); + ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" }); + } + onChange = (values: readonly number[]) => runInAction(() => { + this.Document._sliderMinThumb = values[0]; + this.Document._sliderMaxThumb = values[1]; + Cast(this.Document.onThumbChanged, ScriptField, null)?.script.run({ range: values, this: this.props.Document }) + }) + + render() { + const domain = [NumCast(this.props.Document._sliderMin), NumCast(this.props.Document._sliderMax)] + const defaultValues = [NumCast(this.props.Document._sliderMinThumb), NumCast(this.props.Document._sliderMaxThumb)]; + return ( + <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()} + style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}> + <div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{ + background: this.Document.backgroundColor, color: this.Document.color || "black", + fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "" + }} > + <Slider + mode={2} + step={1} + domain={domain} + rootStyle={{ position: "relative", width: "100%" }} + onChange={this.onChange} + values={defaultValues} + > + + <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> + <Handles> + {({ handles, activeHandleID, getHandleProps }) => ( + <div className="slider-handles"> + {handles.map(handle => ( + <Handle + key={handle.id} + handle={handle} + domain={domain} + isActive={handle.id === activeHandleID} + getHandleProps={getHandleProps} + /> + ))} + </div> + )} + </Handles> + <Tracks left={false} right={false}> + {({ tracks, getTrackProps }) => ( + <div className="slider-tracks"> + {tracks.map(({ id, source, target }) => ( + <Track + key={id} + source={source} + target={target} + disabled={false} + getTrackProps={getTrackProps} + /> + ))} + </div> + )} + </Tracks> + <Ticks count={5}> + {({ ticks }) => ( + <div className="slider-tracks"> + {ticks.map((tick) => ( + <Tick + key={tick.id} + tick={tick} + count={ticks.length} + format={(val: number) => val.toString()} + /> + ))} + </div> + )} + </Ticks> + </Slider> + </div> + </div> + ); + } +}
\ No newline at end of file |
