diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-08-13 17:06:15 -0400 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-08-13 17:06:15 -0400 |
| commit | 7fe84ba95c30a082f5146e684d0c31b61ec676df (patch) | |
| tree | 6621ae183ff7032a8d8fff097e7f68b2ad574a03 /src/client/views | |
| parent | 314f62bb5335e3fb3f25501823d41cf0cdc53cac (diff) | |
| parent | 3b45f1d30a947dc1702ec347b83e98374c5b603c (diff) | |
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views')
57 files changed, 1208 insertions, 1282 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 14c922526..c779ce8c4 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -3,7 +3,7 @@ import axios from 'axios'; import { action, computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import Waveform from 'react-audio-waveform'; -import { Doc } from '../../fields/Doc'; +import { Doc, NumListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { Cast } from '../../fields/Types'; @@ -54,7 +54,7 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { } @computed get audioBuckets() { - return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec('number'), []); + return NumListCast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]); } audioBucketField = (start: number, end: number, zoomFactor: number) => this.props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10; @@ -102,7 +102,7 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { render() { return ( <div className="audioWaveform"> - <Waveform color={Colors.MEDIUM_BLUE_ALT} height={this.waveHeight} barWidth={200 / this.audioBuckets.length} pos={this.props.duration} duration={this.props.duration} peaks={this.audioBuckets} progressColor={Colors.MEDIUM_BLUE_ALT} /> + <Waveform color={Colors.MEDIUM_BLUE_ALT} height={this.waveHeight} barWidth={200 / this.audioBuckets.length} pos={this.props.duration} duration={this.props.duration} peaks={Array.from(this.audioBuckets)} progressColor={Colors.MEDIUM_BLUE_ALT} /> </div> ); } diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index ae55c8ebf..21808d6e0 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -43,7 +43,7 @@ export class DashboardView extends React.Component { @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName: string | undefined = undefined; - @observable private newDashboardColor: string | undefined = "#AFAFAF"; + @observable private newDashboardColor: string | undefined = '#AFAFAF'; @action abortCreateNewDashboard = () => { this.newDashboardName = undefined; }; @@ -100,30 +100,15 @@ export class DashboardView extends React.Component { const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const placeholder = `Dashboard ${dashboardCount}`; return ( - <div className="new-dashboard" - style={{ + <div + className="new-dashboard" + style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), - color: StrCast(Doc.UserDoc().userColor) - }} - > + color: StrCast(Doc.UserDoc().userColor), + }}> <div className="header">Create New Dashboard</div> - <EditableText - formLabel='Title' - placeholder={placeholder} - type={Type.SEC} - color={StrCast(Doc.UserDoc().userColor)} - setVal={val => this.setNewDashboardName(val as string)} - fillWidth - /> - <ColorPicker - formLabel='Background' - colorPickerType='github' - type={Type.TERT} - selectedColor={this.newDashboardColor} - setSelectedColor={color => { - this.setNewDashboardColor(color); - }} - /> + <EditableText formLabel="Title" placeholder={placeholder} type={Type.SEC} color={StrCast(Doc.UserDoc().userColor)} setVal={val => this.setNewDashboardName(val as string)} fillWidth /> + <ColorPicker formLabel="Background" colorPickerType="github" type={Type.TERT} selectedColor={this.newDashboardColor} setSelectedColor={this.setNewDashboardColor} /> <div className="button-bar"> <Button text="Cancel" color={StrCast(Doc.UserDoc().userColor)} onClick={this.abortCreateNewDashboard} /> <Button type={Type.TERT} text="Create" color={StrCast(Doc.UserDoc().userVariantColor)} onClick={() => this.createNewDashboard(this.newDashboardName!, this.newDashboardColor)} /> @@ -165,21 +150,14 @@ export class DashboardView extends React.Component { }; render() { - const color = StrCast(Doc.UserDoc().userColor) - const variant = StrCast(Doc.UserDoc().userVariantColor) + const color = StrCast(Doc.UserDoc().userColor); + const variant = StrCast(Doc.UserDoc().userVariantColor); return ( <> <div className="dashboard-view"> <div className="left-menu"> - <Button - text={'My Dashboards'} - active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} - color={color} - align={'flex-start'} - onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} - fillWidth - /> - <Button + <Button text={'My Dashboards'} active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth /> + <Button text={'Shared Dashboards' + ' (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'} active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards} color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color} @@ -196,11 +174,11 @@ export class DashboardView extends React.Component { .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key)) .some(key => dashboard[DocAcl][key] !== AclPrivate); return ( - <div - className="dashboard-container" - key={dashboard[Id]} + <div + className="dashboard-container" + key={dashboard[Id]} style={{ background: this.isUnviewedSharedDashboard(dashboard) && this.selectedDashboardGroup === DashboardGroup.SharedDashboards ? '#6CB982' : shared ? variant : '' }} - onContextMenu={e => this.onContextMenu(dashboard, e)} + onContextMenu={e => this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> <img src={ @@ -208,12 +186,7 @@ export class DashboardView extends React.Component { } /> <div className="info"> - <EditableText - type={Type.PRIM} - color={color} - val={StrCast(dashboard.title)} - setVal={val => (Doc.GetProto(dashboard).title = val)} - /> + <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (Doc.GetProto(dashboard).title = val)} /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>} <div className="more" @@ -229,10 +202,13 @@ export class DashboardView extends React.Component { <Button size={Size.SMALL} color={color} icon={<FontAwesomeIcon color={color} icon="bars" />} /> </div> </div> - <div className={`background`} style={{ - background: StrCast(Doc.UserDoc().userColor), - filter: 'opacity(0.2)' - }}/> + <div + className={`background`} + style={{ + background: StrCast(Doc.UserDoc().userColor), + filter: 'opacity(0.2)', + }} + /> <div className={'dashboard-status' + (shared ? '-shared' : '')}>{shared ? 'shared' : ''}</div> </div> ); @@ -243,10 +219,13 @@ export class DashboardView extends React.Component { this.setNewDashboardName(''); }}> + - <div className={`background`} style={{ - background: StrCast(Doc.UserDoc().userColor), - filter: 'opacity(0.2)' - }}/> + <div + className={`background`} + style={{ + background: StrCast(Doc.UserDoc().userColor), + filter: 'opacity(0.2)', + }} + /> </div> </div> </div> diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index a41fc8ded..e076e69ca 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -3,7 +3,7 @@ import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { Cast, StrCast } from '../../fields/Types'; +import { Cast, DocCast, StrCast } from '../../fields/Types'; import { distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util'; import { returnFalse } from '../../Utils'; import { DocUtils } from '../documents/Documents'; @@ -145,13 +145,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { - const recent = Doc.MyRecentlyClosed; + const recent = this.rootDoc !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined; toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + Doc.RemoveDocFromList(Doc.GetProto(doc), 'proto_embeddings', doc); doc.embedContainer = undefined; if (recent) { - Doc.RemoveDocFromList(recent, 'data', doc); doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); } }); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 16f5ad168..6f5e9f5c0 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -589,7 +589,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV <div style={{ position: 'absolute', zIndex: 1000 }}> <LinkPopup key="popup" - showPopup={this._showLinkPopup} linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))} linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this.props.views().lastElement()?.rootDoc} diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index bab07ac96..f3daf3ffa 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -617,8 +617,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } let actualdW = Math.max(docwidth + dW * scale, 20); let actualdH = Math.max(docheight + dH * scale, 20); - let dX = !dWin ? 0 : scale * refCent[0] * (1 - (1 + dWin / refWidth)); - let dY = !dHin ? 0 : scale * refCent[1] * (1 - (1 + dHin / refHeight)); + let dX = !dWin ? 0 : (scale * refCent[0] * -dWin) / refWidth; + let dY = !dHin ? 0 : (scale * refCent[1] * -dHin) / refHeight; const preserveNativeDim = !doc._nativeHeightUnfrozen && !doc._nativeDimModifiable; const fixedAspect = nwidth && nheight && (!doc._layout_fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable); if (fixedAspect) { @@ -630,6 +630,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else { if (!doc._layout_fitWidth || preserveNativeDim) { actualdH = (nheight / nwidth) * actualdW; + dYin && (dY = -dW * scale * (nheight / nwidth)); doc._height = actualdH; } else if (!modifyNativeDim || dragBotRight) { doc._height = actualdH; @@ -646,6 +647,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else { if (!doc._layout_fitWidth || preserveNativeDim) { actualdW = (nwidth / nheight) * actualdH; + dXin && (dX = -dH * scale * (nwidth / nheight)); doc._width = actualdW; } else if (!modifyNativeDim || dragBotRight) { doc._width = actualdW; @@ -911,7 +913,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> - <div className="documentDecorations-topbar" style={{ display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined }} onPointerDown={this.onContainerDown}> + <div + className="documentDecorations-topbar" + style={{ + color: 'black', + display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined, + }} + onPointerDown={this.onContainerDown}> {hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} {titleArea} diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 4a986cb54..d60617020 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -232,7 +232,7 @@ export class EditableView extends React.Component<EditableProps> { onChange: this.props.autosuggestProps.onChange, }} /> - ) : + ) : ( <input className="editableView-input" ref={r => (this._inputref = r)} @@ -248,6 +248,7 @@ export class EditableView extends React.Component<EditableProps> { onClick={this.stopPropagation} onPointerUp={this.stopPropagation} /> + ); // ) : ( // <textarea // className="editableView-input" diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 68d29942b..63bd01b19 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -169,8 +169,6 @@ export class FilterPanel extends React.Component<filterProps> { render() { const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); - console.log("this is option " + options) - console.log("this is alll facets " + this._allFacets) return ( <div className="filterBox-treeView"> <div className="filterBox-select"> @@ -192,17 +190,15 @@ export class FilterPanel extends React.Component<filterProps> { <div className="filterBox-tree" key="tree"> {Array.from(this.activeFacets.keys()).map(facetHeader => ( <div> - <div className = "filterBox-facetHeader"> - <div className = "filterBox-facetHeader-Header"> </div> + <div className="filterBox-facetHeader"> + <div className="filterBox-facetHeader-Header"> </div> {facetHeader.charAt(0).toUpperCase() + facetHeader.slice(1)} - - <div className = "filterBox-facetHeader-collapse"> - <AiOutlineMinusSquare/> - {/* <CiCircleRemove/> */} - </div> - + + <div className="filterBox-facetHeader-collapse"> + <AiOutlineMinusSquare /> + {/* <CiCircleRemove/> */} + </div> </div> - {this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader)} </div> diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index c39fa79da..6c213f40f 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -105,7 +105,7 @@ export class InkTranscription extends React.Component { : null; } - r.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); return (this._textRef = r); }; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 286d39943..f79a30ad3 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -313,8 +313,10 @@ export class LightboxView extends React.Component<LightboxViewProps> { className="lightboxView-tabBtn" title="open in tab" onClick={e => { + const lightdoc = LightboxView._docTarget || LightboxView._doc!; e.stopPropagation(); - CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none); + Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc); + CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }}> diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 2fa42d091..6dd1d53ee 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -6,10 +6,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import { AssignAllExtensions } from '../../extensions/General/Extensions'; import { FieldLoader } from '../../fields/FieldLoader'; -import { DocServer } from '../DocServer'; -import { Docs } from '../documents/Documents'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; -import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils import { ReplayMovements } from '../util/ReplayMovements'; import { TrackMovements } from '../util/TrackMovements'; import { CollectionView } from './collections/CollectionView'; @@ -20,7 +17,7 @@ import './global/globalScripts'; dotenv.config(); AssignAllExtensions(); -FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure why this is needed to get the code loaded properly... +FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; // bcz: not sure why this is needed to get the code loaded properly... (async () => { MainView.Live = window.location.search.includes('live'); @@ -29,7 +26,11 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure window.location.search.includes('safe') && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); // if (info.email === 'guest') DocServer.Control.makeReadOnly(); - await CurrentUserUtils.loadUserDocument(info.id); + if (!info.userDocumentId) { + alert('Fatal Error: user not found in database'); + return; + } + await CurrentUserUtils.loadUserDocument(info); setTimeout(() => { document.getElementById('root')!.addEventListener( 'wheel', diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 41c8dd19d..60eb64caa 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -4,7 +4,7 @@ import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'browndash-components/dist/styles/global.min.css'; -import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; +import { action, computed, configure, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import 'normalize.css'; import * as React from 'react'; @@ -49,7 +49,6 @@ import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import './MainView.scss'; -import { NewLightboxView } from './newlightbox/NewLightboxView'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; @@ -194,14 +193,6 @@ export class MainView extends React.Component { 'currentFrame', ]); // can play with these fields on someone else's } - DocServer.GetRefField('rtfProto').then( - proto => - proto instanceof Doc && - reaction( - () => StrCast(proto.BROADCAST_MESSAGE), - msg => msg && alert(msg) - ) - ); const tag = document.createElement('script'); tag.src = 'https://www.youtube.com/iframe_api'; @@ -226,7 +217,7 @@ export class MainView extends React.Component { window.removeEventListener('keydown', KeyManager.Instance.handle); window.removeEventListener('pointerdown', this.globalPointerDown, true); window.removeEventListener('pointermove', this.globalPointerMove, true); - window.removeEventListener('mouseclick', this.globalPointerClick, true); + window.removeEventListener('pointerup', this.globalPointerClick, true); window.removeEventListener('paste', KeyManager.Instance.paste as any); document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } @@ -539,13 +530,13 @@ export class MainView extends React.Component { }); initEventListeners = () => { - window.addEventListener('beforeunload', () => DocServer.UPDATE_SERVER_CACHE()); + window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE); window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page window.addEventListener('dragover', e => e.preventDefault(), false); // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined)); document.addEventListener('pointerdown', this.globalPointerDown, true); document.addEventListener('pointermove', this.globalPointerMove, true); - document.addEventListener('mouseclick', this.globalPointerClick, true); + document.addEventListener('pointerup', this.globalPointerClick, true); document.addEventListener( 'click', (e: MouseEvent) => { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 2e3668268..8cae34d7d 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -244,10 +244,10 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get layout_autoHeightButton() { // store previous dimensions to store old values return this.propertyToggleBtn( - on => 'Auto\xA0Size', + on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'), '_layout_autoHeight', on => `Automatical vertical sizing to show all content`, - on => 'arrows-alt-v' + on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" /> ); } @@ -375,7 +375,6 @@ export class PropertiesButtons extends React.Component<{}, {}> { val: value[1], }; }); - console.log('click val: ', this.onClickVal); return !this.selectedDoc ? null : ( <Dropdown tooltip={'Choose onClick behavior'} diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 395aa2b61..d157e7b1c 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -38,7 +38,6 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC }, new Set<Doc>()) .keys() ); - console.log('embeddings ' + embeddings.length); return doclayouts .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document)) diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx index ec8043ffe..6fab0168b 100644 --- a/src/client/views/PropertiesSection.tsx +++ b/src/client/views/PropertiesSection.tsx @@ -1,19 +1,19 @@ import React = require('react'); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import { action, computed, observable } from "mobx" -import { observer } from "mobx-react" -import './PropertiesSection.scss' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import './PropertiesSection.scss'; import { Doc } from '../../fields/Doc'; import { StrCast } from '../../fields/Types'; export interface PropertiesSectionProps { - title: string, - content?: JSX.Element | string | null, - isOpen: boolean, - setIsOpen: (bool: boolean) => any - inSection?: boolean, - setInSection?: (bool: boolean) => any - onDoubleClick?: () => void + title: string; + content?: JSX.Element | string | null; + isOpen: boolean; + setIsOpen: (bool: boolean) => any; + inSection?: boolean; + setInSection?: (bool: boolean) => any; + onDoubleClick?: () => void; } @observer @@ -33,34 +33,32 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> { @observable isDouble: boolean = false; render() { - console.log(this.props.title, this.props.content) - if (this.props.content === undefined || this.props.content === null) return null - else return <div className="propertiesView-section" onPointerEnter={action(() => (this.props.setInSection && this.props.setInSection(true)))} onPointerLeave={action(() => (this.props.setInSection && this.props.setInSection(false)))}> - <div className="propertiesView-sectionTitle" - onDoubleClick={action((e) => { - this.isDouble = true; - this.props.onDoubleClick && this.props.onDoubleClick() - console.log("open options") - this.props.setIsOpen(true) - setTimeout(() => this.isDouble = false, 300) - })} - onClick={action((e) => { - this.props.setIsOpen(!this.props.isOpen) - })} - style={{ - background: this.props.isOpen ? this.variantColor : this.backgroundColor, - color: this.color - }}> - {this.props.title} - <div className="propertiesView-sectionTitle-icon"> - <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" /> - </div> - </div> - {!this.props.isOpen ? null : - <div className="propertiesView-content"> - {this.props.content} - </div> - } - </div> + if (this.props.content === undefined || this.props.content === null) return null; + else + return ( + <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}> + <div + className="propertiesView-sectionTitle" + onDoubleClick={action(e => { + this.isDouble = true; + this.props.onDoubleClick && this.props.onDoubleClick(); + this.props.setIsOpen(true); + setTimeout(() => (this.isDouble = false), 300); + })} + onClick={action(e => { + this.props.setIsOpen(!this.props.isOpen); + })} + style={{ + background: this.props.isOpen ? this.variantColor : this.backgroundColor, + color: this.color, + }}> + {this.props.title} + <div className="propertiesView-sectionTitle-icon"> + <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" /> + </div> + </div> + {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.content}</div>} + </div> + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 27b9c3c7a..872f1c6ab 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -8,7 +8,7 @@ import { concat } from 'lodash'; import { Lambda, action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import * as Icons from "react-icons/bs"; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" +import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" import { GrCircleInformation } from 'react-icons/gr'; import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; @@ -51,7 +51,6 @@ interface PropertiesViewProps { addDocTab: (doc: Doc, where: OpenWhere) => boolean; } - @observer export class PropertiesView extends React.Component<PropertiesViewProps> { private _widthUndo?: UndoManager.Batch; @@ -191,7 +190,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { }); rows.push( - <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: "white", textAlign: "center" }}> + <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: 'white', textAlign: 'center' }}> <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} /> </div> ); @@ -249,14 +248,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this.props.addDocTab} />; } - @computed get contextCount(){ - console.log("in context count"); - if (this.selectedDocumentView){ - const target = (this.selectedDocumentView.props.Document) - const embeddings = DocListCast(target.proto_embeddings) - console.log(embeddings.length -1 ); - return (embeddings.length - 1) - } else{ + @computed get contextCount() { + if (this.selectedDocumentView) { + const target = this.selectedDocumentView.props.Document; + const embeddings = DocListCast(target.proto_embeddings); + return embeddings.length - 1; + } else { return 0; } } @@ -266,13 +263,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />; } - @computed get linkCount(){ - const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc; + @computed get linkCount() { + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc; var counter = 0; - LinkManager.Links(selAnchor).forEach((l, i) => - counter ++ - ); + LinkManager.Links(selAnchor).forEach((l, i) => counter++); return counter; } @@ -356,7 +351,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return ( <Tooltip title={<div className="dash-tooltip">Notify with message</div>}> <div className="notify-button"> - <FontAwesomeIcon className="notify-button-icon" icon="bell" size="sm" /> + <FontAwesomeIcon className="notify-button-icon" icon="bell" size="sm" /> </div> </Tooltip> ); @@ -367,16 +362,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ @computed get expansionIcon() { return ( - <div className="expansion-button" > - <IconButton - icon={<FontAwesomeIcon icon={'ellipsis-h'} />} - size={Size.XSMALL} - color={StrCast(Doc.UserDoc().userColor)} + <div className="expansion-button"> + <IconButton + icon={<FontAwesomeIcon icon={'ellipsis-h'} />} + size={Size.XSMALL} + color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => { if (this.selectedDocumentView || this.selectedDoc) { SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc); } - })} + })} /> </div> ); @@ -418,7 +413,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div> <div className={'propertiesView-shareDropDown'}> <div className={`propertiesView-shareDropDown${permission}`}> - <div >{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div> + <div>{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div> </div> </div> </div> @@ -450,7 +445,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { const target = docs[0]; const showAdmin = GetEffectiveAcl(target) == AclAdmin; - console.log(GetEffectiveAcl(target), Doc.GetProto(target)[`acl-${normalizeEmail(Doc.CurrentUserEmail)}`]) const individualTableEntries = []; const usersAdded: string[] = []; // all shared users being added - organized by denormalized email @@ -522,7 +516,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div> <br></br> Individuals with Access to this Document </div> - <div className="propertiesView-sharingTable" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}> + <div className="propertiesView-sharingTable" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}> {<div> {individualTableEntries}</div>} </div> {groupTableEntries.length > 0 ? ( @@ -530,7 +524,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div> <br></br> Groups with Access to this Document </div> - <div className="propertiesView-sharingTable" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}> + <div className="propertiesView-sharingTable" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}> {<div> {groupTableEntries}</div>} </div> </div> @@ -562,68 +556,43 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } @computed get editableTitle() { - const titles = new Set<string>(); const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title); SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); - return ( - <EditableText - val={title} - setVal={this.setTitle} - color={this.color} - type={Type.SEC} - formLabel={"Title"} - fillWidth - /> - ); + return <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />; } @computed get currentType() { - // console.log("current type " + this.selectedDoc?.type) - - const documentType = StrCast(this.selectedDoc?.type) - var currentType: string = documentType; - var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType); - - return ( - <div> - <div className = "propertiesView-wordType">Type</div> - <div className= "currentType"> - <div className='currentType-icon'> - {this.currentComponent} - </div> - - {capitalizedDocType} - - </div> - - </div> - - ) - } + const documentType = StrCast(this.selectedDoc?.type); + var currentType: string = documentType; + var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType); - @computed get currentComponent() { + return ( + <div> + <div className="propertiesView-wordType">Type</div> + <div className="currentType"> + <div className="currentType-icon">{this.currentComponent}</div> - var iconName = StrCast(this.selectedDoc?.systemIcon) + {capitalizedDocType} + </div> + </div> + ); + } - // if (this.selectedDoc?.type === DocumentType.COL){ - // console.log("i did it!") - // } - + @computed get currentComponent() { + var iconName = StrCast(this.selectedDoc?.systemIcon); - if (iconName){ + if (iconName) { const Icon = Icons[iconName as keyof typeof Icons]; return <Icon />; - } else{ - return <Icons.BsFillCollectionFill/> - + } else { + return <Icons.BsFillCollectionFill />; } } @undoBatch @action setTitle = (value: string | number) => { - console.log(value) if (SelectionManager.Views().length > 1) { SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, 'title', value, true)); } else if (this.dataDoc) { @@ -687,7 +656,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { className="inking-button-points" style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }} onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}> - <FontAwesomeIcon icon="bezier-curve" size="lg" /> + <FontAwesomeIcon icon="bezier-curve" size="lg" /> </div> </Tooltip> </div> @@ -706,10 +675,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <input className="inputBox-input" type="text" value={value} onChange={e => setter(e.target.value)} onKeyPress={e => e.stopPropagation()} /> <div className="inputBox-button"> <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> - <FontAwesomeIcon icon="caret-up" size="sm" /> + <FontAwesomeIcon icon="caret-up" size="sm" /> </div> <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> - <FontAwesomeIcon icon="caret-down" size="sm" /> + <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> </div> @@ -963,10 +932,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <input className="inputBox-input" type="text" value={value} onChange={e => setter(e.target.value)} /> <div className="inputBox-button"> <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> - <FontAwesomeIcon icon="caret-up" size="sm" /> + <FontAwesomeIcon icon="caret-up" size="sm" /> </div> <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> - <FontAwesomeIcon icon="caret-down" size="sm" /> + <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> </div> @@ -983,7 +952,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { this.openSharing = false; this.openLayout = false; this.openFilters = false; - } + }; @computed get widthAndDash() { return ( @@ -1065,64 +1034,45 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any) => { - return <div> - <NumberInput - formLabel={label} - formLabelPlacement={'left'} - type={Type.SEC} - unit={unit} - fillWidth - color={this.color} - number={number} - setNumber={setNumber} - min={min} - max={max} - /> - <Slider - multithumb={false} - color={this.color} - size={Size.XSMALL} - min={min} - max={max} - unit={unit} - number={number} - setNumber={setNumber} - fillWidth - /> - </div> - } + return ( + <div> + <NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} /> + <Slider multithumb={false} color={this.color} size={Size.XSMALL} min={min} max={max} unit={unit} number={number} setNumber={setNumber} fillWidth /> + </div> + ); + }; @computed get transformEditor() { return ( <div className="transform-editor"> {this.isInk ? this.controlPointsButton : null} {this.getNumber( - "Height", - " px", + 'Height', + ' px', 0, 1000, Number(this.shapeHgt), undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height') )} {this.getNumber( - "Width", - " px", + 'Width', + ' px', 0, 1000, Number(this.shapeWid), undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width') )} {this.getNumber( - "X Coordinate", - " px", + 'X Coordinate', + ' px', -2000, 2000, Number(this.shapeXps), undoable((val: string) => !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord') )} {this.getNumber( - "Y Coordinate", - " px", + 'Y Coordinate', + ' px', -2000, 2000, Number(this.shapeYps), @@ -1133,38 +1083,44 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } @computed get optionsSubMenu() { - return <PropertiesSection - title="Options" - content={<PropertiesButtons />} - inSection={this.inOptions} - isOpen={this.openOptions} - setInSection={(bool) => this.inOptions = bool} - setIsOpen={(bool) => this.openOptions = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + return ( + <PropertiesSection + title="Options" + content={<PropertiesButtons />} + inSection={this.inOptions} + isOpen={this.openOptions} + setInSection={bool => (this.inOptions = bool)} + setIsOpen={bool => (this.openOptions = bool)} + onDoubleClick={() => this.onDoubleClick()} + /> + ); } @computed get sharingSubMenu() { - return <PropertiesSection - title="Sharing & Permissions" - content={<> - {/* <div className="propertiesView-buttonContainer"> */} - <div className="propertiesView-acls-checkbox"> - Layout Permissions - <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> - </div> - {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}> + return ( + <PropertiesSection + title="Sharing & Permissions" + content={ + <> + {/* <div className="propertiesView-buttonContainer"> */} + <div className="propertiesView-acls-checkbox"> + Layout Permissions + <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> + </div> + {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}> <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}> <FontAwesomeIcon icon="redo-alt" size="1x" /> </button> </Tooltip> */} - {/* </div> */} - {this.sharingTable} - </>} - isOpen={this.openSharing} - setIsOpen={(bool) => this.openSharing = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + {/* </div> */} + {this.sharingTable} + </> + } + isOpen={this.openSharing} + setIsOpen={bool => (this.openSharing = bool)} + onDoubleClick={() => this.onDoubleClick()} + /> + ); } /** @@ -1192,15 +1148,19 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { }; @computed get filtersSubMenu() { - return <PropertiesSection - title="Filters" - content={<div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}> - <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} /> - </div>} - isOpen={this.openFilters} - setIsOpen={(bool) => this.openFilters = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + return ( + <PropertiesSection + title="Filters" + content={ + <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}> + <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} /> + </div> + } + isOpen={this.openFilters} + setIsOpen={bool => (this.openFilters = bool)} + onDoubleClick={() => this.onDoubleClick()} + /> + ); } @computed get inkSubMenu() { @@ -1208,68 +1168,42 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return ( <> - <PropertiesSection - title="Appearance" - content={this.isInk ? this.appearanceEditor : null} - isOpen={this.openAppearance} - setIsOpen={(bool) => this.openAppearance = bool} - onDoubleClick={() => this.onDoubleClick()} - /> - <PropertiesSection - title="Transform" - content={this.transformEditor} - isOpen={this.openTransform} - setIsOpen={(bool) => this.openTransform = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + <PropertiesSection title="Appearance" content={this.isInk ? this.appearanceEditor : null} isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.onDoubleClick()} /> + <PropertiesSection title="Transform" content={this.transformEditor} isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.onDoubleClick()} /> </> ); } @computed get fieldsSubMenu() { - return <PropertiesSection - title="Fields & Tags" - content={<div className="propertiesView-content fields">{ - Doc.noviceMode ? this.noviceFields : this.expandedField} - </div>} - isOpen={this.openFields} - setIsOpen={(bool) => this.openFields = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + return ( + <PropertiesSection + title="Fields & Tags" + content={<div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>} + isOpen={this.openFields} + setIsOpen={bool => (this.openFields = bool)} + onDoubleClick={() => this.onDoubleClick()} + /> + ); } @computed get contextsSubMenu() { - return <PropertiesSection - title="Other Contexts" - content={this.contextCount > 0 ? this.contexts : "There are no other contexts."} - isOpen={this.openContexts} - setIsOpen={(bool) => this.openContexts = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + return ( + <PropertiesSection + title="Other Contexts" + content={this.contextCount > 0 ? this.contexts : 'There are no other contexts.'} + isOpen={this.openContexts} + setIsOpen={bool => (this.openContexts = bool)} + onDoubleClick={() => this.onDoubleClick()} + /> + ); } - - - - @computed get linksSubMenu() { - return <PropertiesSection - title="Linked To" - content={this.linkCount > 0 ? this.links : "There are no current links." } - isOpen={this.openLinks} - setIsOpen={(bool) => this.openLinks = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + return <PropertiesSection title="Linked To" content={this.linkCount > 0 ? this.links : 'There are no current links.'} isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={() => this.onDoubleClick()} />; } @computed get layoutSubMenu() { - return <PropertiesSection - title="Layout" - content={this.layoutPreview} - isOpen={this.openLayout} - setIsOpen={(bool) => this.openLayout = bool} - onDoubleClick={() => this.onDoubleClick()} - /> + return <PropertiesSection title="Layout" content={this.layoutPreview} isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={() => this.onDoubleClick()} />; } @computed get description() { @@ -1470,224 +1404,226 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { if (scale > 1) scale = 1; this.sourceAnchor && (this.sourceAnchor.followLinkZoomScale = scale); }; - + @computed get linkProperties() { const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3)); const targZoom = this.sourceAnchor?.followLinkZoom; const indent = 30; const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!); - return <> - <div className="propertiesView-section" style={{ background: 'darkgray' }}> - <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> - <p>Relationship</p> - {this.editRelationship} - </div> - <div className="propertiesView-input" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> - <p>Description</p> - {this.editDescription} - </div> - <div className="propertiesView-input inline"> - <p>Show link</p> - <button - style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> - <p>Auto-move anchors</p> - <button - style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> - <p>Display arrow</p> - <button - style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - </div> - {!hasSelectedAnchor ? null : ( - <div className="propertiesView-section"> - <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}> - <p>Follow by</p> - <select onChange={e => this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}> - <option value={undefined}>Default</option> - <option value={OpenWhere.addLeft}>Opening in new left pane</option> - <option value={OpenWhere.addRight}>Opening in new right pane</option> - <option value={OpenWhere.replaceLeft}>Replacing left tab</option> - <option value={OpenWhere.replaceRight}>Replacing right tab</option> - <option value={OpenWhere.lightbox}>Opening in lightbox</option> - <option value={OpenWhere.add}>Opening in new tab</option> - <option value={OpenWhere.replace}>Replacing current tab</option> - <option value={OpenWhere.inParent}>Opening in same collection</option> - {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null} - </select> - </div> - <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}> - <p>Animation</p> - <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> - <option value="default">Default</option> - {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( - <option key={effect.toString()} value={effect.toString()}> - {effect.toString()} - </option> - ))} - </select> - <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}> - {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} - {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} - {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} - {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} - {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} + return ( + <> + <div className="propertiesView-section" style={{ background: 'darkgray' }}> + <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> + <p>Relationship</p> + {this.editRelationship} + </div> + <div className="propertiesView-input" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> + <p>Description</p> + {this.editDescription} + </div> + <div className="propertiesView-input inline"> + <p>Show link</p> + <button + style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> + <p>Auto-move anchors</p> + <button + style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> + <p>Display arrow</p> + <button + style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> </div> </div> - {PresBox.inputter( - '0.1', - '0.1', - '10', - NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, - true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), - indent - )}{' '} - <div - className={'slider-headers'} - style={{ - display: 'grid', - justifyContent: 'space-between', - width: `calc(100% - ${indent * 2}px)`, - marginLeft: indent, - marginRight: indent, - gridTemplateColumns: 'auto auto', - borderTop: 'solid', - }}> - <div className="slider-text">Fast</div> - <div className="slider-text">Slow</div> - </div>{' '} - <div className="propertiesView-input inline"> - <p>Play Target Audio</p> - <button - style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Zoom Text Selections</p> - <button - style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Toggle Follow to Outer Context</p> - <button - style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Toggle Target (Show/Hide)</p> - <button - style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Ease Transitions</p> - <button - style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Capture Offset to Target</p> - <button - style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => { - this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined); - this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined); - }} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Center Target (no zoom)</p> - <button - style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}> - <p>Zoom %</p> - <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}> - <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} /> - <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> - <FontAwesomeIcon icon={'caret-up'} /> + {!hasSelectedAnchor ? null : ( + <div className="propertiesView-section"> + <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}> + <p>Follow by</p> + <select onChange={e => this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}> + <option value={undefined}>Default</option> + <option value={OpenWhere.addLeft}>Opening in new left pane</option> + <option value={OpenWhere.addRight}>Opening in new right pane</option> + <option value={OpenWhere.replaceLeft}>Replacing left tab</option> + <option value={OpenWhere.replaceRight}>Replacing right tab</option> + <option value={OpenWhere.lightbox}>Opening in lightbox</option> + <option value={OpenWhere.add}>Opening in new tab</option> + <option value={OpenWhere.replace}>Replacing current tab</option> + <option value={OpenWhere.inParent}>Opening in same collection</option> + {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null} + </select> + </div> + <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}> + <p>Animation</p> + <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> + <option value="default">Default</option> + {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( + <option key={effect.toString()} value={effect.toString()}> + {effect.toString()} + </option> + ))} + </select> + <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}> + {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} + {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} + {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} + {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} + {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> - <FontAwesomeIcon icon={'caret-down'} /> + </div> + {PresBox.inputter( + '0.1', + '0.1', + '10', + NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, + true, + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), + indent + )}{' '} + <div + className={'slider-headers'} + style={{ + display: 'grid', + justifyContent: 'space-between', + width: `calc(100% - ${indent * 2}px)`, + marginLeft: indent, + marginRight: indent, + gridTemplateColumns: 'auto auto', + borderTop: 'solid', + }}> + <div className="slider-text">Fast</div> + <div className="slider-text">Slow</div> + </div>{' '} + <div className="propertiesView-input inline"> + <p>Play Target Audio</p> + <button + style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Zoom Text Selections</p> + <button + style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Toggle Follow to Outer Context</p> + <button + style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Toggle Target (Show/Hide)</p> + <button + style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Ease Transitions</p> + <button + style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Capture Offset to Target</p> + <button + style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => { + this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined); + this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined); + }} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Center Target (no zoom)</p> + <button + style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}> + <p>Zoom %</p> + <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}> + <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} /> + <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> + <FontAwesomeIcon icon={'caret-up'} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> + <FontAwesomeIcon icon={'caret-down'} /> + </div> + </div> </div> + <button + style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> </div> + {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} + <div + className={'slider-headers'} + style={{ + display: !targZoom ? 'none' : 'grid', + justifyContent: 'space-between', + width: `calc(100% - ${indent * 2}px)`, + marginLeft: indent, + marginRight: indent, + gridTemplateColumns: 'auto auto', + borderTop: 'solid', + }}> + <div className="slider-text">0%</div> + <div className="slider-text">100%</div> + </div>{' '} </div> - <button - style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} - <div - className={'slider-headers'} - style={{ - display: !targZoom ? 'none' : 'grid', - justifyContent: 'space-between', - width: `calc(100% - ${indent * 2}px)`, - marginLeft: indent, - marginRight: indent, - gridTemplateColumns: 'auto auto', - borderTop: 'solid', - }}> - <div className="slider-text">0%</div> - <div className="slider-text">100%</div> - </div>{' '} - </div> - )} - </> + )} + </> + ); } /** @@ -1723,23 +1659,20 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { width: this.props.width, minWidth: this.props.width, }}> - <div className = "propertiesView-propAndInfoGrouping"> + <div className="propertiesView-propAndInfoGrouping"> <div className="propertiesView-title" style={{ width: this.props.width }}> Properties </div> - <div className = "propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/')}> - <GrCircleInformation/> </div> - + <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/')}> + <GrCircleInformation />{' '} + </div> </div> - <div className="propertiesView-name">{this.editableTitle}</div> - <div className = "propertiesView-type"> {this.currentType} </div> + <div className="propertiesView-type"> {this.currentType} </div> {this.contextsSubMenu} {this.linksSubMenu} - {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : ( - this.linkProperties - )} + {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : this.linkProperties} {this.inkSubMenu} {this.optionsSubMenu} {this.fieldsSubMenu} @@ -1760,7 +1693,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { Presentation </div> <div className="propertiesView-name" style={{ borderBottom: 0 }}> - {this.editableTitle} <div className="propertiesView-presSelected"> <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div> @@ -1772,7 +1704,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Transitions <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" /> + <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openPresTransitions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.transitionDropdown}</div> : null} @@ -1786,7 +1718,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Visibilty <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" /> + <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openPresVisibilityAndDuration ? <div className="propertiesView-presTrails-content">{PresBox.Instance.visibiltyDurationDropdown}</div> : null} @@ -1797,7 +1729,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Progressivize <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" /> + <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null} @@ -1808,7 +1740,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))} style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'} <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" /> + <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openSlideOptions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index bbbad3690..63ff348e3 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -22,7 +22,7 @@ import { DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { SliderBox } from './nodes/SliderBox'; -import { BsArrowDown, BsArrowUp, BsArrowDownUp } from 'react-icons/bs' +import { BsArrowDown, BsArrowUp, BsArrowDownUp } from 'react-icons/bs'; import './StyleProvider.scss'; import React = require('react'); @@ -44,7 +44,6 @@ export enum StyleProp { ShowCaption = 'layout_showCaption', TitleHeight = 'titleHeight', // Height of Title area ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) - JitterRotation = 'jitterRotation', // whether documents should be randomly rotated BorderPath = 'customBorder', // border path for document view FontSize = 'fontSize', // size of text font FontFamily = 'fontFamily', // font family of text @@ -98,7 +97,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle); - const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); // prettier-ignore switch (property.split(':')[0]) { case StyleProp.TreeViewIcon: @@ -161,7 +159,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps '' ); case StyleProp.Color: - if (MainView.Instance.LastButton === doc) return Doc.UserDoc().userBackgroundColor; + if (MainView.Instance.LastButton === doc) return SettingsManager.Instance.userBackgroundColor; if (Doc.IsSystem(doc!)) return StrCast(Doc.UserDoc().userColor) if (doc?.type === DocumentType.FONTICON) return Doc.UserDoc().userColor; const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); @@ -188,8 +186,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps </div> ), }; - case StyleProp.JitterRotation: - return Doc.IsComicStyle(doc) ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) || (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) || @@ -208,12 +204,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case DocumentType.PRES: docColor = docColor || (darkScheme() ? 'transparent' : 'transparent'); break; case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break; case DocumentType.RTF: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; - case DocumentType.FILTER: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : 'rgba(105, 105, 105, 0.432)'); break; case DocumentType.INK: docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break; case DocumentType.EQUATION: docColor = docColor || 'transparent'; break; case DocumentType.LABEL: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; - case DocumentType.LINKANCHOR: docColor = isAnchor ? Colors.LIGHT_BLUE : 'transparent'; break; case DocumentType.LINK: docColor = (isAnchor ? docColor : '') || 'transparent'; break; case DocumentType.IMG: case DocumentType.WEB: @@ -224,7 +218,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case DocumentType.COL: if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break; docColor = docColor || (Doc.IsSystem(doc) - ? StrCast(Doc.UserDoc().userBackgroundColor) + ? SettingsManager.Instance.userBackgroundColor : doc.annotationOn ? '#00000010' // faint interior for collections on PDFs, images, etc : doc?._isGroup diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx index caf04cc1b..a551e5332 100644 --- a/src/client/views/UndoStack.tsx +++ b/src/client/views/UndoStack.tsx @@ -7,6 +7,7 @@ import { StrCast } from '../../fields/Types'; import { Doc } from '../../fields/Doc'; import { Popup, Type, isDark } from 'browndash-components'; import { Colors } from './global/globalEnums'; +import { SettingsManager } from '../util/SettingsManager'; interface UndoStackProps { width?: number; @@ -18,35 +19,37 @@ export class UndoStack extends React.Component<UndoStackProps> { @observable static HideInline: boolean; @observable static Expand: boolean; render() { - const background = UndoManager.batchCounter.get() ? 'yellow' : StrCast(Doc.UserDoc().userBackgroundColor) + const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.Instance.userBackgroundColor; return this.props.inline && UndoStack.HideInline ? null : ( <div className="undoStack-outerContainer"> - <Popup + <Popup text={'Undo/Redo Stack'} color={UndoManager.batchCounter.get() ? 'yellow' : StrCast(Doc.UserDoc().userVariantColor)} placement={`top-start`} type={Type.TERT} popup={ - <div className="undoStack-commandsContainer" ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })} - style={{ - background: background, - color: isDark(background) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY - }}> - {UndoManager.undoStackNames.map((name, i) => ( - <div className="undoStack-resultContainer" key={i}> - <div className="undoStack-commandString">{name.replace(/[^\.]*\./, '')}</div> - </div> - ))} - {Array.from(UndoManager.redoStackNames) - .reverse() - .map((name, i) => ( + <div + className="undoStack-commandsContainer" + ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })} + style={{ + background: background, + color: isDark(background) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY, + }}> + {UndoManager.undoStackNames.map((name, i) => ( <div className="undoStack-resultContainer" key={i}> - <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: 'red' }}> - {name.replace(/[^\.]*\./, '')} - </div> + <div className="undoStack-commandString">{name.replace(/[^\.]*\./, '')}</div> </div> - ))} - </div> + ))} + {Array.from(UndoManager.redoStackNames) + .reverse() + .map((name, i) => ( + <div className="undoStack-resultContainer" key={i}> + <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: 'red' }}> + {name.replace(/[^\.]*\./, '')} + </div> + </div> + ))} + </div> } /> </div> diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index 3465a5283..addc00c85 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -123,7 +123,7 @@ export type RegionData = makeInterface<[typeof RegionDataSchema]>; export const RegionData = makeInterface(RegionDataSchema); interface IProps { - node: Doc; + animatedDoc: Doc; RegionData: Doc; collection: Doc; tickSpacing: number; @@ -167,7 +167,7 @@ export class Keyframe extends React.Component<IProps> { return RegionData(this.props.RegionData); } @computed private get regions() { - return DocListCast(this.props.node.regions); + return DocListCast(this.props.animatedDoc.regions); } @computed private get keyframes() { return DocListCast(this.regiondata.keyframes); @@ -375,7 +375,7 @@ export class Keyframe extends React.Component<IProps> { */ @action makeRegionMenu = (kf: Doc, e: MouseEvent) => { - TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.node.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)), + TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.animatedDoc.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)), TimelineMenu.Instance.addItem('input', `fadeIn: ${this.regiondata.fadeIn}ms`, val => { runInAction(() => { let cannotMove: boolean = false; @@ -461,7 +461,7 @@ export class Keyframe extends React.Component<IProps> { e.stopPropagation(); const div = ref.current!; div.style.opacity = '1'; - Doc.BrushDoc(this.props.node); + Doc.BrushDoc(this.props.animatedDoc); }; /** @@ -473,7 +473,7 @@ export class Keyframe extends React.Component<IProps> { e.stopPropagation(); const div = ref.current!; div.style.opacity = '0'; - Doc.UnBrushDoc(this.props.node); + Doc.UnBrushDoc(this.props.animatedDoc); }; ///////////////////////UI STUFF ///////////////////////// diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index adc97bbb4..7ca13756a 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -535,7 +535,7 @@ export class Timeline extends React.Component<FieldViewProps> { {this.children.map(doc => ( <Track ref={ref => this.mapOfTracks.push(ref)} - node={doc} + animatedDoc={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx index 2349ba786..1010332f5 100644 --- a/src/client/views/animationtimeline/Track.tsx +++ b/src/client/views/animationtimeline/Track.tsx @@ -12,7 +12,7 @@ import { Keyframe, KeyframeFunc, RegionData } from './Keyframe'; import './Track.scss'; interface IProps { - node: Doc; + animatedDoc: Doc; currentBarX: number; transform: Transform; collection: Doc; @@ -36,23 +36,23 @@ export class Track extends React.Component<IProps> { private objectWhitelist = ['data']; @computed private get regions() { - return DocListCast(this.props.node.regions); + return DocListCast(this.props.animatedDoc.regions); } @computed private get time() { return NumCast(KeyframeFunc.convertPixelTime(this.props.currentBarX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement)); } async componentDidMount() { - const regions = await DocListCastAsync(this.props.node.regions); - if (!regions) this.props.node.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff + const regions = await DocListCastAsync(this.props.animatedDoc.regions); + if (!regions) this.props.animatedDoc.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff //these two lines are exactly same from timeline.tsx const relativeHeight = window.innerHeight / 20; runInAction(() => (this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT)); //for responsiveness this._timelineVisibleReaction = this.timelineVisibleReaction(); this._currentBarXReaction = this.currentBarXReaction(); - if (DocListCast(this.props.node.regions).length === 0) this.createRegion(this.time); - this.props.node.hidden = false; - this.props.node.opacity = 1; + if (DocListCast(this.props.animatedDoc.regions).length === 0) this.createRegion(this.time); + this.props.animatedDoc.hidden = false; + this.props.animatedDoc.opacity = 1; // this.autoCreateKeyframe(); } @@ -127,13 +127,13 @@ export class Track extends React.Component<IProps> { */ @action autoCreateKeyframe = () => { - const objects = this.objectWhitelist.map(key => this.props.node[key]); - intercept(this.props.node, change => { + const objects = this.objectWhitelist.map(key => this.props.animatedDoc[key]); + intercept(this.props.animatedDoc, change => { return change; }); return reaction( () => { - return [...this.primitiveWhitelist.map(key => this.props.node[key]), ...objects]; + return [...this.primitiveWhitelist.map(key => this.props.animatedDoc[key]), ...objects]; }, (changed, reaction) => { //check for region @@ -171,14 +171,14 @@ export class Track extends React.Component<IProps> { () => { const regiondata = this.findRegion(this.time); if (regiondata) { - this.props.node.hidden = false; + this.props.animatedDoc.hidden = false; // if (!this._autoKfReaction) { // // this._autoKfReaction = this.autoCreateKeyframe(); // } this.timeChange(); } else { - this.props.node.hidden = true; - this.props.node.opacity = 0; + this.props.animatedDoc.hidden = true; + this.props.animatedDoc.opacity = 0; //if (this._autoKfReaction) this._autoKfReaction(); } } @@ -250,10 +250,10 @@ export class Track extends React.Component<IProps> { private applyKeys = async (kf: Doc) => { this.primitiveWhitelist.forEach(key => { if (!kf[key]) { - this.props.node[key] = undefined; + this.props.animatedDoc[key] = undefined; } else { const stored = kf[key]; - this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored; + this.props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored; } }); }; @@ -282,11 +282,11 @@ export class Track extends React.Component<IProps> { const dif = NumCast(right[key]) - NumCast(left[key]); const deltaLeft = this.time - NumCast(left.time); const ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time)); - this.props.node[key] = NumCast(left[key]) + dif * ratio; + this.props.animatedDoc[key] = NumCast(left[key]) + dif * ratio; } else { // case data const stored = left[key]; - this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored; + this.props.animatedDoc[key] = stored instanceof ObjectField ? stored[Copy]() : stored; } }); }; @@ -326,7 +326,7 @@ export class Track extends React.Component<IProps> { regiondata.duration = rightRegion.position - regiondata.position; } if (this.regions.length === 0 || !rightRegion || (rightRegion && rightRegion.position - regiondata.position >= NumCast(regiondata.fadeIn) + NumCast(regiondata.fadeOut))) { - Cast(this.props.node.regions, listSpec(Doc))?.push(regiondata); + Cast(this.props.animatedDoc.regions, listSpec(Doc))?.push(regiondata); this._newKeyframe = true; this.saveStateRegion = regiondata; return regiondata; @@ -360,7 +360,7 @@ export class Track extends React.Component<IProps> { @action copyDocDataToKeyFrame = (doc: Doc) => { this.primitiveWhitelist.map(key => { - const originalVal = this.props.node[key]; + const originalVal = this.props.animatedDoc[key]; doc[key] = originalVal instanceof ObjectField ? originalVal[Copy]() : originalVal; }); }; @@ -377,8 +377,8 @@ export class Track extends React.Component<IProps> { ref={this._inner} style={{ height: `${this._trackHeight}px` }} onDoubleClick={this.onInnerDoubleClick} - onPointerOver={() => Doc.BrushDoc(this.props.node)} - onPointerOut={() => Doc.UnBrushDoc(this.props.node)}> + onPointerOver={() => Doc.BrushDoc(this.props.animatedDoc)} + onPointerOut={() => Doc.UnBrushDoc(this.props.animatedDoc)}> {this.regions?.map((region, i) => { return <Keyframe key={`${i}`} {...this.props} RegionData={region} makeKeyData={this.makeKeyData} />; })} diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index a4c5229aa..d93015506 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -176,6 +176,7 @@ display: flex; align-content: center; justify-content: center; + background: $dark-gray; } .lm_controls > li { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 16982595d..0052c4196 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -164,7 +164,7 @@ export class CollectionDockingView extends CollectionSubView() { public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; - const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !keyValue); + const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !tab.contentItem.config.props.keyValue && !keyValue); if (tab) { tab.header.parent.setActiveContentItem(tab.contentItem); return true; @@ -466,7 +466,10 @@ export class CollectionDockingView extends CollectionSubView() { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); - Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); + // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed + if (tab.DashDoc.embedContainer === this.rootDoc) tab.DashDoc.embedContainer = undefined; + if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); + Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc); } if (CollectionDockingView.Instance) { const dview = CollectionDockingView.Instance.props.Document; diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 6f88f6727..06522b85e 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -7,8 +7,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; -import { NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, numberRange, returnEmptyString, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { CompileScript } from '../../util/Scripting'; @@ -252,10 +251,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr return ( <div className="collectionStackingView-optionPicker"> <div className="optionOptions"> - <div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.toggleEmbedding}> - Create Embedding - </div> - <div className={'optionPicker' + (selected === true ? ' active' : '')} onClick={this.deleteRow}> + <div className={'optionPicker' + (selected === true ? ' active' : '')} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}> Delete </div> </div> @@ -273,13 +269,16 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `; return this.collapsed ? null : ( <div style={{ position: 'relative' }}> + {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null} {showChrome ? ( <div className="collectionStackingView-addDocumentButton" - style={{ - //width: style.columnWidth / style.numGroupColumns, - padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`, - }}> + style={ + { + //width: style.columnWidth / style.numGroupColumns, + //padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`, + } + }> <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} /> </div> ) : null} @@ -288,12 +287,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr ref={this._contRef} style={{ padding: stackPad, + minHeight: this.props.showHandle && this.props.parent.props.isContentActive() ? '10px' : undefined, width: this.props.parent.NodeWidth, gridGap: this.props.parent.gridGap, gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ''), }}> {this.props.parent.children(this.props.docList)} - {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null} </div> </div> ); @@ -313,23 +312,33 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr onPointerDown={this.headerDown} title={evContents === `NO ${key.toUpperCase()} VALUE` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''} style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this.color : 'lightgrey' }}> - {noChrome ? evContents : editableHeaderView} + {noChrome ? evContents : <div>{editableHeaderView}</div>} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : ( <div className="collectionStackingView-sectionColor"> - <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}> + <button + className="collectionStackingView-sectionColorButton" + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + action(e => (this._paletteOn = !this._paletteOn)) + ) + }> <FontAwesomeIcon icon="palette" size="lg" /> </button> {this._paletteOn ? this.renderColorPicker() : null} </div> )} {noChrome ? null : ( - <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}> + <button className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}> <FontAwesomeIcon icon={this.collapsed ? 'chevron-down' : 'chevron-up'} size="lg" /> </button> )} {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : ( - <div className="collectionStackingView-sectionOptions"> - <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderMenu()}> + <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}> + <Flyout anchorPoint={anchorPoints.RIGHT_TOP} content={this.renderMenu()}> <button className="collectionStackingView-sectionOptionButton"> <FontAwesomeIcon icon="ellipsis-v" size="lg" /> </button> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 9eb716763..f65e8698f 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -33,7 +33,7 @@ import { LightboxView } from '../LightboxView'; import { MainView } from '../MainView'; import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocumentView, OpenWhereMod } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhereMod } from '../nodes/DocumentView'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { CollectionDockingView } from './CollectionDockingView'; import './CollectionMenu.scss'; @@ -129,7 +129,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { docViewPath={returnEmptyDoclist} moveDocument={returnFalse} addDocument={returnFalse} - addDocTab={returnFalse} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} removeDocument={returnFalse} ScreenToLocalTransform={this.buttonBarXf} @@ -154,21 +154,21 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { const hardCodedButtons = ( <div className={`hardCodedButtons`}> - <Toggle - toggleType={ToggleType.BUTTON} - type={Type.PRIM} - color={StrCast(Doc.UserDoc().userColor)} - onClick={this.toggleTopBar} - toggleStatus={SettingsManager.headerBarHeight > 0} + <Toggle + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + color={StrCast(Doc.UserDoc().userColor)} + onClick={this.toggleTopBar} + toggleStatus={SettingsManager.headerBarHeight > 0} icon={<FontAwesomeIcon icon={headerIcon} size="lg" />} tooltip={headerTitle} /> - <Toggle - toggleType={ToggleType.BUTTON} + <Toggle + toggleType={ToggleType.BUTTON} type={Type.PRIM} - color={StrCast(Doc.UserDoc().userColor)} - onClick={this.toggleProperties} - toggleStatus={SettingsManager.propertiesWidth > 0} + color={StrCast(Doc.UserDoc().userColor)} + onClick={this.toggleProperties} + toggleStatus={SettingsManager.propertiesWidth > 0} icon={<FontAwesomeIcon icon={propIcon} size="lg" />} tooltip={propTitle} /> @@ -178,11 +178,12 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { // NEW BUTTONS //dash col linear view buttons const contMenuButtons = ( - <div className="collectionMenu-container" - style={{ - background: StrCast(Doc.UserDoc().userBackgroundColor), - // borderColor: StrCast(Doc.UserDoc().userColor) - }} > + <div + className="collectionMenu-container" + style={{ + background: SettingsManager.Instance.userBackgroundColor, + // borderColor: StrCast(Doc.UserDoc().userColor) + }}> {this.contMenuButtons} {hardCodedButtons} </div> diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 91be31289..bbd528e13 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -93,13 +93,14 @@ export class CollectionPileView extends CollectionSubView() { this.layoutDoc._freeform_panY = -10; this.props.Document._freeform_pileEngine = computePassLayout.name; } else { - const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500); + const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400); this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2; this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width](); this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height](); this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0; this.layoutDoc._width = this.layoutDoc._height = defaultSize; + this.layoutDoc.background; this.props.Document._freeform_pileEngine = computeStarburstLayout.name; } }); diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 99a68e94b..255bc3889 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -128,6 +128,7 @@ height: 15; position: absolute; margin-left: -5; + z-index: 10; } // Documents in stacking view diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 805002452..e4a0d6dad 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,7 +1,7 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CursorProperty } from 'csstype'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData, Height, Width } from '../../../fields/DocSymbols'; @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -31,7 +31,6 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; import { CollectionSubView } from './CollectionSubView'; -import { Colors } from '../global/globalEnums'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { @@ -59,7 +58,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map<string, number>(); // Assuming that this is the current css cursor style - @observable _cursor: CursorProperty = 'grab'; + @observable _cursor: CursorProperty = 'ew-resize'; // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling // does this mean whether the browser is hidden? Or is chrome something else entirely? @@ -308,19 +307,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ? false : undefined; isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); + @observable docRefs = new ObservableMap<Doc, DocumentView>(); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, width: () => number, count: number) { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); - let dref: Opt<DocumentView>; - const stackedDocTransform = () => this.getDocTransform(doc, dref); + const stackedDocTransform = () => this.getDocTransform(doc); this._docXfs.push({ stackedDocTransform, width, height }); - //DocumentView is how the node will be rendered return count > this._renderCount ? null : ( <DocumentView - ref={r => (dref = r || undefined)} + ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))} Document={doc} DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} renderDepth={this.props.renderDepth + 1} @@ -368,9 +366,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ); } - getDocTransform(doc: Doc, dref?: DocumentView) { - const y = this._scroll; // required for document decorations to update when the text box container is scrolled - const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + getDocTransform(doc: Doc) { + const dref = this.docRefs.get(doc); + this._scroll; // must be referenced for document decorations to update when the text box container is scrolled + const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv); // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); } @@ -409,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection e, this.onDividerMove, action(() => { - this._cursor = 'grab'; + this._cursor = 'ew-resize'; batch.end(); }), emptyFunction @@ -423,7 +422,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @computed get columnDragger() { return ( - <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef} style={{ cursor: this._cursor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}> + <div + className="collectionStackingView-columnDragger" + onPointerDown={this.columnDividerDown} + ref={this._draggerRef} + style={{ cursor: this._cursor, color: StrCast(Doc.UserDoc().userColor), left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}> <FontAwesomeIcon icon={'arrows-alt-h'} /> </div> ); @@ -595,7 +598,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection action((entries: any) => { if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0); - this.props.setHeight?.(this.headerMargin + height); + this.props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel } }) ); @@ -666,7 +669,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection if (menuDoc) { const width: number = NumCast(menuDoc._width, 30); const height: number = NumCast(menuDoc._height, 30); - console.log(menuDoc.title, width, height); return ( <div className="buttonMenu-docBtn" style={{ width: width, height: height }}> <DocumentView diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 00137736d..ea3b5065f 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -150,6 +150,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree console.log('WHAAAT'); } dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree ? 'same' : dragData.dropAction; + e.stopPropagation(); } }; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 1ff5688f9..71032ff68 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -400,7 +400,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { return NumCast(Cast(PresBox.Instance.activeItem.presentationTargetDoc, Doc, null)._currentFrame); }; static Activate = (tabDoc: Doc) => { - const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc); + const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue); tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) return tab !== undefined; }; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index d904749b1..25a547066 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -127,7 +127,7 @@ export class TreeView extends React.Component<TreeViewProps> { : this.props.treeView.fileSysMode ? this.doc.isFolder ? this.fieldKey - : 'embeddings' // for displaying + : 'data' // file system folders display their contents (data). used to be they displayed their embeddings but now its a tree structure and not a flat list : this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode @@ -222,10 +222,8 @@ export class TreeView extends React.Component<TreeViewProps> { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding - const bestEmbedding = docView.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) - ? docView.rootDoc - : Doc.BestEmbedding(docView.rootDoc); - this.props.addDocTab(bestEmbedding, OpenWhere.lightbox); + const bestEmbedding = docView.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.rootDoc : Doc.BestEmbedding(docView.rootDoc); + this.props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; @@ -344,7 +342,6 @@ export class TreeView extends React.Component<TreeViewProps> { TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView }; return this.props.addDocument(folder); }; - deleteItem = () => this.props.removeDoc?.(this.doc); preTreeDrop = (e: Event, de: DragManager.DropEvent) => { const dragData = de.complete.docDragData; @@ -540,8 +537,6 @@ export class TreeView extends React.Component<TreeViewProps> { TraceMobx(); const expandKey = this.treeViewExpandedView; const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {}; - const color = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Color) - console.log("tree view", color, this.doc.title, Doc.IsSystem(this.doc)) if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortKeys = Object.keys(sortings); @@ -581,9 +576,7 @@ export class TreeView extends React.Component<TreeViewProps> { ); } return ( - <div style={{ - color: color - }}> + <div> {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( <div className={'treeView-sorting'}> <IconButton @@ -662,14 +655,14 @@ export class TreeView extends React.Component<TreeViewProps> { ); } else if (this.treeViewExpandedView === 'fields') { return ( - <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit', color: color }}> + <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}> <div>{this.expandedField}</div> </ul> ); } return ( <ul - style={{ color: color }} + style={{}} onPointerDown={e => { e.preventDefault(); e.stopPropagation(); @@ -725,26 +718,19 @@ export class TreeView extends React.Component<TreeViewProps> { }> {this.props.treeView.outlineMode ? ( !(this.doc.text as RichTextField)?.Text ? null : ( - <IconButton - color={color} - icon={<FontAwesomeIcon - icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} - size={Size.XSMALL} - /> + <IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} /> ) ) : ( <div className="treeView-bulletIcons" style={{ color: Doc.IsSystem(DocCast(this.doc.proto)) ? 'red' : undefined }}> - {this.onCheckedClick ? <IconButton - color={color} - icon={<FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} />} - size={Size.XSMALL} - /> : + {this.onCheckedClick ? ( <IconButton - color={color} - icon={<FontAwesomeIcon icon={iconType as IconProp} />} + color={color} + icon={<FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} />} size={Size.XSMALL} /> - } + ) : ( + <IconButton color={color} icon={<FontAwesomeIcon icon={iconType as IconProp} />} size={Size.XSMALL} /> + )} </div> )} </div> @@ -776,17 +762,15 @@ export class TreeView extends React.Component<TreeViewProps> { return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( <> {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */} - {this.doc._layout_hideContextMenu ? null : ( - <IconButton - color={color} - icon={<FontAwesomeIcon icon="bars" />} - size={Size.XSMALL} - onClick={e => { - this.showContextMenu(e); - e.stopPropagation(); - }} - /> - )} + <IconButton + color={color} + icon={<FontAwesomeIcon icon="bars" />} + size={Size.XSMALL} + onClick={e => { + this.showContextMenu(e); + e.stopPropagation(); + }} + /> {Doc.noviceMode ? null : this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : ( <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> {this.treeViewExpandedView} @@ -803,10 +787,10 @@ export class TreeView extends React.Component<TreeViewProps> { }; contextMenuItems = () => { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; - const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' }; const folderOp = this.childDocs?.length ? [makeFolder] : []; const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(self), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; + const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Reopen' }; return [ ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), ...(this.doc.isFolder @@ -817,7 +801,9 @@ export class TreeView extends React.Component<TreeViewProps> { ? [openEmbedding, makeFolder] : this.doc._type_collection === CollectionViewType.Docking ? [] - : [deleteItem, openEmbedding, focusDoc]), + : this.props.treeView.rootDoc === Doc.MyRecentlyClosed + ? [reopenDoc] + : [openEmbedding, focusDoc]), ]; }; childContextMenuItems = () => { @@ -917,6 +903,7 @@ export class TreeView extends React.Component<TreeViewProps> { height={12} sizeToContent={true} fontSize={12} + isEditingCallback={action(e => (this._editTitle = e))} GetValue={() => StrCast(this.doc.title)} OnTab={undoBatch((shift?: boolean) => { if (!shift) this.props.indentDocument?.(true); @@ -989,6 +976,8 @@ export class TreeView extends React.Component<TreeViewProps> { ref={this._tref} title="click to edit title. Double Click or Drag to Open" style={{ + backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? StrCast(Doc.UserDoc().userVariantColor) : undefined, + color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(StrCast(Doc.UserDoc().userVariantColor)) : undefined, fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined, textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined, outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined, @@ -1014,12 +1003,13 @@ export class TreeView extends React.Component<TreeViewProps> { onPointerDown={this.ignoreEvent} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - {contents} - <div className={`treeView-background`} + <div + className="treeView-background" style={{ - background: StrCast(Doc.UserDoc().userColor) + background: StrCast(Doc.UserDoc().userColor), }} /> + {contents} </div> {this.renderBorder} </> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 34f707b30..29122cb91 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1264,13 +1264,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.addDocument?.(newDoc); } }; - pointerEvents = () => { + @computed get _pointerEvents() { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); const pointerEvents = DocumentDecorations.Instance.Interacting ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.()); return pointerEvents; - }; + } + pointerEvents = () => this._pointerEvents; + childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; @@ -1299,7 +1301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive} - isContentActive={this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction} + isContentActive={this.childContentsActive} focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this.props.addDocument} @@ -1316,7 +1318,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection layout_showTitle={this.props.childlayout_showTitle} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} pointerEvents={this.pointerEvents} - //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeViewFreezeChildDimensions)} // bcz: check this /> ); @@ -1356,6 +1357,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _lightboxDoc: Opt<Doc>; getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { + const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); const childDoc = params.pair.layout; const childDocLayout = Doc.Layout(childDoc); const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values @@ -1366,11 +1368,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection layoutFrameNumber === undefined ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); + // prettier-ignore + const rotation = Cast(_rotation,'number', + !this.layoutDoc._rotation_jitter ? null + : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) ); return { x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), - rotation: Cast(_rotation, 'number'), + rotation: rotation, color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color), backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor), opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity), @@ -1435,21 +1441,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }.bind(this) ); - childPositionProviderUnmemoized = (doc: Doc, replica: string) => { - return this._layoutPoolData.get(doc[Id] + (replica || '')); - }; + childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || '')); childDataProvider = computedFn( function childDataProvider(this: any, doc: Doc, replica: string) { - return this._layoutPoolData.get(doc[Id] + (replica || '')); + return this.childPositionProviderUnmemoized(doc, replica); }.bind(this) ); - childSizeProviderUnmemoized = (doc: Doc, replica: string) => { - return this._layoutSizeData.get(doc[Id] + (replica || '')); - }; + childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || '')); childSizeProvider = computedFn( function childSizeProvider(this: any, doc: Doc, replica: string) { - return this._layoutSizeData.get(doc[Id] + (replica || '')); + return this.childSizeProviderUnmemoized(doc, replica); }.bind(this) ); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 090cf356c..7c53bfdbe 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -377,7 +377,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque : ((doc: Doc) => { Doc.GetProto(doc).data = new List<Doc>(selected); Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform'; - !this.props.isAnnotationOverlay && Doc.AddFileOrphan(Doc.GetProto(doc)); doc._freeform_panX = doc._freeform_panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 56b8366d0..2254b2e5f 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -204,7 +204,7 @@ export class CollectionLinearView extends CollectionSubView() { const menuOpener = ( <Toggle - text={Cast(this.props.Document.icon, 'string', null)} + text={Cast(this.props.Document.icon, 'string', null)} icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />} type={Type.TERT} color={StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)} @@ -212,7 +212,7 @@ export class CollectionLinearView extends CollectionSubView() { toggleType={ToggleType.BUTTON} toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)} onClick={() => { - this.layoutDoc.linearView_IsOpen = !isExpanded; + this.layoutDoc.linearView_IsOpen = !isExpanded; }} tooltip={isExpanded ? 'Close' : 'Open'} fillWidth={true} @@ -223,25 +223,22 @@ export class CollectionLinearView extends CollectionSubView() { return ( <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}> <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}> - {this.props.Document.linearView_Dropdown ? - <div>Hello World!</div> - : + { <> - {!this.props.Document.linearView_Expandable ? null : menuOpener} - {!this.layoutDoc.linearView_IsOpen ? null : ( - <div - className="collectionLinearView-content" - style={{ - height: this.dimension(), - flexDirection: flexDir as any, - gap: flexGap, - }}> - {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} - </div> - )} + {menuOpener} + {!this.layoutDoc.linearView_IsOpen ? null : ( + <div + className="collectionLinearView-content" + style={{ + height: this.dimension(), + flexDirection: flexDir as any, + gap: flexGap, + }}> + {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} + </div> + )} </> } - </div> </div> ); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index ee5bf82ed..babe5c810 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -810,7 +810,6 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get sortedDocs() { - trace(); const field = StrCast(this.layoutDoc.sortField); const desc = BoolCast(this.layoutDoc.sortDesc); const docs = !field diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 2ea8a7846..256377758 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -10,7 +10,7 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; -import { UndoManager } from '../../util/UndoManager'; +import { undoable, UndoManager } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { InkTranscription } from '../InkTranscription'; import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from '../InkingStroke'; @@ -33,7 +33,6 @@ ScriptingGlobals.add(function setView(view: string) { // toggle: Set overlay status of selected document ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { const selectedViews = SelectionManager.Views(); - console.log(color, checkResult); if (Doc.ActiveTool !== InkTool.None) { if (checkResult) { return ActiveFillColor(); @@ -54,7 +53,6 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (contentFrameNumber !== undefined) { CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); } else { - console.log('setting color to: ', color); dv.rootDoc['_' + fieldKey] = color; } }); @@ -79,7 +77,6 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole // toggle: Set overlay status of selected document ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { - console.log(checkResult); const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; if (checkResult) { if (NumCast(selected?.Document.z) >= 1) return true; @@ -121,7 +118,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli ]); if (checkResult) { - console.log(attr, map.get(attr)?.checkResult(selected)); return map.get(attr)?.checkResult(selected); } const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; @@ -195,14 +191,12 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: const map = new Map(attrs.concat(alignments).concat(listings)); if (checkResult) { - console.log(charStyle, checkResult, map.get(charStyle)?.checkResult()); return map.get(charStyle)?.checkResult(); } - map.get(charStyle)?.toggle(); + undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)(); }); export function checkInksToGroup() { - // console.log("getting here to inks group"); if (Doc.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx index 0ede75407..ff17e5c12 100644 --- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx +++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx @@ -1,6 +1,6 @@ import './ButtonMenu.scss'; import * as React from 'react'; -import { IButtonMenu } from "./utils"; +import { IButtonMenu } from './utils'; import { NewLightboxView } from '../NewLightboxView'; import { SelectionManager } from '../../../util/SelectionManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; @@ -11,43 +11,40 @@ import { MainView } from '../../MainView'; import { action } from 'mobx'; export const ButtonMenu = (props: IButtonMenu) => { - - return <div className={`newLightboxButtonMenu-container`}> - <div - className="newLightboxView-navBtn" - title="toggle fit width" - onClick={e => { - e.stopPropagation(); - NewLightboxView.NewLightboxDoc!._fitWidth = !NewLightboxView.NewLightboxDoc!._fitWidth; - }}> - </div> - <div - className="newLightboxView-tabBtn" - title="open in tab" - onClick={e => { - e.stopPropagation(); - CollectionDockingView.AddSplit(NewLightboxView.NewLightboxDoc || NewLightboxView.NewLightboxDoc!, OpenWhereMod.none); - SelectionManager.DeselectAll(); - NewLightboxView.SetNewLightboxDoc(undefined); - }}> - </div> - <div - className="newLightboxView-penBtn" - title="toggle pen annotation" - style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }} - onClick={e => { - e.stopPropagation(); - Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; - }}> - </div> - <div - className="newLightboxView-exploreBtn" - title="toggle explore mode to navigate among documents only" - style={{ background: MainView.Instance._exploreMode ? 'white' : undefined }} - onClick={action(e => { - e.stopPropagation(); - MainView.Instance._exploreMode = !MainView.Instance._exploreMode; - })}> - </div> - </div> -}
\ No newline at end of file + return ( + <div className={`newLightboxButtonMenu-container`}> + <div + className="newLightboxView-navBtn" + title="toggle fit width" + onClick={e => { + e.stopPropagation(); + NewLightboxView.LightboxDoc!._fitWidth = !NewLightboxView.LightboxDoc!._fitWidth; + }}></div> + <div + className="newLightboxView-tabBtn" + title="open in tab" + onClick={e => { + e.stopPropagation(); + CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none); + SelectionManager.DeselectAll(); + NewLightboxView.SetNewLightboxDoc(undefined); + }}></div> + <div + className="newLightboxView-penBtn" + title="toggle pen annotation" + style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }} + onClick={e => { + e.stopPropagation(); + Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; + }}></div> + <div + className="newLightboxView-exploreBtn" + title="toggle explore mode to navigate among documents only" + style={{ background: MainView.Instance._exploreMode ? 'white' : undefined }} + onClick={action(e => { + e.stopPropagation(); + MainView.Instance._exploreMode = !MainView.Instance._exploreMode; + })}></div> + </div> + ); +}; diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.tsx b/src/client/views/newlightbox/ExploreView/ExploreView.tsx index 855bfd9e2..a1d6375c4 100644 --- a/src/client/views/newlightbox/ExploreView/ExploreView.tsx +++ b/src/client/views/newlightbox/ExploreView/ExploreView.tsx @@ -1,30 +1,32 @@ import './ExploreView.scss'; -import { IBounds, IExploreView, emptyBounds } from "./utils"; -import { IRecommendation } from "../components"; +import { IBounds, IExploreView, emptyBounds } from './utils'; +import { IRecommendation } from '../components'; import * as React from 'react'; import { NewLightboxView } from '../NewLightboxView'; import { StrCast } from '../../../../fields/Types'; - - export const ExploreView = (props: IExploreView) => { - const { recs, bounds=emptyBounds } = props - - return <div className={`exploreView-container`}> - {recs && recs.map((rec) => { - console.log(rec.embedding, bounds) - const x_bound: number = Math.max(Math.abs(bounds.max_x), Math.abs(bounds.min_x)) - const y_bound: number = Math.max(Math.abs(bounds.max_y), Math.abs(bounds.min_y)) - console.log(x_bound, y_bound) - if (rec.embedding) { - const x = (rec.embedding.x / x_bound) * 50; - const y = (rec.embedding.y / y_bound) * 50; - console.log(x, y) - return <div className={`exploreView-doc`} onClick={() => {}} style={{top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)`}}> - {rec.title} - </div> - } else return (null) - })} - <div className={`exploreView-doc`} style={{top: `calc(50% + ${0}%)`, left: `calc(50% + ${0}%)`, background: '#073763', color: 'white'}}>{StrCast(NewLightboxView.NewLightboxDoc?.title)}</div> - </div> -}
\ No newline at end of file + const { recs, bounds = emptyBounds } = props; + + return ( + <div className={`exploreView-container`}> + {recs && + recs.map(rec => { + const x_bound: number = Math.max(Math.abs(bounds.max_x), Math.abs(bounds.min_x)); + const y_bound: number = Math.max(Math.abs(bounds.max_y), Math.abs(bounds.min_y)); + if (rec.embedding) { + const x = (rec.embedding.x / x_bound) * 50; + const y = (rec.embedding.y / y_bound) * 50; + return ( + <div className={`exploreView-doc`} onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}> + {rec.title} + </div> + ); + } else return null; + })} + <div className={`exploreView-doc`} style={{ top: `calc(50% + ${0}%)`, left: `calc(50% + ${0}%)`, background: '#073763', color: 'white' }}> + {StrCast(NewLightboxView.LightboxDoc?.title)} + </div> + </div> + ); +}; diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx index c0d357ad5..2c2f04b9f 100644 --- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx +++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IRecommendation } from "./utils"; +import { IRecommendation } from './utils'; import './Recommendation.scss'; import { getType } from '../../utils'; import { FaEyeSlash } from 'react-icons/fa'; @@ -9,82 +9,94 @@ import { Doc } from '../../../../../fields/Doc'; import { Docs } from '../../../../documents/Documents'; export const Recommendation = (props: IRecommendation) => { - const {title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId} = props - - return <div className={`recommendation-container ${loading && 'loading'} ${previewUrl && 'previewUrl'}`} onClick={() => { - let doc: Doc | null = null; - if (source == "Dash" && docId) { - const docView = DocumentManager.Instance.getDocumentViewById(docId) - if (docView) { - doc = docView.rootDoc; - } - } else if (data) { - console.log(data, type) - switch(type) { - case "YouTube": - console.log('create ', type, 'document') - doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript: transcript }) - break; - case "Video": - console.log('create ', type, 'document') - doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript: transcript }) - break; - case "Webpage": - console.log('create ', type, 'document') - doc = Docs.Create.WebDocument(data, { title: title, text: text }) - break; - case "HTML": - console.log('create ', type, 'document') - doc = Docs.Create.WebDocument(data, { title: title, text: text }) - break; - case "Text": - console.log('create ', type, 'document') - doc = Docs.Create.TextDocument(data, { title: title, text: text }) - break; - case "PDF": - console.log('create ', type, 'document') - doc = Docs.Create.PdfDocument(data, { title: title, text: text }) - break; - } - } - if (doc !== null) NewLightboxView.SetNewLightboxDoc(doc) - }}> - {loading ? - <div className={`image-container`}> - </div> - : - previewUrl ? <div className={`image-container`}> - {<img className={`image`} src={previewUrl}></img>} - </div> - : null - } - <div className={`title`}>{title}</div> - <div className={`info`}> - {!loading && <div className={`type-container`}> - <div className={`lb-label`}>Type</div><div className={`lb-type`}>{getType(type!)}</div> - </div>} - {!loading && <div className={`distance-container`}> - <div className={`lb-label`}>Distance</div><div className={`lb-distance`}>{distance}</div> - </div>} - </div> - <div className={`source`}> - {!loading && <div className={`source-container`}> - <div className={`lb-label`}>Source</div><div className={`lb-source`}>{source}</div> - </div>} - </div> - <div className={`explainer`}> - {!loading && - <div> - You are seeing this recommendation because this document also explores - <div className={`concepts-container`}> - {related_concepts?.map((val) => { - return <div className={'concept'}>{val}</div> - })} - </div> - </div>} - </div> - <div className={`hide-rec`}> - {!loading && <><div>Hide Recommendation</div><div style={{fontSize: 15, paddingRight: 5}}><FaEyeSlash/></div></>} + const { title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId } = props; + + return ( + <div + className={`recommendation-container ${loading && 'loading'} ${previewUrl && 'previewUrl'}`} + onClick={() => { + let doc: Doc | null = null; + if (source == 'Dash' && docId) { + const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement(); + if (docView) { + doc = docView.rootDoc; + } + } else if (data) { + switch (type) { + case 'YouTube': + console.log('create ', type, 'document'); + doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript: transcript }); + break; + case 'Video': + console.log('create ', type, 'document'); + doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript: transcript }); + break; + case 'Webpage': + console.log('create ', type, 'document'); + doc = Docs.Create.WebDocument(data, { title: title, text: text }); + break; + case 'HTML': + console.log('create ', type, 'document'); + doc = Docs.Create.WebDocument(data, { title: title, text: text }); + break; + case 'Text': + console.log('create ', type, 'document'); + doc = Docs.Create.TextDocument(data, { title: title, text: text }); + break; + case 'PDF': + console.log('create ', type, 'document'); + doc = Docs.Create.PdfDocument(data, { title: title, text: text }); + break; + } + } + if (doc !== null) NewLightboxView.SetNewLightboxDoc(doc); + }}> + {loading ? <div className={`image-container`}></div> : previewUrl ? <div className={`image-container`}>{<img className={`image`} src={previewUrl}></img>}</div> : null} + <div className={`title`}>{title}</div> + <div className={`info`}> + {!loading && ( + <div className={`type-container`}> + <div className={`lb-label`}>Type</div> + <div className={`lb-type`}>{getType(type!)}</div> + </div> + )} + {!loading && ( + <div className={`distance-container`}> + <div className={`lb-label`}>Distance</div> + <div className={`lb-distance`}>{distance}</div> + </div> + )} + </div> + <div className={`source`}> + {!loading && ( + <div className={`source-container`}> + <div className={`lb-label`}>Source</div> + <div className={`lb-source`}>{source}</div> + </div> + )} + </div> + <div className={`explainer`}> + {!loading && ( + <div> + You are seeing this recommendation because this document also explores + <div className={`concepts-container`}> + {related_concepts?.map(val => { + return <div className={'concept'}>{val}</div>; + })} + </div> + </div> + )} + </div> + <div className={`hide-rec`}> + {!loading && ( + <> + <div>Hide Recommendation</div> + <div style={{ fontSize: 15, paddingRight: 5 }}> + <FaEyeSlash /> + </div> + </> + )} + </div> </div> - </div> -}
\ No newline at end of file + ); +}; diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index e1ff6f8eb..10bfb0c64 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -34,7 +34,6 @@ export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never }; export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => { - console.log('x axis creator being called'); g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15)); }; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 7e8eef0a5..2d8663c9c 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -18,7 +18,6 @@ import { SearchBox } from '../search/SearchBox'; import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; -import { FontIconBox } from './FontIconBox/FontIconBox'; import { ColorBox } from './ColorBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; @@ -26,9 +25,11 @@ import { DocumentViewProps } from './DocumentView'; import './DocumentView.scss'; import { EquationBox } from './EquationBox'; import { FieldView, FieldViewProps } from './FieldView'; +import { FontIconBox } from './FontIconBox/FontIconBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { FunctionPlotBox } from './FunctionPlotBox'; import { ImageBox } from './ImageBox'; +import { ImportElementBox } from './importBox/ImportElementBox'; import { KeyValueBox } from './KeyValueBox'; import { LabelBox } from './LabelBox'; import { LinkAnchorBox } from './LinkAnchorBox'; @@ -269,6 +270,7 @@ export class DocumentContentsView extends React.Component< LoadingBox, PhysicsSimulationBox, SchemaRowBox, + ImportElementBox, }} bindings={bindings} jsx={layoutFrame} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 66352678c..bb9f45bdd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -47,11 +47,11 @@ import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; import { FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); +import { SettingsManager } from '../../util/SettingsManager'; const { Howl } = require('howler'); interface Window { @@ -314,7 +314,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; } - get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined { + @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ':selected' : '')); } @computed get finalLayoutKey() { @@ -332,8 +332,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return ( DocumentView.LongPress || onScriptDisable === 'always' || - (onScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) || - this._componentView?.isAnyChildContentActive?.() + (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.())) ); } @computed get onClickHandler() { @@ -800,7 +799,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } const constantItems: ContextMenuProps[] = []; if (!Doc.IsSystem(this.rootDoc)) { - constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); + constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); (this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) @@ -863,34 +862,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); }; - rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; + @computed get _rootSelected() { + return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false; + } + rootSelected = (outsideReaction?: boolean) => this._rootSelected; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler); setHeight = (height: number) => (this.layoutDoc._height = height); setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); - isContentActive = (outsideReaction?: boolean): boolean | undefined => { + @computed get _isContentActive() { // true - if the document has been activated directly or indirectly (by having its children selected) // false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking) - return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none' || (this.rootDoc.pointerEvents === 'none' && !StrCast(this.props.LayoutTemplateString).includes(KeyValueBox.name)) + return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none' ? false - : Doc.ActiveTool !== InkTool.None || - SnappingManager.GetIsDragging() || - this.rootSelected() || - this.rootDoc.forceActive || - this.props.isSelected(outsideReaction) || - this._componentView?.isAnyChildContentActive?.() || - this.props.isContentActive() + : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive() ? true : undefined; - }; + } + isContentActive = (): boolean | undefined => this._isContentActive; @observable _retryThumb = 1; - thumbShown = () => { - const childHighlighted = () => - Array.from(Doc.highlightedDocs.keys()) - .concat(Array.from(Doc.brushManager.BrushedDoc.keys())) - .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc)); + @computed get _thumbShown() { + const childHighlighted = () => false; + // Array.from(Doc.highlightedDocs.keys()) + // .concat(Array.from(Doc.brushManager.BrushedDoc.keys())) + // .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc)); const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc)); return !this.props.LayoutTemplateString && !this.isContentActive() && @@ -900,12 +897,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking) ? true : false; - }; + } + thumbShown = () => this._thumbShown; childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; - /// disable pointer events on content when there's an enabled onClick script (but not the browse script), or if contents are marked inactive - contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.()) || this.isContentActive() === false ? 'none' : this.pointerEvents); - + /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive + @computed get _contentPointerEvents() { + return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents; + } + contentPointerEvents = () => this._contentPointerEvents; @computed get contents() { TraceMobx(); const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString; @@ -1088,7 +1088,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps <div className="documentView-captionWrapper" style={{ - pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined, + pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined, minWidth: 50 * ffscale(), maxHeight: `max(100%, ${20 * ffscale()}px)`, }}> @@ -1110,7 +1110,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc; const background = StrCast( SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, - Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().headingColor) : 'rgba(0,0,0,0.4)' + Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().headingColor) : SettingsManager.Instance.userVariantColor ); const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); const titleView = !showTitle ? null : ( @@ -1223,16 +1223,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>; } } + @computed get highlighting() { + return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting); + } + @computed get borderPath() { + return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath); + } render() { TraceMobx(); - const highlighting = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting); - const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath); + const highlighting = this.highlighting; + const borderPath = this.borderPath; const boxShadow = this.props.treeViewDoc || !highlighting ? this.boxShadow : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed' ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}` - : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined); + : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined); const renderDoc = this.renderDoc({ borderRadius: this.borderRounding, outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px', @@ -1248,9 +1254,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)} - onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)} - onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)} + onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)} + onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)} style={{ borderRadius: this.borderRounding, pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss index 9714e1bd0..f5871db22 100644 --- a/src/client/views/nodes/EquationBox.scss +++ b/src/client/views/nodes/EquationBox.scss @@ -1,8 +1,9 @@ -@import "../global/globalCssVariables.scss"; +@import '../global/globalCssVariables.scss'; .equationBox-cont { - transform-origin: top left; + transform-origin: center; + background-color: #e7e7e7; > span { - width: 100%; + width: 100%; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 3ec53beac..91eac675f 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -1,6 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -21,6 +21,7 @@ import { RichTextMenu } from '../formattedText/RichTextMenu'; import './FontIconBox.scss'; import { SelectedDocView } from '../../selectedDoc'; import { Utils } from '../../../../Utils'; +import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa'; export enum ButtonType { TextButton = 'textBtn', @@ -86,10 +87,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() { } Icon = (color: string, iconFalse?: boolean) => { let icon; - if (iconFalse ) { + if (iconFalse) { icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any; - if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} /> - else return null + if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />; + else return null; } icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />; @@ -124,36 +125,38 @@ export class FontIconBox extends DocComponent<ButtonProps>() { */ @computed get numberDropdown() { let type: NumberDropdownType; - switch(this.type) { - case ButtonType.NumberDropdownButton: - type = 'dropdown' + switch (this.type) { + case ButtonType.NumberDropdownButton: + type = 'dropdown'; break; case ButtonType.NumberInlineButton: - type = 'input' + type = 'input'; break; case ButtonType.NumberSliderButton: default: - type = 'slider' + type = 'slider'; break; } - const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined }); + const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: value === undefined }); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); // Script for checking the outcome of the toggle const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3))); const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; - return <NumberDropdown - color={color} - numberDropdownType={type} - showPlusMinus={false} - tooltip={this.label} - type={Type.PRIM} - min={NumCast(this.rootDoc.numBtnMin, 0)} - max={NumCast(this.rootDoc.numBtnMax, 100)} - number={checkResult} - setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)} - fillWidth - /> + return ( + <NumberDropdown + color={color} + numberDropdownType={type} + showPlusMinus={false} + tooltip={this.label} + type={Type.PRIM} + min={NumCast(this.rootDoc.numBtnMin, 0)} + max={NumCast(this.rootDoc.numBtnMax, 100)} + number={checkResult} + setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)} + fillWidth + /> + ); } /** @@ -202,84 +205,64 @@ export class FontIconBox extends DocComponent<ButtonProps>() { let dropdown = true; let getStyle: (val: string) => any = () => {}; let icon: IconProp = 'caret-down'; - let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView') + let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView'); try { if (isViewDropdown) { const selectedDocs: Doc[] = SelectionManager.Docs(); const selected = SelectionManager.Docs().lastElement(); - console.log('selected') if (selected) { if (StrCast(selected.type) === DocumentType.COL) { text = StrCast(selected._type_collection); - console.log("collection selected", text) } else { - console.log("doc selected", selected.title); dropdown = false; if (selectedDocs.length > 1) { - text = selectedDocs.length + " selected" + text = selectedDocs.length + ' selected'; } else { text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType); icon = Doc.toIcon(selected); } - return <Popup - icon={<FontAwesomeIcon size={'1x'} icon={icon} />} - text={text} - type={Type.TERT} - color={color} - popup={<SelectedDocView selectedDocs={selectedDocs}/>} - fillWidth - /> + return <Popup icon={<FontAwesomeIcon size={'1x'} icon={icon} />} text={text} type={Type.TERT} color={color} popup={<SelectedDocView selectedDocs={selectedDocs} />} fillWidth />; } } else { dropdown = false; - return <Button - text={`None Selected`} - type={Type.TERT} - color={color} - fillWidth - inactive - /> + return <Button text={`None Selected`} type={Type.TERT} color={color} fillWidth inactive />; } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; - } else { + } else { text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - getStyle = (val: string) => { return { fontFamily: val } } + getStyle = (val: string) => { + return { fontFamily: val }; + }; } } catch (e) { console.log(e); } - console.log("current item: ", text); - // Get items to place into the list const list: IListItemProps[] = this.buttonList .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) - .map(value => ( - { - text: value.charAt(0).toUpperCase() + value.slice(1), - val: value, - style: getStyle(value), - onClick: undoable(() => script.script.run({ self: this.rootDoc, value }), value) - // shortcut: '#', - } - )); - + .map(value => ({ + text: value.charAt(0).toUpperCase() + value.slice(1), + val: value, + style: getStyle(value), + onClick: undoable(() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), value), + // shortcut: '#', + })); return ( - <Dropdown + <Dropdown selectedVal={text} - setSelectedVal={undoable((val) => script.script.run({ self: this.rootDoc, val }), `dropdown select ${this.label}`)} - color={color} - type={isViewDropdown ? Type.TERT : Type.PRIM} - dropdownType={DropdownType.SELECT} + setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)} + color={color} + type={isViewDropdown ? Type.TERT : Type.PRIM} + dropdownType={DropdownType.SELECT} items={list} tooltip={this.label} fillWidth /> - ) + ); } - @computed get colorScript() { return ScriptCast(this.rootDoc.script); } @@ -290,14 +273,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() { @computed get colorButton() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; + const curColor = this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; const tooltip: string = StrCast(this.rootDoc.toolTip); return ( - <ColorPicker - setSelectedColor={(value) => { + <ColorPicker + setSelectedColor={value => { const s = this.colorScript; - s && undoable(() => s.script.run({ self: this.rootDoc, value: value, _readOnly_: false }).result, `Set ${tooltip} to ${value}`)(); + s && undoable(() => s.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false }).result, `Set ${tooltip} to ${value}`)(); }} selectedColor={curColor} type={Type.PRIM} @@ -306,7 +289,35 @@ export class FontIconBox extends DocComponent<ButtonProps>() { tooltip={tooltip} label={this.label} /> - ) + ); + } + @computed get multiToggleButton() { + // Determine the type of toggle button + const tooltip: string = StrCast(this.rootDoc.toolTip); + + const script = ScriptCast(this.rootDoc.onClick); + const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + // Colors + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const items = DocListCast(this.rootDoc.data); + return ( + <MultiToggle + tooltip={`Toggle ${tooltip}`} + type={Type.PRIM} + color={color} + label={this.label} + items={DocListCast(this.rootDoc.data).map(item => ({ + icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />, + tooltip: StrCast(item.toolTip), + val: StrCast(item.toolType), + }))} + selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)} + setSelectedVal={(val: string | number) => { + const itemDoc = items.find(item => item.toolType === val); + itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false }); + }} + /> + ); } @computed get toggleButton() { @@ -315,25 +326,24 @@ export class FontIconBox extends DocComponent<ButtonProps>() { const tooltip: string = StrCast(this.rootDoc.toolTip); const script = ScriptCast(this.rootDoc.onClick); - const toggleStatus = script ? script.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; // Colors const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - console.log(tooltip, toggleStatus); return ( - <Toggle - tooltip={`Toggle ${tooltip}`} - toggleType={ToggleType.BUTTON} - type={Type.PRIM} - toggleStatus={toggleStatus} - text={buttonText} - color={color} - icon={this.Icon(color)!} + <Toggle + tooltip={`Toggle ${tooltip}`} + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + toggleStatus={toggleStatus} + text={buttonText} + color={color} + icon={this.Icon(color)!} label={this.label} - onPointerDown={() => script.script.run({ self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} + onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} /> - ) + ); } /** @@ -344,79 +354,50 @@ export class FontIconBox extends DocComponent<ButtonProps>() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const tooltip: string = StrCast(this.rootDoc.toolTip); - return ( - <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label}/> - ) + return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />; } @computed get editableText() { // Script for running the toggle const script = ScriptCast(this.rootDoc.script); // Function to run the script - const checkResult = script?.script.run({ value: '', _readOnly_: true }).result; + const checkResult = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result; + + const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: false }).result; + + return <EditableText editing={false} setEditing={(editing: boolean) => {}} />; - const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result; - - return <EditableText - editing={false} setEditing={(editing: boolean) => {}} - /> - return ( <div className="menuButton editableText"> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} /> <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}> - <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} /> + <EditableView GetValue={() => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} /> </div> </div> ); } render() { - // determine dash button metadata const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const tooltip: string = StrCast(this.rootDoc.toolTip); - const onClickScript = ScriptCast(this.rootDoc.onClick); - const script = ScriptCast(this.rootDoc.script); - // TODO:glr Add label of button type - let button: JSX.Element = this.defaultButton; + const tooltip = StrCast(this.rootDoc.toolTip); + const scriptFunc = () => ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false }); + const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label }; // prettier-ignore switch (this.type) { - case ButtonType.EditableText: - button = this.editableText; - break; - case ButtonType.DropdownList: - button = this.dropdownListButton; - break; - case ButtonType.ColorButton: - button = this.colorButton; - break; case ButtonType.NumberDropdownButton: case ButtonType.NumberInlineButton: - case ButtonType.NumberSliderButton: - button = this.numberDropdown; - break; - case ButtonType.DropdownButton: - button = this.dropdownButton; - break; - case ButtonType.ToggleButton: button = this.toggleButton; break; + case ButtonType.NumberSliderButton: return this.numberDropdown; + case ButtonType.EditableText: return this.editableText; + case ButtonType.DropdownList: return this.dropdownListButton; + case ButtonType.ColorButton: return this.colorButton; + case ButtonType.DropdownButton: return this.dropdownButton; + case ButtonType.MultiToggleButton: return this.multiToggleButton; + case ButtonType.ToggleButton: return this.toggleButton; case ButtonType.ClickButton: - case ButtonType.ToolButton: - button = ( - <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ _readOnly_: false })} color={color} icon={this.Icon(color)!} label={this.label}/> - ); - break; - case ButtonType.TextButton: - button = ( - <Button tooltip={tooltip} icon={this.Icon(color)!} text={StrCast(this.rootDoc.buttonText)} label={this.label}/> - ); - break; - case ButtonType.MenuButton: button = ( - <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/> - ); - break; + case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />; + case ButtonType.TextButton: return <Button {...btnProps} text={StrCast(this.rootDoc.buttonText)}/>; + case ButtonType.MenuButton: return <IconButton {...btnProps} color={color} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; } - - return button; + return this.defaultButton; } } diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 86191de63..d69009415 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -4,7 +4,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import wiki from 'wikijs'; import { Doc, DocCastAsync, Opt } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; +import { DirectLinks, Height, Width } from '../../../fields/DocSymbols'; import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; import { DocServer } from '../../DocServer'; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index de0b57fd7..4919ee94c 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -61,7 +61,6 @@ const script = document.createElement('script'); script.defer = true; script.async = true; script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`; -console.log(script.src); document.head.appendChild(script); /** diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss index 6fb5ea7b3..1e9b64a0b 100644 --- a/src/client/views/nodes/ScreenshotBox.scss +++ b/src/client/views/nodes/ScreenshotBox.scss @@ -1,6 +1,5 @@ .screenshotBox { transform-origin: top left; - background: white; color: black; // .screenshotBox-viewer { // opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger @@ -12,46 +11,39 @@ #CANCAN { canvas { - width:100% !important; + width: 100% !important; height: 100% !important; } } -.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen { +.screenshotBox-content, +.screenshotBox-content-interactive, +.screenshotBox-cont-fullScreen { width: 100%; z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt position: absolute; } -.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-content-fullScreen { - height: Auto; +.screenshotBox-content, +.screenshotBox-content-interactive, +.screenshotBox-content-fullScreen { + height: Auto; } .screenshotBox-uiButtons { - background:dimgray; - border: orange solid 1px; position: absolute; right: 25; top: 0; - width:25; + width: 22; height: 25; - .screenshotBox-snapshot{ - color : white; - top :0px; - right : 5px; - position: absolute; - background-color:rgba(50, 50, 50, 0.2); - transform-origin: left top; - pointer-events:all; - } - .screenshotBox-recorder{ - color : white; - top :0px; + .screenshotBox-recorder { + color: white; + top: 4px; left: 5px; position: absolute; - background-color:rgba(50, 50, 50, 0.2); + background-color: rgba(50, 50, 50, 0.2); transform-origin: left top; - pointer-events:all; + pointer-events: all; } } diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 312b3c619..271ff3cf8 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -25,6 +25,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import './ScreenshotBox.scss'; import { VideoBox } from './VideoBox'; +import { SettingsManager } from '../../util/SettingsManager'; declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has @@ -224,7 +225,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl const aud_chunks: any = []; this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data); this._audioRec.onstop = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({file}))); + const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file }))); if (!(result instanceof Error)) { this.dataDoc[this.props.fieldKey + '-audio'] = new AudioField(result.accessPaths.agnostic.client); } @@ -235,9 +236,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '-recordingStart'] = new DateField(new Date())); this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data); this._videoRec.onstop = async (e: any) => { - console.log('screenshotbox: upload'); const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() }); - const [{ result }] = await Networking.UploadFilesToServer({file}); + const [{ result }] = await Networking.UploadFilesToServer({ file }); this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000; if (!(result instanceof Error)) { // convert this screenshotBox into normal videoBox @@ -313,7 +313,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl </> </CollectionFreeFormView> </div> - <div style={{ background: 'white', position: 'relative', height: this.formattedPanelHeight() }}> + <div style={{ background: SettingsManager.Instance.userColor, position: 'relative', height: this.formattedPanelHeight() }}> {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : ( <FormattedTextBox {...this.props} @@ -335,8 +335,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl </div> </div> {!this.props.isSelected() ? null : ( - <div className="screenshotBox-uiButtons"> - <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording}> + <div className="screenshotBox-uiButtons" style={{ background: SettingsManager.Instance.userColor }}> + <div className="screenshotBox-recorder" style={{ color: SettingsManager.Instance.userBackgroundColor, background: SettingsManager.Instance.userVariantColor }} key="snap" onPointerDown={this.toggleRecording}> <FontAwesomeIcon icon="file" size="lg" /> </div> </div> diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 3ad3c911d..7c8a1849e 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -610,7 +610,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable ' ': { dataProvider: (token: any) => this.handleToken(token), component: (blob: any) => { - console.log('Blob', blob); return this.renderFuncListElement(blob.entity); }, output: (item: any, trigger: any) => { @@ -621,7 +620,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable '.': { dataProvider: (token: any) => this.handleToken(token), component: (blob: any) => { - console.log('Blob', blob); return this.renderFuncListElement(blob.entity); }, output: (item: any, trigger: any) => { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 5cdd6b5f2..07b931312 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -125,11 +125,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._searchRef.current?.setRangeText(searchString); }); } - if (clear) { - this._iframe?.contentWindow?.getSelection()?.empty(); - } - if (searchString) { - (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true); + try { + if (clear) { + this._iframe?.contentWindow?.getSelection()?.empty(); + } + if (searchString) { + (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true); + } + } catch (e) { + console.log("WebBox search error", e) } return true; }; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index b4fb7a44e..d5ad128fe 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -17,6 +17,7 @@ import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; import React = require('react'); +import { Transform } from '../../../util/Transform'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -113,6 +114,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna componentWillUnmount() { this._reactionDisposer?.(); } + return100 = () => 100; // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { @@ -123,7 +125,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna col={0} deselectCell={emptyFunction} selectCell={emptyFunction} - maxWidth={this.props.hideKey ? undefined : () => 100} + maxWidth={this.props.hideKey ? undefined : this.return100} columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero} selectedCell={() => [this._dashDoc!, 0]} fieldKey={this._fieldKey} @@ -135,6 +137,8 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna allowCRs={true} oneLine={!this._expanded} finishEdit={action(() => (this._expanded = false))} + transform={Transform.Identity} + menuTarget={null} /> </div> ); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 109b62e6f..348bdd79e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -84,7 +84,6 @@ audiotag:hover { height: 11; } -.formattedTextBox-outer-selected, .formattedTextBox-outer { position: relative; overflow: auto; @@ -92,9 +91,6 @@ audiotag:hover { width: 100%; height: unset; } -.formattedTextBox-outer-selected { - cursor: text; -} .formattedTextBox-sidebar-handle { position: absolute; @@ -148,10 +144,8 @@ audiotag:hover { } .formattedTextBox-inner-rounded, -.formattedTextBox-inner-rounded-selected, .formattedTextBox-inner, -.formattedTextBox-inner-minimal, -.formattedTextBox-inner-selected { +.formattedTextBox-inner-minimal { height: 100%; white-space: pre-wrap; .ProseMirror:hover { @@ -169,17 +163,6 @@ audiotag:hover { border-width: 1px; } } -.formattedTextBox-inner-rounded-selected, -.formattedTextBox-inner-selected { - > .ProseMirror { - padding: 10px; - } -} -.formattedTextBox-outer-selected { - > .ProseMirror:hover { - background: unset; - } -} .gpt-typing-wrapper { padding: 10px; @@ -640,7 +623,6 @@ footnote::before { } } - .formattedTextBox-outer-selected, .formattedTextBox-outer { position: relative; overflow: auto; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 072977150..332f0f467 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -97,6 +97,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; public _applyingChange: string = ''; + private _finishingLink = false; private _searchIndex = 0; private _lastTimedMark: Mark | undefined = undefined; private _cachedLinks: Doc[] = []; @@ -246,7 +247,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc; const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title), annotationOn: this.rootDoc }); this.addDocument(anchor); + this._finishingLink = true; this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation); + this._finishingLink = false; PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc); return anchor; }; @@ -327,7 +330,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); if ((!prevData && !protoData) || newText || (!newText && !templateData)) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (this.props.isContentActive() && removeSelection(newJson) !== removeSelection(prevData?.Data)) { + if ((this._finishingLink || this.props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) { const numstring = NumCast(dataDoc[this.fieldKey], null); dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText); dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited @@ -458,8 +461,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { alink = alink ?? - (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)) || - DocUtils.MakeLink(this.props.Document, target, { link_relationship: LinkManager.AutoKeywords })!); + (LinkManager.Links(this.rootDoc).find( + link => + Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && // + Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target) + ) || + DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!); newAutoLinks.add(alink); const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); @@ -841,7 +848,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const appearance = cm.findByDescription('Appearance...'); const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; - appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); + appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); !Doc.noviceMode && @@ -1589,7 +1596,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); }; + @action componentWillUnmount() { + if (this._recording) { + this._recording = !this._recording; + } Object.values(this._disposers).forEach(disposer => disposer?.()); this.endUndoTypingBatch(); FormattedTextBox.LiveTextUndo?.end(); @@ -1628,8 +1639,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } if (this._recording && !e.ctrlKey && e.button === 0) { this.breakupDictation(); - e.preventDefault(); - e.stopPropagation(); } this._downX = e.clientX; this._downY = e.clientY; @@ -2026,12 +2035,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } cycleAlternateText = () => { if (this.layoutDoc._layout_enableAltContentUI) { - const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`]; + const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`]; this.rootDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; } }; @computed get overlayAlternateIcon() { - const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`]; + const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`]; return ( <Tooltip title={ @@ -2075,17 +2084,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } }; _oldWheel: any; + @computed get fontColor() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color); + } + @computed get fontSize() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize); + } + @computed get fontFamily() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily); + } + @computed get fontWeight() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight); + } render() { TraceMobx(); - const active = this.props.isContentActive(); const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : ''; - if (!active && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); - const minimal = this.props.ignoreAutoHeight; + setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const selPad = (active && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0; - const selPaddingClass = active && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : ''; const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > return styleFromLayoutString?.height === '0px' ? null : ( <div @@ -2102,23 +2119,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ? {} : { transform: `scale(${scale})`, - transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%`, }), - display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined, + // display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined, transition: 'inherit', // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined, - color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), - fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), - fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily), - fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight), + color: this.fontColor, + fontSize: this.fontSize, + fontFamily: this.fontFamily, + fontWeight: this.fontWeight, ...styleFromLayoutString, }}> <div className="formattedTextBox-cont" ref={this._ref} style={{ + cursor: this.props.isContentActive() ? 'text' : undefined, overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined), pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none', @@ -2133,7 +2150,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onPointerDown={this.onPointerDown} onDoubleClick={this.onDoubleClick}> <div - className={`formattedTextBox-outer${active ? '-selected' : ''}`} + className={`formattedTextBox-outer`} ref={this._scrollRef} style={{ width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`, @@ -2142,15 +2159,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onScroll={this.onScroll} onDrop={this.ondrop}> <div - className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`} + className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ padding: StrCast(this.layoutDoc._textBoxPadding), - paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`), - paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`), - paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`), - paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`), - // pointerEvents: !active && IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined, + paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`), + paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`), + paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`), + paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`), }} /> </div> diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index ac1e7ce5d..8bafc2cef 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -243,13 +243,13 @@ export class RichTextRules { // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // [[<fieldKey> : <Doc>]] - // [[:Doc]] => hyperlink + // [[:docTitle]] => hyperlink // [[fieldKey]] => show field // [[fieldKey=value]] => show field and also set its value - // [[fieldKey:Doc]] => show field of doc + // [[fieldKey:docTitle]] => show field of doc new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; - const docId = match[3]?.replace(':', ''); + const docTitle = match[3]?.replace(':', ''); const value = match[2]?.substring(1); const linkToDoc = (target: Doc) => { const rstate = this.TextBox.EditorView?.state; @@ -266,12 +266,12 @@ export class RichTextRules { } }; if (!fieldKey) { - if (docId) { - const target = DocServer.QUERY_SERVER_CACHE(docId); - if (target) setTimeout(() => linkToDoc(target)); - else DocServer.GetRefField(docId).then(docx => linkToDoc((docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId + '(auto)', _width: 500, _height: 500 }, docId))); - - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); + if (docTitle) { + const target = DocServer.FindDocByTitle(docTitle); + if (target) { + setTimeout(() => linkToDoc(target)); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); + } } return state.tr; } @@ -279,8 +279,12 @@ export class RichTextRules { const num = value.match(/^[0-9.]$/); this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId, hideKey: false }); - return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); + const target = DocServer.FindDocByTitle(docTitle); + if (target) { + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target[Id], hideKey: false }); + return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); + } + return state.tr; }), // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx index a5d21cc8e..58f0b29e4 100644 --- a/src/client/views/nodes/importBox/ImportElementBox.tsx +++ b/src/client/views/nodes/importBox/ImportElementBox.tsx @@ -1,13 +1,13 @@ -import { observer } from "mobx-react"; -import { ViewBoxBaseComponent } from "../../DocComponent"; -import { FieldView, FieldViewProps } from "../FieldView"; -import { computed } from "mobx"; -import { Id } from "../../../../fields/FieldSymbols"; -import React from "react"; -import { EditableView } from "../../EditableView"; -import { DocListCast } from "../../../../fields/Doc"; -import { StrCast } from "../../../../fields/Types"; - +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../../../fields/Doc'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../../Utils'; +import { Transform } from '../../../util/Transform'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { DefaultStyleProvider } from '../../StyleProvider'; +import { DocumentView, DocumentViewInternal } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import React = require('react'); @observer export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -15,56 +15,24 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() { return FieldView.LayoutString(ImportElementBox, fieldKey); } - private _itemRef: React.RefObject<HTMLDivElement> = React.createRef(); - private _dragRef: React.RefObject<HTMLDivElement> = React.createRef(); - private _titleRef: React.RefObject<EditableView> = React.createRef(); - - @computed importBoxVoew() { - return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox; - } - - @computed get indexInPres() { - return DocListCast(this.presBox?.[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc); + screenToLocalXf = () => this.props.ScreenToLocalTransform().scale(1 * (this.props.NativeDimScaling?.() || 1)); + @computed get mainItem() { + return ( + <div style={{ backgroundColor: 'pink' }}> + <DocumentView + {...this.props} // + LayoutTemplateString={undefined} + Document={this.rootDoc} + isContentActive={returnFalse} + DataDoc={undefined} + addDocument={returnFalse} + ScreenToLocalTransform={this.screenToLocalXf} + hideResizeHandles={true} + /> + </div> + ); } - - @computed get presBox() { - return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc; + render() { + return !(this.rootDoc instanceof Doc) ? null : this.mainItem; } - - // @computed get selectedArray() { - // return this.presBoxView?.selectedArray; - // } - -@computed get mainItem() { - const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres; - //const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false; - // const activeItem: Doc = this.rootDoc; - - return( - <div - className = {`presItem-container`} - // key={this.props.Document[Id] + this.indexInPres} - style = {{backgroundColor: 'pink'}} - - > - <div - ref = {this._dragRef} - className = {`presItem-slide ${isCurrent ? 'active' : ''}`} - style = {{ - backgroundColor: 'green' - }}> - <div - className="presItem-number" - title = "select without navigation" - - > - {/* <EditableView ref={this._titleRef} oneLine={true} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} /> */} - - </div> - </div> - - </div> - ) } - -}
\ No newline at end of file diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index dd8cf7002..7404650d6 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -17,6 +17,7 @@ import { EditorView } from 'prosemirror-view'; import './AnchorMenu.scss'; import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { StrCast } from '../../../fields/Types'; +import { DocumentType } from '../../documents/DocumentTypes'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -241,7 +242,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { colorPicker={this.highlightColor} color={StrCast(Doc.UserDoc().userColor)} /> - <ColorPicker colorPickerType={'github'} selectedColor={this.highlightColor} setSelectedColor={color => this.changeHighlightColor(color)} size={Size.XSMALL} /> + <ColorPicker selectedColor={this.highlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} /> </Group> ); } @@ -266,7 +267,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { canSummarize = (): boolean => { const docs = SelectionManager.Docs(); if (docs.length > 0) { - return docs.some(doc => doc.type === 'pdf' || doc.type === 'web'); + return docs.some(doc => doc.type === DocumentType.PDF || doc.type === DocumentType.WEB); } return false; }; @@ -288,31 +289,74 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { this.Status === 'marquee' ? ( <> {this.highlighter} - <IconButton tooltip={'Drag to Place Annotation'} onPointerDown={this.pointerDown} icon={<FontAwesomeIcon icon="comment-alt" />} color={StrCast(Doc.UserDoc().userColor)} /> + <IconButton + tooltip="Drag to Place Annotation" // + onPointerDown={this.pointerDown} + icon={<FontAwesomeIcon icon="comment-alt" />} + color={StrCast(Doc.UserDoc().userColor)} + /> {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( - <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}> - <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}> - <FontAwesomeIcon icon="comment-dots" size="lg" /> - </button> - </Tooltip> + <IconButton + tooltip="Summarize with AI" // + onPointerDown={this.gptSummarize} + icon={<FontAwesomeIcon icon="comment-dots" size="lg" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( - <IconButton tooltip={'Click to Record Annotation'} onPointerDown={this.audioDown} icon={<FontAwesomeIcon icon="microphone" />} color={StrCast(Doc.UserDoc().userColor)} /> + <IconButton + tooltip="Click to Record Annotation" // + onPointerDown={this.audioDown} + icon={<FontAwesomeIcon icon="microphone" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} {/* Removed text editing for now, not quite ready */} {/* {this.canEdit() && <IconButton tooltip={'AI edit suggestions'} onPointerDown={this.gptEdit} icon={<FontAwesomeIcon icon="pencil-alt" />} color={StrCast(Doc.UserDoc().userColor)} />} */} {<IconButton tooltip={'Generate DALL-E Image'} onPointerDown={this.gptImage} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} />} - <Popup tooltip="Find document to link to selected text" type={Type.PRIM} icon={<FontAwesomeIcon icon={'search'} />} popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} color={StrCast(Doc.UserDoc().userColor)} /> + <Popup + tooltip="Find document to link to selected text" // + type={Type.PRIM} + icon={<FontAwesomeIcon icon={'search'} />} + popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} + color={StrCast(Doc.UserDoc().userColor)} + /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : ( - <IconButton tooltip={'Click/Drag to create cropped image'} onPointerDown={this.cropDown} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} /> + <IconButton + tooltip="Click/Drag to create cropped image" // + onPointerDown={this.cropDown} + icon={<FontAwesomeIcon icon="image" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} </> ) : ( <> - {this.Delete !== returnFalse && <IconButton tooltip={'Remove Link Anchor'} onPointerDown={this.Delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={StrCast(Doc.UserDoc().userColor)} />} - {this.PinToPres !== returnFalse && <IconButton tooltip={'Pin to Presentation'} onPointerDown={this.PinToPres} icon={<FontAwesomeIcon icon="map-pin" />} color={StrCast(Doc.UserDoc().userColor)} />} - {this.ShowTargetTrail !== returnFalse && <IconButton tooltip={'Show Linked Trail'} onPointerDown={this.ShowTargetTrail} icon={<FontAwesomeIcon icon="taxi" />} color={StrCast(Doc.UserDoc().userColor)} />} + {this.Delete !== returnFalse && ( + <IconButton + tooltip="Remove Link Anchor" // + onPointerDown={this.Delete} + icon={<FontAwesomeIcon icon="trash-alt" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} + {this.PinToPres !== returnFalse && ( + <IconButton + tooltip="Pin to Presentation" // + onPointerDown={this.PinToPres} + icon={<FontAwesomeIcon icon="map-pin" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} + {this.ShowTargetTrail !== returnFalse && ( + <IconButton + tooltip="Show Linked Trail" // + onPointerDown={this.ShowTargetTrail} + icon={<FontAwesomeIcon icon="taxi" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} {this.IsTargetToggler !== returnFalse && ( <Toggle tooltip={'Make target visibility toggle on click'} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index e911bd283..1ceea697a 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -18,6 +18,7 @@ import './SearchBox.scss'; import { fetchRecommendations } from '../newlightbox/utils'; import { IRecommendation, Recommendation } from '../newlightbox/components'; import { Colors } from '../global/globalEnums'; +import { SettingsManager } from '../../util/SettingsManager'; const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; @@ -218,7 +219,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { } @action static staticSearchCollection(rootDoc: Opt<Doc>, query: string) { - const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; + const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.SEARCH, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; const blockedKeys = [ 'x', 'y', @@ -398,22 +399,21 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { if (query) { this.searchCollection(query); - const response = await fetchRecommendations('', query, [], true) - const recs = response.recommendations - const recommendations:IRecommendation[] = [] + const response = await fetchRecommendations('', query, [], true); + const recs = response.recommendations; + const recommendations: IRecommendation[] = []; for (const key in recs) { const title = recs[key].title; - console.log(title); - const url = recs[key].url - const type = recs[key].type - const text = recs[key].text - const transcript = recs[key].transcript - const previewUrl = recs[key].previewUrl - const embedding = recs[key].embedding - const distance = recs[key].distance - const source = recs[key].source - const related_concepts = recs[key].related_concepts - const docId = recs[key].doc_id + const url = recs[key].url; + const type = recs[key].type; + const text = recs[key].text; + const transcript = recs[key].transcript; + const previewUrl = recs[key].previewUrl; + const embedding = recs[key].embedding; + const distance = recs[key].distance; + const source = recs[key].source; + const related_concepts = recs[key].related_concepts; + const docId = recs[key].doc_id; recommendations.push({ title: title, data: url, @@ -425,11 +425,11 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { distance: Math.round(distance * 100) / 100, source: source, related_concepts: related_concepts, - docId: docId - }) + docId: docId, + }); } - const setRecommendations = action(() => this._recommendations = recommendations) - setRecommendations() + const setRecommendations = action(() => (this._recommendations = recommendations)); + setRecommendations(); } }; @@ -439,6 +439,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { */ resetSearch = action(() => { this._results.forEach((_, doc) => { + DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); Doc.UnBrushDoc(doc); Doc.UnHighlightDoc(doc); Doc.ClearSearchMatches(); @@ -461,7 +462,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { */ @computed public get selectOptions() { - const selectValues = ['all', 'rtf', 'image', 'pdf', 'web', 'video', 'audio', 'collection']; + const selectValues = ['all', DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.WEB, DocumentType.VID, DocumentType.AUDIO, DocumentType.COL]; return selectValues.map(value => ( <option key={value} value={value}> @@ -517,19 +518,19 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { className={className}> <div className="searchBox-result-title">{title as string}</div> <div className="searchBox-result-type">{formattedType}</div> - <div className="searchBox-result-keys">{result[1].join(', ')}</div> + <div className="searchBox-result-keys" style={{ color: SettingsManager.Instance.userVariantColor }}> + {result[1].join(', ')} + </div> </div> </Tooltip> ); } }); - const recommendationsJSX: JSX.Element[] = this._recommendations.map((props) => ( - <Recommendation {...props}/> - )) + const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />); return ( - <div className="searchBox-container" style={{pointerEvents: 'all', background: StrCast(Doc.UserDoc().userBackgroundColor)}}> + <div className="searchBox-container" style={{ pointerEvents: 'all', color: SettingsManager.Instance.userColor, background: SettingsManager.Instance.userBackgroundColor }}> <div className="searchBox-bar"> {isLinkSearch ? null : ( <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}> @@ -552,20 +553,24 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { ref={this._inputRef} /> </div> - {resultsJSX.length > 0 && <div className="searchBox-results-container"> - <div className="section-header" style={{background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}}> - <div className="section-title">Results</div> - <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div> + {resultsJSX.length > 0 && ( + <div className="searchBox-results-container"> + <div className="section-header" style={{ background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE) }}> + <div className="section-title">Results</div> + <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div> + </div> + <div className="searchBox-results-view">{resultsJSX}</div> </div> - <div className="searchBox-results-view">{resultsJSX}</div> - </div>} - {recommendationsJSX.length > 0 && <div className="searchBox-recommendations-container"> - <div className="section-header" style={{background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE)}}> - <div className="section-title">Recommendations</div> - <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div> + )} + {recommendationsJSX.length > 0 && ( + <div className="searchBox-recommendations-container"> + <div className="section-header" style={{ background: StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE) }}> + <div className="section-title">Recommendations</div> + <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div> + </div> + <div className="searchBox-recommendations-view">{recommendationsJSX}</div> </div> - <div className="searchBox-recommendations-view">{recommendationsJSX}</div> - </div>} + )} </div> ); } diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index cb8eda9de..c194ede32 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { FaBug, FaCamera, FaStamp } from 'react-icons/fa'; import { Doc, DocListCast } from '../../../fields/Doc'; -import { AclAdmin } from '../../../fields/DocSymbols'; +import { AclAdmin, DashVersion } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; import { GetEffectiveAcl } from '../../../fields/util'; import { DocumentManager } from '../../util/DocumentManager'; @@ -21,6 +21,7 @@ import { MainView } from '../MainView'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { Colors } from '../global/globalEnums'; import './TopBar.scss'; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; /** * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user @@ -35,10 +36,14 @@ export class TopBar extends React.Component { }); }; - @computed get color() { return StrCast(Doc.UserDoc().userColor, Colors.LIGHT_GRAY); } - @computed get variantColor() { return StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE); } + @computed get color() { + return StrCast(Doc.UserDoc().userColor, Colors.LIGHT_GRAY); + } + @computed get variantColor() { + return StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE); + } @computed get backgroundColor() { - return PingManager.Instance.IsBeating ? StrCast(Doc.UserDoc().userBackgroundColor, Colors.DARK_GRAY) : Colors.MEDIUM_GRAY; + return PingManager.Instance.IsBeating ? SettingsManager.Instance.userBackgroundColor : Colors.MEDIUM_GRAY; } @observable happyHeart: boolean = PingManager.Instance.IsBeating; @@ -102,7 +107,7 @@ export class TopBar extends React.Component { tooltip="Open Dashboards" size={Size.SMALL} color={this.color} - style={{fontWeight: 700, fontSize: '1rem'}} + style={{ fontWeight: 700, fontSize: '1rem' }} onClick={(e: React.MouseEvent) => { const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard); ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' }); @@ -132,9 +137,10 @@ export class TopBar extends React.Component { * and allows the user to access their account settings etc. */ @computed get topbarRight() { + const upToDate = DashVersion === CurrentUserUtils.ServerVersion; return ( <div className="topbar-right"> - {Doc.ActiveDashboard ? + {Doc.ActiveDashboard ? ( <Button text={GetEffectiveAcl(Doc.ActiveDashboard) === AclAdmin ? 'Share' : 'View Original'} type={Type.TERT} @@ -143,16 +149,16 @@ export class TopBar extends React.Component { SharingManager.Instance.open(undefined, Doc.ActiveDashboard); }} /> - : null } - <IconButton tooltip={"Issue Reporter ⌘I"} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} /> - <IconButton tooltip={"Documentation ⌘D"} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} /> - <IconButton tooltip={"Settings ⌘⇧S"} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} /> + ) : null} + <IconButton tooltip={'Issue Reporter ⌘I'} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} /> + <IconButton tooltip={'Documentation ⌘D'} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} /> + <IconButton tooltip={'Settings ⌘⇧S'} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} /> <IconButton size={Size.SMALL} onClick={ServerStats.Instance.open} type={Type.TERT} - tooltip={'Server is ' + (PingManager.Instance.IsBeating ? '' : 'NOT ') + 'running'} - color={this.happyHeart ? Colors.LIGHT_BLUE : Colors.ERROR_RED} + tooltip={'Server is ' + (PingManager.Instance.IsBeating ? '' : 'NOT ') + 'running ' + (upToDate ? DashVersion : 'out of date version:' + DashVersion)} + color={this.happyHeart ? (upToDate ? Colors.LIGHT_BLUE : Colors.YELLOW) : Colors.ERROR_RED} icon={<FontAwesomeIcon icon={this.happyHeart ? 'heart' : 'heart-broken'} />} /> {/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.color} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */} @@ -163,12 +169,14 @@ export class TopBar extends React.Component { render() { return ( //TODO:glr Add support for light / dark mode - <div style={{ - pointerEvents: 'all', - color: this.color, - background: this.backgroundColor, - // borderColor: this.color - }} className="topbar-container"> + <div + style={{ + pointerEvents: 'all', + color: this.color, + background: this.backgroundColor, + // borderColor: this.color + }} + className="topbar-container"> <div className="topbar-inner-container"> {this.topbarLeft} {this.topbarCenter} |
