diff options
author | bobzel <zzzman@gmail.com> | 2021-04-11 13:16:51 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-11 13:16:51 -0400 |
commit | 607e219255efbe2961bc23d5bd296c810e39e8d1 (patch) | |
tree | 04fec0def75bb4c1264306717de31ebd0a2a2518 /src | |
parent | f87c528f95912319c4181b3955cad4f1042c021e (diff) | |
parent | b215d4860454fca9050bb96a5f7c222c1eb9a3c7 (diff) |
Merge pull request #952 from browngraphicslab/demo_changes
Demo changes
Diffstat (limited to 'src')
33 files changed, 593 insertions, 130 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3e05787bf..219890945 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -164,6 +164,7 @@ export class DocumentOptions { version?: string; // version identifier for a document label?: string; hidden?: boolean; + mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing" autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. toolTip?: string; // tooltip to display on hover @@ -703,8 +704,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); } - export function ScreenshotDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options); + export function ScreenshotDocument(title: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", { ...options, title }); } export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) { @@ -1340,7 +1341,7 @@ export namespace DocUtils { } } - export function LeavePushpin(doc: Doc) { + export function LeavePushpin(doc: Doc, annotationField: string) { if (doc.isPushpin) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); const hasContextAnchor = DocListCast(doc.links). @@ -1353,7 +1354,7 @@ export namespace DocUtils { icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#ACCEF7", _width: 15, _height: 15, _xPadding: 0, _isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null) }); - Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin); + Doc.AddDocToList(context, annotationField, pushpin); const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", ""); doc._timecodeToShow = undefined; return pushpin; diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss new file mode 100644 index 000000000..71539ee1e --- /dev/null +++ b/src/client/util/CaptureManager.scss @@ -0,0 +1,175 @@ +@import "../views/globalCssVariables"; + +.capture-interface { + //background-color: whitesmoke !important; + width: 450px; + + button { + background: #315a96; + outline: none; + border-radius: 5px; + border: 0px; + color: #fcfbf7; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + padding: 10px; + margin: 10px; + transition: transform 0.2s; + margin: 2px; + } +} + +.capture-t1 { + display: flex; + justify-content: left; + align-items: center; + font-size: 20px; + font-family: 'Roboto'; + font-weight: 600; + color: black; + + .recordButtonOutline { + border-radius: 100%; + width: 25px; + height: 25px; + margin-right: 10px; + border: solid 1px #a94442; + display: flex; + align-items: center; + justify-content: center; + } + + .recordButtonInner { + border-radius: 100%; + width: 70%; + height: 70%; + background: #a94442; + } +} + +.capture-t2 { + font-size: 12px; + padding-right: 15px; + color: black; + margin-top: 10px; + margin-bottom: 10px; + /* right: 135; */ + // position: absolute; + // left: 243; +} + +.capture-block { + display: block; + padding-bottom: 8px; + padding-top: 6px; + + .capture-block-title { + font-size: 16; + font-weight: bold; + text-align: left; + color: black; + width: 80; + margin-right: 50px; + margin-bottom: 5px; + } + + .capture-block-list { + height: 135px; + width: calc(100% + 15px); + overflow: scroll; + } + + .capture-block-radio { + font-size: 12; + display: block; + font-weight: normal; + + .radio-container { + display: flex; + justify-content: left; + align-items: center; + font-size: 13px; + font-family: 'Roboto'; + } + } + + .list-item { + display: flex; + height: 25px; + font-family: 'Roboto'; + font-size: 13px; + + .number { + width: 20px; + height: 20px; + display: flex; + justify-content: center; + align-items: center; + background-color: #BDDBE8; + border-radius: 100%; + font-weight: 800; + margin-right: 5px; + } + } + + .buttons { + display: flex; + position: absolute; + bottom: 0; + right: 15; + justify-content: flex-end; + align-items: center; + height: 60px; + + .save { + cursor: pointer; + width: 80px; + height: 40px; + font-size: 14px; + display: flex; + font-weight: bold; + justify-content: center; + align-items: center; + background: #337ab7; + color: whitesmoke; + border-radius: 5px; + text-transform: uppercase; + } + + .cancel { + cursor: pointer; + width: 80px; + height: 40px; + font-size: 14px; + display: flex; + font-weight: 100; + justify-content: center; + align-items: center; + background: #ccc; + color: black; + border-radius: 5px; + text-transform: uppercase; + margin-left: 10px; + } + } +} + +.close-button { + position: absolute; + top: 10; + right: 10; + background:transparent; + border-radius:100%; + width: 25px; + height: 25px; + display: flex; + align-items: center; + justify-content: center; + transition: 0.2s; +} + +.close-button:hover { + background: rgba(0,0,0,0.1); +} + diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx new file mode 100644 index 000000000..c38337c91 --- /dev/null +++ b/src/client/util/CaptureManager.tsx @@ -0,0 +1,140 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { convertToObject } from "typescript"; +import { Doc, DocListCast } from "../../fields/Doc"; +import { BoolCast, StrCast, Cast } from "../../fields/Types"; +import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; +import { LightboxView } from "../views/LightboxView"; +import { MainViewModal } from "../views/MainViewModal"; +import "./CaptureManager.scss"; +import { SelectionManager } from "./SelectionManager"; +import { undoBatch } from "./UndoManager"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +@observer +export class CaptureManager extends React.Component<{}> { + public static Instance: CaptureManager; + static _settingsStyle = addStyleSheet(); + @observable _document: any; + @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. + + + constructor(props: {}) { + super(props); + CaptureManager.Instance = this; + } + + public close = action(() => this.isOpen = false); + public open = action((doc: Doc) => { + this.isOpen = true; + this._document = doc; + }); + + + @computed get visibilityContent() { + + return <div className="capture-block"> + <div className="capture-block-title">Visibility</div> + <div className="capture-block-radio"> + <div className="radio-container"> + <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private + </div> + <div className="radio-container"> + <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public + </div> + </div> + </div>; + } + + @computed get linksContent() { + const doc = this._document; + const order: JSX.Element[] = []; + if (doc) { + console.log('title', doc.title); + console.log('links', doc.links); + const linkDocs = DocListCast(doc.links); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2 + linkDocs.forEach((l, i) => { + if (l) { + console.log(i, (l.anchor1 as Doc).title); + console.log(i, (l.anchor2 as Doc).title); + order.push( + <div className="list-item"> + <div className="number">{i}</div> + {(l.anchor1 as Doc).title} + </div> + ); + } + }); + } + + return <div className="capture-block"> + <div className="capture-block-title">Links</div> + <div className="capture-block-list"> + {order} + </div> + </div>; + } + + @computed get closeButtons() { + return <div className="capture-block"> + <div className="buttons"> + <div className="save" onClick={() => { + LightboxView.SetLightboxDoc(this._document); + this.close(); + }}> + Save + </div> + <div className="cancel" onClick={() => { + const selected = SelectionManager.Views().slice(); + SelectionManager.DeselectAll(); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + this.close(); + }}> + Cancel + </div> + </div> + </div> + } + + + + private get captureInterface() { + return <div className="capture-interface"> + <div className="capture-t1"> + <div className="recordButtonOutline" style={{}}> + <div className="recordButtonInner" style={{}}> + </div> + </div> + Conversation Capture + </div> + <div className="capture-t2"> + + </div> + {this.visibilityContent} + {this.linksContent} + <div className="close-button" onClick={this.close}> + <FontAwesomeIcon icon={"times"} color="black" size={"lg"} /> + </div> + {this.closeButtons} + </div>; + + } + + render() { + return <MainViewModal + contents={this.captureInterface} + isDisplayed={this.isOpen} + interactive={true} + closeOnExternalClick={this.close} + dialogueBoxStyle={{ width: "500px", height: "350px", border: "none", background: "whitesmoke" }} + overlayStyle={{ background: "black" }} + overlayDisplayedOpacity={0.6} + /> + } +}
\ No newline at end of file diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 0875d9be7..cb8bf5a7f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -423,7 +423,7 @@ export class CurrentUserUtils { ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) }); + doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, _width: 400, _height: 200, system: true, cloneFieldFilter: new List<string>(["system"]) }); } if (doc.emptyAudio === undefined) { doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) }); diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss index c9b5e7658..a275901be 100644 --- a/src/client/views/AntimodeMenu.scss +++ b/src/client/views/AntimodeMenu.scss @@ -7,7 +7,7 @@ height: $antimodemenu-height; background: #323232; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - border-radius: 0px 6px 6px 6px; + // border-radius: 0px 6px 6px 6px; z-index: 1001; display: flex; @@ -24,6 +24,16 @@ background-color: transparent; width: 35px; height: 35px; + padding: 5; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + position: relative; + + .svg { + margin: 0; + } &.active { background-color: #121212; @@ -31,7 +41,7 @@ } .antimodeMenu-button:hover { - background-color: #121212; + background-color: rgba(0, 0, 0, 0.4); } .antimodeMenu-dragger { diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index 5acb3e4c8..fe6d39ca4 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -140,7 +140,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, position: this.Pinned ? "unset" : undefined }}> - <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} /> + {/* {this.getDragger} */} {buttons} </div> ); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 5ff96ac8d..c017b7015 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -151,7 +151,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T if (toRemove.length !== 0) { const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc; toRemove.forEach(doc => { - leavePushpin && DocUtils.LeavePushpin(doc); + leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); doc.context = undefined; recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); @@ -216,7 +216,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T else { added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document this.props.layerProvider?.(doc, true); - DocUtils.LeavePushpin(doc); + //DocUtils.LeavePushpin(doc); doc._stayInCollection = undefined; doc.context = this.props.Document; if (annotationKey ?? this._annotationKey) Doc.GetProto(doc).annotationOn = this.props.Document; diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 8ccb64744..3f04a0f3a 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -358,9 +358,6 @@ .mainView-libraryFlyout-out { transition: width .25s; box-shadow: rgb(156, 147, 150) 0.2vw 0.2vw 0.2vw; - .mainView-docButtons { - left: 0; - } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 19a2eb1b4..fa2a738c8 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -17,6 +17,7 @@ import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, return import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Docs, DocUtils } from '../documents/Documents'; +import { CaptureManager } from '../util/CaptureManager'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DocumentManager } from '../util/DocumentManager'; import { GroupManager } from '../util/GroupManager'; @@ -274,7 +275,11 @@ export class MainView extends React.Component { @computed get dockingContent() { return <div key="docking" className={`mainContent-div${this._flyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }} - style={{ minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`, width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)` }}> + style={{ + minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`, + transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined, + width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)` + }}> {!this.mainContainer ? (null) : this.mainDocView} </div>; } @@ -621,6 +626,7 @@ export class MainView extends React.Component { <DictationOverlay /> <SharingManager /> <SettingsManager /> + <CaptureManager /> <GroupManager /> <GoogleAuthenticationManager /> <DocumentDecorations boundsLeft={this.leftOffset} boundsTop={this.topOffset} /> diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 7f91c0079..55dee005d 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -34,7 +34,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> { <div className="overlay" onClick={this.props?.closeOnExternalClick} style={{ - backgroundColor: "gainsboro", + backgroundColor: "black", ...(p.overlayStyle || {}), opacity: p.isDisplayed ? overlayOpacity : 0 }} diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index 2c81d727e..9eac75f4b 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -14,35 +14,53 @@ top: 0; width: 100%; - .antimodeMenu-button { - padding: 0; - width: 30px; + .recordButtonOutline { + border-radius: 100%; + width: 18px; + height: 18px; + border: solid 1px #f5f5f5; display: flex; + align-items: center; + justify-content: center; + } - svg { - margin: auto; - } + .recordButtonInner { + border-radius: 100%; + width: 70%; + height: 70%; + background: white; } .collectionMenu { display: flex; - padding-bottom: 1px; - height: 32px; - border-bottom: .5px solid rgb(180, 180, 180); + height: 100%; overflow: visible; z-index: 901; border: unset; + .collectionMenu-divider { + height: 85%; + margin-left: 3px; + margin-right: 3px; + width: 1.5px; + background-color: #656060; + } + .collectionViewBaseChrome { display: flex; + align-items: center; .collectionViewBaseChrome-viewPicker { font-size: 75%; - background: #323232; outline-color: black; color: white; border: none; - border-right: solid gray 1px; + background: #323232; + } + + .collectionViewBaseChrome-viewPicker:focus { + outline: none; + border: none; } .collectionViewBaseChrome-viewPicker:active { @@ -65,18 +83,26 @@ margin-left: 3px; margin-right: 0px; font-size: 75%; - background: #323232; + text-transform: capitalize; color: white; border: none; - border-right: solid gray 1px; + background: #323232; + } + + .collectionViewBaseChrome-cmdPicker:focus { + border: none; + outline: none; } .commandEntry-outerDiv { pointer-events: all; - background-color: #323232; + background-color: transparent; display: flex; flex-direction: row; - height: 30px; + align-items: center; + justify-content: center; + height: 100%; + overflow: hidden; .commandEntry-drop { color: white; @@ -86,6 +112,15 @@ } } + .commandEntry-outerDiv:hover{ + background-color: rgba(0,0,0,0.2); + + .collectionViewBaseChrome-viewPicker, + .collectionViewBaseChrome-cmdPicker{ + background: rgb(41,41,41); + } + } + .collectionViewBaseChrome-collapse { transition: all .5s, opacity 0.3s; position: absolute; @@ -103,11 +138,12 @@ .collectionViewBaseChrome-template, .collectionViewBaseChrome-viewModes { - display: grid; - background: rgb(238, 238, 238); + align-items: center; + height: 100%; + display: flex; + background: transparent; color: grey; - margin-top: auto; - margin-bottom: auto; + justify-content: center; } .collectionViewBaseChrome-viewSpecs { @@ -263,27 +299,52 @@ .collectionTreeViewChrome-pivotField-cont, .collection3DCarouselViewChrome-scrollSpeed-cont { justify-self: right; - display: flex; + align-items: center; + display: grid; + grid-auto-columns: auto; font-size: 75%; letter-spacing: 2px; .collectionStackingViewChrome-pivotField-label, .collectionTreeViewChrome-pivotField-label, .collection3DCarouselViewChrome-scrollSpeed-label { - vertical-align: center; - padding-left: 10px; - margin: auto; + grid-column: 1; + margin-right: 7px; + user-select: none; + font-family: 'Roboto'; + letter-spacing: normal; + } + + .collectionStackingViewChrome-sortIcon { + transition: transform .5s; + grid-column: 3; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + width: 25px; + height: 25px; + border-radius: 100%; + } + + .collectionStackingViewChrome-sortIcon:hover { + background-color: rgba(0,0,0,0.2); } .collectionStackingViewChrome-pivotField, .collectionTreeViewChrome-pivotField, .collection3DCarouselViewChrome-scrollSpeed { color: white; - width: 100%; + grid-column: 2; + grid-row: 1; + width: 90%; min-width: 100px; display: flex; + height: 80%; + border-radius: 7px; align-items: center; - background: rgb(238, 238, 238); + background: #eeeeee; .editable-view-input, input, @@ -378,20 +439,13 @@ display: inline-flex; position: relative; align-items: center; - margin-left: 10px; - - .antimodeMenu-button { - text-align: center; - display: block; - position: relative; - } + height: 100%; .color-previewI { - width: 80%; - height: 20%; - bottom: 0; + width: 60%; + top: 80%; position: absolute; - margin-left: 2px; + height: 4px; } .color-previewII { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 623e05b33..a26953ff6 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -34,6 +34,9 @@ import "./CollectionMenu.scss"; import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { TabDocView } from "./TabDocView"; import { LightboxView } from "../LightboxView"; +import { Docs } from "../../documents/Documents"; +import { DocumentManager } from "../../util/DocumentManager"; +import { CollectionDockingView } from "./CollectionDockingView"; @observer export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -388,7 +391,29 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top"> <button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderLeft: "1px solid gray" }} onClick={e => TabDocView.PinDoc(targetDoc, { unpin: isPinned })}> - <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" /> + <FontAwesomeIcon className="colMenu-icon" size="lg" icon="map-pin" /> + </button> + </Tooltip>; + } + + @undoBatch + @action + startRecording = () => { + const doc = Docs.Create.ScreenshotDocument("screen recording", { _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); + //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + CollectionDockingView.AddSplit(doc, "right"); + } + + @computed + get recordButton() { + const targetDoc = this.selectedDoc; + return <Tooltip key="record" title={<div className="dash-tooltip">{"Capture screen"}</div>} placement="top"> + <button className="antimodeMenu-button" + onClick={e => this.startRecording()}> + <div className="recordButtonOutline" style={{}}> + <div className="recordButtonInner" style={{}}> + </div> + </div> </button> </Tooltip>; } @@ -478,7 +503,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp const targetDoc = this.selectedDoc; return !targetDoc || targetDoc.type === DocumentType.PRES ? (null) : <Tooltip title={<div className="dash-tooltip">{"Tap or Drag to create an alias"}</div>} placement="top"> <button className="antimodeMenu-button" onPointerDown={this.onAliasButtonDown} onClick={this.onAlias} style={{ cursor: "drag" }}> - <FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="lg" /> + <FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" /> </button> </Tooltip>; } @@ -486,38 +511,48 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @computed get lightboxButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"View in Lightbox"}</div>} placement="top"> - <button className="antimodeMenu-button" style={{ borderRight: "1px solid gray", justifyContent: 'center' }} onPointerDown={() => { + <button className="antimodeMenu-button" onPointerDown={() => { const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); LightboxView.SetLightboxDoc(targetDoc, undefined, docs); }}> - <FontAwesomeIcon className="documentdecorations-icon" icon="desktop" size="lg" /> + <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" /> </button> </Tooltip>; } + @computed get toggleOverlayButton() { + return <> + <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> + <button className={"antimodeMenu-button"} key="float" + style={{ + backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined, + pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "none" : undefined, + color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "dimgrey" : undefined + }} + onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> + <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} /> + </button> + </Tooltip> + </> + } + render() { return ( <div className="collectionMenu-cont" > <div className="collectionMenu"> <div className="collectionViewBaseChrome"> + {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} + <div className="collectionMenu-divider" key="divider1"></div> {this.aliasButton} {/* {this.pinButton} */} + {this.toggleOverlayButton} {this.pinWithViewButton} - {this.lightboxButton} - <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> - <button className={"antimodeMenu-button"} key="float" - style={{ - backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined, borderRight: "1px solid gray", - pointerEvents: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "none" : undefined, - color: this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? "dimgrey" : undefined - }} - onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> - <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} /> - </button> - </Tooltip> + <div className="collectionMenu-divider" key="divider2"></div> {this.subChrome} - {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} - {!this._buttonizableCommands ? (null) : this.templateChrome} + <div className="collectionMenu-divider" key="divider3"></div> + {this.lightboxButton} + {this.recordButton} + {/* {!this._buttonizableCommands ? (null) : this.templateChrome} */} </div> </div> </div> diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 6f6cdd5d2..6baf633dd 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -2,7 +2,7 @@ import { action, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; import { NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, setupMoveUpEvents, returnTrue } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; @@ -31,9 +31,16 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { + const isStarburst = this.layoutEngine() === "starburst"; const draggingSelf = this.props.isSelected(); - return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" || (SnappingManager.GetIsDragging() && !draggingSelf) ? undefined : "none", zIndex: this.layoutEngine() === "starburst" && !SnappingManager.GetIsDragging() ? -10 : "auto" }} > - <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} + return <div className="collectionPileView-innards" + style={{ + pointerEvents: isStarburst || (SnappingManager.GetIsDragging() && !draggingSelf) ? undefined : "none", + zIndex: isStarburst && !SnappingManager.GetIsDragging() ? -10 : "auto" + }} > + <CollectionFreeFormView {...this.props} + layoutEngine={this.layoutEngine} + childDocumentsActive={isStarburst ? returnTrue : undefined} addDocument={undoBatch((doc: Doc | Doc[]) => { (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); return this.props.addDocument?.(doc) || false; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 6a1242f20..ab9c4aa2c 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -234,8 +234,9 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument return level; } - dictationHeight = () => this.props.PanelHeight() / 3; - timelineContentHeight = () => this.props.PanelHeight() * 2 / 3; + dictationHeightPercent = 50; + dictationHeight = () => `${this.dictationHeightPercent}%`; + timelineContentHeight = () => this.props.PanelHeight() * this.dictationHeightPercent / 100; dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight()); @computed get renderDictation() { const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); @@ -348,7 +349,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> (time) => { const dictationDoc = Cast(this.props.layoutDoc["data-dictation"], Doc, null); const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc); - if ((isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc)) && DocListCast(this.props.mark.links).length && + if (!LightboxView.LightboxDoc + // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. + // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. + /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/ + && DocListCast(this.props.mark.links).length && time > NumCast(this.props.mark[this.props.startTag]) && time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag])) { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 8d8c69fd5..1aed40bc3 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -55,7 +55,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } - @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); } + @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } @computed get yMargin() { return this.props.yMargin || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index bee7aeb87..47733994b 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -235,7 +235,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } }); const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y); - ContextMenu.Instance.displayMenu(x, y); + ContextMenu.Instance.displayMenu(x, y, undefined, true); } @computed get innards() { TraceMobx(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b998555d8..a5d62acb4 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -74,7 +74,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const { Document, DataDoc } = this.props; const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc)). filter(pair => { // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys - return pair.layout && !pair.layout.hidden && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length)); + return pair.layout && /*!pair.layout.hidden &&*/ (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length)); }); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 4dc0ee3e6..85ae66fdc 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -66,6 +66,7 @@ export interface CollectionViewProps extends FieldViewProps { // property overrides for child documents children?: never | (() => JSX.Element[]) | React.ReactNode; childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) + childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) childFitWidth?: () => boolean; childOpacity?: () => number; childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c623ce653..7a879ecde 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1032,7 +1032,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P docRangeFilters={this.freeformRangeDocFilters} searchFilterDocs={this.searchFilterDocs} isContentActive={this.isAnnotationOverlay ? this.props.isContentActive : returnFalse} - isDocumentActive={this.isContentActive} + isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive} focus={this.focusDocument} addDocTab={this.addDocTab} addDocument={this.props.addDocument} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 85899578c..a2e36f12e 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -53,7 +53,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @observable _position: number = 0; @observable _waveHeight: Opt<number> = this.layoutDoc._height; @observable _paused: boolean = false; - @computed get mediaState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.mediaState as (undefined | "recording" | "paused" | "playing"); } + @computed get mediaState(): undefined | "pendingRecording" | "recording" | "paused" | "playing" { return this.dataDoc.mediaState as (undefined | "pendingRecording" | "recording" | "paused" | "playing"); } set mediaState(value) { this.dataDoc.mediaState = value; } public static SetScrubTime = action((timeInMillisFrom1970: number) => { AudioBox._scrubTime = 0; AudioBox._scrubTime = timeInMillisFrom1970; }); @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 0fc7a752f..4f4df32b3 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -20,6 +20,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { select: (isCtrlPressed: boolean) => void; isContentActive: (outsideReaction?: boolean) => boolean; + isDocumentActive?: () => boolean; isSelected: (outsideReaction?: boolean) => boolean; scaling?: () => number; setHeight: (height: number) => void; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index bf9ca1de0..111509fdb 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -58,7 +58,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { value = eq ? value.substr(1) : value; const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false; value = dubEq ? value.substr(2) : value; - const options: ScriptOptions = { addReturn: true, params: { this: "Doc", _last_: "any" }, globals: true, editable: false }; + const options: ScriptOptions = { addReturn: true, params: { this: "Doc", _last_: "any" }, editable: false }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index d55156057..d5056d934 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -262,6 +262,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} setPdfViewer={this.setPdfViewer} addDocument={this.addDocument} + moveDocument={this.moveDocument} + removeDocument={this.removeDocument} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} startupLive={true} ContentScaling={this.props.scaling} diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 8c5f77550..f3fb6ff17 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; -import { Doc, DocListCast, DocListCastAsync } from "../../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; @@ -29,6 +29,7 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; import Color = require("color"); +import { LightboxView } from "../LightboxView"; export enum PresMovement { Zoom = "zoom", @@ -246,11 +247,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // } } - stopTempMedia = (targetDoc: Doc) => { - if (targetDoc.type === DocumentType.AUDIO) { + stopTempMedia = (targetDocField: FieldResult) => { + const targetDoc = Cast(targetDocField, Doc, null); + if (targetDoc?.type === DocumentType.AUDIO) { if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]); targetDoc._audioStop = true; - } else if (targetDoc.type === DocumentType.VID) { + } else if (targetDoc?.type === DocumentType.VID) { if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]); targetDoc._triggerVideoStop = true; } @@ -330,14 +332,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - if (from && from.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { - const mediaDocList = DocListCast(from.mediaStopTriggerList); - mediaDocList.forEach((doc) => { - this.stopTempMedia(Cast(doc.presentationTargetDoc, Doc, null)); - }); + if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { + DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); } - if (from && from.mediaStop === "auto" && this.layoutDoc.presStatus !== PresStatus.Edit) { - this.stopTempMedia(Cast(from.presentationTargetDoc, Doc, null)); + if (from?.mediaStop === "auto" && this.layoutDoc.presStatus !== PresStatus.Edit) { + this.stopTempMedia(from.presentationTargetDoc); } // If next slide is audio / video 'Play automatically' then the next slide should be played if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && (activeItem.mediaStart === "auto")) { @@ -433,10 +432,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> }; // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { - openInTab(targetDoc); + LightboxView.SetLightboxDoc(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { + LightboxView.SetLightboxDoc(undefined); await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { + LightboxView.SetLightboxDoc(undefined); //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right } @@ -574,6 +575,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (this._presTimer) clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; this.layoutDoc.presLoop = false; + this.childDocs.forEach(this.stopTempMedia); } } @@ -633,10 +635,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> updateMinimize = () => { const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc); if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + console.log("case 1"); this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, "right"); } else if (this.layoutDoc.context && docView) { + console.log("case 2"); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); @@ -647,15 +651,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> docView.props.removeDocument?.(this.layoutDoc); Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); } else { - this.layoutDoc.presStatus = PresStatus.Edit; - clearTimeout(this._presTimer); - const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); - this.rootDoc.y = pt[1] + 10; - this.rootDoc._height = 35; - this.rootDoc._width = 250; - this.props.addDocTab?.(this.rootDoc, "close"); - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); + console.log("case 3"); + // TODO glr: fix this case } } @@ -1193,7 +1190,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return ( - <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> + <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> <div className="ribbon-box"> Movement {isPresCollection || (isPresCollection && isPinWithView) ? @@ -1248,7 +1245,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-doubleButton"> {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>} {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>} - <Tooltip title={<><div className="dash-tooltip">{"Open document in a new tab"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Open</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip> </div> {(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <> <div className="ribbon-doubleButton" > diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 8163b652c..c4f8d1143 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -11,20 +11,21 @@ import { makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { AudioField, VideoField } from "../../../fields/URLField"; +import { TraceMobx } from "../../../fields/util"; import { emptyFunction, OmitKeys, returnFalse, returnOne, Utils } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { Networking } from "../../Network"; +import { CaptureManager } from "../../util/CaptureManager"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { FieldView, FieldViewProps } from './FieldView'; +import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import "./ScreenshotBox.scss"; import { VideoBox } from "./VideoBox"; -import { TraceMobx } from "../../../fields/util"; -import { FormattedTextBox } from "./formattedText/FormattedTextBox"; declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has } @@ -35,7 +36,7 @@ const ScreenshotDocument = makeInterface(documentSchema); @observer export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } - private _videoRef = React.createRef<HTMLVideoElement>(); + private _videoRef: HTMLVideoElement | null = null; private _audioRec: any; private _videoRec: any; @observable _screenCapture = false; @@ -53,7 +54,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl } videoLoad = () => { - const aspect = this._videoRef.current!.videoWidth / this._videoRef.current!.videoHeight; + const aspect = this._videoRef!.videoWidth / this._videoRef!.videoHeight; const nativeWidth = Doc.NativeWidth(this.layoutDoc); const nativeHeight = Doc.NativeHeight(this.layoutDoc); if (!nativeWidth || !nativeHeight) { @@ -79,9 +80,17 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl @computed get content() { const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive"; - return <video className={"videoBox-content" + interactive} key="video" ref={this._videoRef} - autoPlay={this._screenCapture} - style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }} + return <video className={"videoBox-content" + interactive} key="video" + ref={r => { + this._videoRef = r; + setTimeout(() => { + if (this.rootDoc.mediaState === "pendingRecording" && this._videoRef) { // TODO glr: use mediaState + this.toggleRecording(); + } + }, 1000); + }} + autoPlay={true} + style={{ width: "100%", height: "100%" }} onCanPlay={this.videoLoad} controls={true} onClick={e => e.preventDefault()}> @@ -102,8 +111,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(Utils.prepend(result.accessPaths.agnostic.client)); } }; - this._videoRef.current!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); - this._videoRec = new MediaRecorder(this._videoRef.current!.srcObject); + this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); + this._videoRec = new MediaRecorder(this._videoRef!.srcObject); const vid_chunks: any = []; this._videoRec.onstart = () => this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date()); this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data); @@ -129,6 +138,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl this.dataDoc.mediaState = "paused"; const ind = DocUtils.ActiveRecordings.indexOf(this); ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1)); + + CaptureManager.Instance.open(this.rootDoc); } }); diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index dd8d77603..30f0c4393 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,3 +1,13 @@ +.mini-viewer{ + cursor: grab; + position: absolute; + right: 10; + top: 10; + opacity: 0.1; + transition: all 0.4s; + color: white; +} + .videoBox { transform-origin: top left; width: 100%; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ab178c60b..fc6f9ceab 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -2,20 +2,22 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { HtmlField } from "../../../fields/HtmlField"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; -import { makeInterface, listSpec } from "../../../fields/Schema"; +import { listSpec, makeInterface } from "../../../fields/Schema"; +import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, smoothScroll, Utils, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { Scripting } from "../../util/Scripting"; import { SnappingManager } from "../../util/SnappingManager"; import { undoBatch } from "../../util/UndoManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; @@ -79,7 +81,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps runInAction(() => { this._url = this.webField?.toString() || ""; - this._annotationKey = "annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + WebBox.urlHash(this._url); + // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) + this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-annotations-"+urlHash(this["${this.fieldKey}"]?.url?.toString()))`); }); this._disposers.selection = reaction(() => this.props.isSelected(), @@ -289,7 +293,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (future.length) { history.push(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!)); - this._annotationKey = "annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + WebBox.urlHash(this._url); return true; } return false; @@ -303,13 +307,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]); else future.push(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!)); - this._annotationKey = "annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + WebBox.urlHash(this._url); return true; } return false; } - urlHash = (s: string) => { + static urlHash = (s: string) => { return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); } @@ -331,7 +335,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps future && (future.length = 0); } this._url = newUrl; - this._annotationKey = "annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + WebBox.urlHash(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl)); } catch (e) { console.log("WebBox URL error:" + this._url); @@ -507,7 +511,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps PanelHeight={this.panelHeight} dropAction={"alias"} select={emptyFunction} - isContentActive={this.isContentActive} + isContentActive={returnFalse} ContentScaling={returnOne} bringToFront={emptyFunction} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} @@ -555,4 +559,5 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </button> </div>); } -}
\ No newline at end of file +} +Scripting.addGlobal(function urlHash(url: string) { return WebBox.urlHash(url); });
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9482b632a..c2860af76 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -297,7 +297,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.updateState(EditorState.fromJSON(this.config, json)); } } - if (window.getSelection()?.isCollapsed) AnchorMenu.Instance.fadeOut(true); + if (window.getSelection()?.isCollapsed && this.props.isSelected()) { + AnchorMenu.Instance.fadeOut(true); + } } } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss index fbc468292..1d24d6833 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.scss +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -95,9 +95,10 @@ .color-preview-button { .color-preview { - width: 100%; - height: 3px; - margin-top: 3px; + width: 60%; + top: 80%; + position: absolute; + height: 4px; } } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 5da868281..071491463 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -976,7 +976,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const row2 = <div className="antimodeMenu-row row-2" key="row2"> {this.collapsed ? this.getDragger() : (null)} <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}> - <div className="richTextMenu-divider" key="divider 3" />, + <div className="richTextMenu-divider" key="divider 3" /> {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => { this.activeFontSize = val; SelectionManager.Views().map(dv => dv.props.Document._fontSize = val); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 4c181e49a..a2e7b0600 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -9,7 +9,7 @@ import { createSchema } from "../../../fields/Schema"; import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils"; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils, returnFalse } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -516,6 +516,7 @@ export class PDFViewer extends React.Component<IViewerProps> { }}> <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} isAnnotationOverlay={true} + isContentActive={returnFalse} fieldKey={this.props.fieldKey + "-annotations"} setPreviewCursor={this.setPreviewCursor} PanelHeight={this.panelHeight} diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index a1fc77a92..f15d51764 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -57,6 +57,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; } componentDidMount() { + this.layoutDoc.hideLinkButton = true; this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } @@ -114,6 +115,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} + hideLinkButton={true} /> <div className="presItem-embeddedMask" /> </div>; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 300dae0aa..9faba9486 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1324,7 +1324,7 @@ export namespace Doc { } -Scripting.addGlobal(function idToDoc(id: string) { return DocServer.GetCachedRefField(id); }); +Scripting.addGlobal(function idToDoc(id: string): any { return DocServer.GetCachedRefField(id); }); Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; }); Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); |