diff options
Diffstat (limited to 'src')
34 files changed, 681 insertions, 637 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index e03632c8b..8b9fe2aab 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -16,18 +16,18 @@ export namespace Utils { return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD; } - export function cleanDocumentType(type: DocumentType) { - switch(type) { + export function cleanDocumentType(type: DocumentType) { + switch (type) { case DocumentType.IMG: - return "Image" + return 'Image'; case DocumentType.AUDIO: - return "Audio" + return 'Audio'; case DocumentType.COL: - return "Collection" + return 'Collection'; case DocumentType.RTF: - return "Text" - default: - return type.charAt(0).toUpperCase() + type.slice(1) + return 'Text'; + default: + return type.charAt(0).toUpperCase() + type.slice(1); } } @@ -80,7 +80,7 @@ export namespace Utils { } } - export function GetScreenTransform(ele?: HTMLElement): { scale: number; translateX: number; translateY: number } { + export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } { if (!ele) { return { scale: 1, translateX: 1, translateY: 1 }; } @@ -140,7 +140,7 @@ export namespace Utils { } export function colorString(color: ColorState) { - return color.hex.startsWith('#') ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex; + return color.hex.startsWith('#') && color.hex.length < 8 ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex; } export function fromRGBAstr(rgba: string) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f3f645ca2..f22fa9f17 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -58,6 +58,7 @@ import { SliderBox } from '../views/nodes/SliderBox'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import { PresBox } from '../views/nodes/trails/PresBox'; import { PresElementBox } from '../views/nodes/trails/PresElementBox'; +import { ImportElementBox } from '../views/nodes/importBox/ImportElementBox'; import { VideoBox } from '../views/nodes/VideoBox'; import { WebBox } from '../views/nodes/WebBox'; import { SearchBox } from '../views/search/SearchBox'; @@ -354,6 +355,7 @@ export class DocumentOptions { onDoubleClick?: ScriptField; onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked + onClickScriptDisable?: STRt = new StrInfo('"always" disable click script, "never" disable click script, or default'); defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait onPointerDown?: ScriptField; @@ -620,7 +622,7 @@ export namespace Docs { DocumentType.WEBCAM, { layout: { view: RecordingBox, dataField: defaultDataKey }, - options: { systemIcon: 'BsFillCameraVideoFill' }, + options: { systemIcon: 'BsFillCameraVideoFill' }, }, ], [ @@ -693,7 +695,22 @@ export namespace Docs { { data: '', layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 }, - options: { _height: 100, layout_forceReflow: true, nativeHeightUnfrozen: true, mass1: '', mass2: '', nativeDimModifiable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' }, + options: { + _height: 100, + layout_forceReflow: true, + nativeHeightUnfrozen: true, + mass1: '', + mass2: '', + nativeDimModifiable: true, + position: '', + acceleration: '', + pendulum: '', + spring: '', + wedge: '', + simulation: '', + review: '', + systemIcon: 'BsShareFill', + }, }, ], ]); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2e4fb0f1c..a86011042 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -344,13 +344,13 @@ export class CurrentUserUtils { const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails"; return [ { title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), ignoreClick: true, icon: "desktop", funcs: {hidden: "IsNoviceMode()"} }, - { title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), ignoreClick: true, icon: "search", }, + { title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), ignoreClick: true, icon: "search", }, { title: "Files", toolTip: "Files", target: this.setupFilesystem(doc, "myFilesystem"), ignoreClick: true, icon: "folder-open", }, { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), ignoreClick: true, icon: "wrench", funcs: {hidden: "IsNoviceMode()"} }, - { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), ignoreClick: true, icon: "upload", }, + { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), ignoreClick:false, icon: "upload", }, { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), ignoreClick: true, icon: "archive", }, { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}}, - { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}}, + { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}}, { title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, icon: "address-card",funcs: {hidden: "IsNoviceMode()"} }, ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}})); } @@ -805,19 +805,17 @@ export class CurrentUserUtils { DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); if (!Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards) Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards = new List<Doc>(); - console.log(doc.mySharedDocs); } /// Import option on the left side button panel - static setupImportSidebar(doc: Doc, field:string) { - // PresElementBox.LayoutString('data') + static setupImportSidebar(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { - title: "My Imports", _forceActive: true, ignoreClick: true, _layout_showTitle: "title", childLayoutString: ImportElementBox.LayoutString('data'), - _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0, + title: "My Imports", _forceActive: true, _layout_showTitle: "title", childLayoutString: ImportElementBox.LayoutString('data'), + _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0, onClickScriptDisable:"never", childDragAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true, dontRegisterView: true, layout_explainer: "This is where documents that are Imported into Dash will go." }; - const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts); + const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.MasonryDocument([], opts), reqdOpts, undefined, {onClick: "deselectAll()"}); const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, color: Colors.BLACK, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss index 253ed5d2a..673af16ee 100644 --- a/src/client/util/GroupManager.scss +++ b/src/client/util/GroupManager.scss @@ -7,33 +7,48 @@ .group-create { display: flex; flex-direction: column; - height: 90%; + overflow: visible; + height: 100%; + width: 98.5%; + padding: 5px; justify-content: space-between; margin-left: 5px; - input { - border-radius: 5px; - padding: 8px; - min-width: 100%; - border: 1px solid hsl(0, 0%, 80%); - outline: none; - height: 30; - - &:focus { - border: 2.5px solid #2684FF; - } - } - p { font-size: 20px; text-align: left; - color: black; } + } - button { - align-self: flex-end; + .group-input { + + input { + padding: 8px; + width: 100%; + outline: none; + border: none; + min-width: 100%; + border: 2px solid; + border-radius: 4px; + border-color: inherit; + background: white; + color: black; + height: 30; } } + + .select-users { + margin-right: 3; + max-height: 30; + width: 100%; + display: inline-flex; + flex-direction: row; + border: 2px solid; + border-radius: 4px; + border-color: inherit; + background: white; + color: black; + } } @@ -55,11 +70,14 @@ .group-interface { display: flex; flex-direction: column; + overflow: hidden; + padding: 10px; .overlay { transform: translate(-10px, -10px); width: 400px; height: 320px; + overflow: hidden; } .delete-button { @@ -81,7 +99,6 @@ font-size: 20px; text-align: left; margin-right: 15px; - color: black; } } @@ -94,18 +111,24 @@ margin-left: 5; width: 50px; cursor: pointer; + display: flex; + flex-direction: row; + } + + .style-divider { + width: 100%; + height: 1px; } .group-body { justify-content: space-between; - height: 220; - background-color: #e8e8e8; + height: 225; padding-right: 1em; justify-content: space-around; text-align: left; - overflow-y: auto; + overflow-y: hidden; width: 100%; .group-row { @@ -117,7 +140,6 @@ .group-name { max-width: 65%; margin: 0 10; - color: black; } .group-info { diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index da947aba6..5802d5ee0 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -15,6 +15,7 @@ import { SharingManager, User } from './SharingManager'; import { listSpec } from '../../fields/Schema'; import { DateField } from '../../fields/DateField'; import { Id } from '../../fields/FieldSymbols'; +import { Button, IconButton, Size } from 'browndash-components'; /** * Interface for options for the react-select component @@ -280,47 +281,59 @@ export class GroupManager extends React.Component<{}> { */ private get groupCreationModal() { const contents = ( - <div className="group-create"> + <div className="group-create" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}> <div className="group-heading" style={{ marginBottom: 0 }}> <p> <b>New Group</b> </p> - <div - className={'close-button'} - onClick={action(() => { - this.createGroupModalOpen = false; - TaskCompletionBox.taskCompleted = false; - })}> - <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> + <div className="close-button"> + <Button + icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} + onClick={action(() => { + this.createGroupModalOpen = false; + TaskCompletionBox.taskCompleted = false; + })} + color={StrCast(Doc.UserDoc().userColor)} + /> </div> </div> - <input className="group-input" ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} /> - <Select - isMulti - options={this.options} - onChange={this.handleChange} - placeholder={'Select users'} - value={this.selectedUsers} - closeMenuOnSelect={false} - styles={{ - dropdownIndicator: (base, state) => ({ - ...base, - transition: '0.5s all ease', - transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined, - }), - multiValue: base => ({ - ...base, - maxWidth: '50%', - - '&:hover': { - maxWidth: 'unset', - }, - }), - }} - /> - <button ref={this.createGroupButtonRef} onClick={this.createGroup} style={{ background: this.buttonColour }} disabled={this.buttonColour === '#979797'}> - Create - </button> + <div className="group-input" style={{border: StrCast(Doc.UserDoc().userColor)}} > + <input ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} /> + </div> + <div style={{border: StrCast(Doc.UserDoc().userColor)}}> + <Select + className="select-users" + isMulti + options={this.options} + onChange={this.handleChange} + placeholder={'Select users'} + value={this.selectedUsers} + closeMenuOnSelect={false} + styles={{ + control: () => ({ + display: 'inline-flex', + width: '100%', + }), + indicatorSeparator: () => ({ + display: 'inline-flex', + visibility: 'hidden', + }), + indicatorsContainer: () => ({ + display: 'inline-flex', + textDecorationColor: 'black', + }), + valueContainer: () => ({ + display: 'inline-flex', + fontStyle: StrCast(Doc.UserDoc().userColor), + color: StrCast(Doc.UserDoc().userColor), + width: '100%', + }), + }} + /> + </div> + <div className={"create-button"}> + <Button text={"Create"} type={"tertiary"} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} /> + </div> </div> ); @@ -353,37 +366,34 @@ export class GroupManager extends React.Component<{}> { const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; return ( - <div className="group-interface"> + <div className="group-interface" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}> {this.groupCreationModal} {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null} <div className="group-heading"> <p> <b>Manage Groups</b> </p> - <button onClick={action(() => (this.createGroupModalOpen = true))}> - <FontAwesomeIcon icon={'plus'} size={'sm'} /> Create Group - </button> - <div className={'close-button'} onClick={this.close}> - <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> + <Button icon={<FontAwesomeIcon icon={'plus'}/>} iconPlacement={'left'} text={"Create Group"} type={"tertiary"} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} /> + <div className={'close-button'} > + <Button + icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} + onClick={this.close} + color={StrCast(Doc.UserDoc().userColor)} + /> </div> </div> <div className="main-container"> <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> - Name{' '} - {this.groupSort === 'ascending' ? ( - <FontAwesomeIcon icon={'caret-up'} size={'xs'} /> - ) : this.groupSort === 'descending' ? ( - <FontAwesomeIcon icon={'caret-down'} size={'xs'} /> - ) : ( - <FontAwesomeIcon icon={'caret-right'} size={'xs'} /> - )} + Name + <IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'}/>} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} /> </div> - <div className="group-body"> + <div className={'style-divider'} style={{background: StrCast(Doc.UserDoc().userColor)}}/> + <div className="group-body" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}> {groups.map(group => ( <div className="group-row" key={StrCast(group.title || group.groupName)}> <div className="group-name">{StrCast(group.title || group.groupName)}</div> <div className="group-info" onClick={action(() => (this.currentGroup = group))}> - <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> + <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.currentGroup = group))} /> </div> </div> ))} diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss index 2eb164988..d50569b26 100644 --- a/src/client/util/GroupMemberView.scss +++ b/src/client/util/GroupMemberView.scss @@ -1,6 +1,9 @@ .editing-interface { width: 100%; height: 100%; + padding: 10px; + float: right; + overflow: visible; hr { margin-top: 20; @@ -10,7 +13,6 @@ outline: none; border-radius: 5px; border: 0px; - color: #fcfbf7; text-transform: none; letter-spacing: 2px; font-size: 75%; @@ -37,8 +39,6 @@ text-align: center; border: none; outline: none; - color: black; - margin-top: -5; height: 20; text-overflow: ellipsis; background: none; @@ -61,21 +61,36 @@ margin-bottom: 25; .add-member-dropdown { - width: 65%; - margin: 0 5; + margin-right: 3; + max-height: 30; + width: 250px; + display: inline-flex; + flex-direction: row; + border: 2px solid; + border-radius: 4px; + border-color: inherit; + background: white; + color: black; + } - input { - height: 30; - } + .delete-button { + align-self: center; + background: inherit; } } } + .style-divider { + width: 98%; + height: 1px; + margin-top: 20px; + margin-bottom: 20px; + } + .editing-contents { overflow-y: auto; height: 62%; width: 100%; - color: black; margin-top: -15px; .editing-row { diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index b7f89794d..f2050dc61 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -8,6 +8,7 @@ import { StrCast } from "../../fields/Types"; import { MainViewModal } from "../views/MainViewModal"; import { GroupManager, UserOptions } from "./GroupManager"; import "./GroupMemberView.scss"; +import { Button, IconButton, Size } from "browndash-components"; interface GroupMemberViewProps { group: Doc; @@ -28,7 +29,7 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group); return (!this.props.group ? null : - <div className="editing-interface"> + <div className="editing-interface" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}> <div className="editing-header"> <input className="group-title" @@ -38,28 +39,48 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { disabled={!hasEditAccess} > </input> - <div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <div className={"memberView-closeButton"} > + <Button + icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} + onClick={action(this.props.onCloseButtonClick)} + color={StrCast(Doc.UserDoc().userColor)} + /> </div> {GroupManager.Instance.hasEditAccess(this.props.group) ? <div className="group-buttons"> - <div className="add-member-dropdown"> - <Select + <div style={{border: StrCast(Doc.UserDoc().userColor)}} > + <Select + className="add-member-dropdown" isSearchable={true} options={options} onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.props.group, (selectedOption as UserOptions).value)} placeholder={"Add members"} value={null} styles={{ - dropdownIndicator: (base, state) => ({ - ...base, - transition: '0.5s all ease', - transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined - }) + control: () => ({ + display: 'inline-flex', + width: '100%', + }), + indicatorSeparator: () => ({ + display: 'inline-flex', + visibility: 'hidden', + }), + indicatorsContainer: () => ({ + display: 'inline-flex', + textDecorationColor: 'black', + }), + valueContainer: () => ({ + display: 'inline-flex', + fontStyle: StrCast(Doc.UserDoc().userColor), + color: StrCast(Doc.UserDoc().userColor), + width: '100%', + }), }} /> </div> - <button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button> + <div className={"delete-button"}> + <Button text={"Delete Group"} type={"tertiary"} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.deleteGroup(this.props.group)} /> + </div> </div> : null} <div @@ -69,7 +90,7 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */} </div> </div> - <hr /> + <div className={'style-divider'} style={{background: StrCast(Doc.UserDoc().userColor)}}/> <div className="editing-contents" style={{ height: hasEditAccess ? "62%" : "85%" }} > @@ -83,7 +104,7 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { </div> {hasEditAccess ? <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}> - <FontAwesomeIcon icon={"trash-alt"} size={"sm"} /> + <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)} /> </div> : null} </div> diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 043f0f1f3..d0f459291 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -178,7 +178,7 @@ export namespace InteractionUtils { filter: mask ? `url(#mask${defGuid})` : undefined, opacity: 1.0, // opacity: strokeWidth !== width ? 0.5 : undefined, - pointerEvents: pevents as any, + pointerEvents: (pevents as any) === 'all' ? 'visiblepainted' : (pevents as any), stroke: color ?? 'rgb(0, 0, 0)', strokeWidth: strokeWidth, strokeLinecap: lineCap as any, diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 3e526c4c0..b8fea340f 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -30,7 +30,7 @@ export class LinkFollower { public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, altKey: boolean) => { const batch = UndoManager.StartBatch('Follow Link'); runInAction(() => (LinkFollower.IsFollowing = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value - LinkFollower.traverseLink( + return LinkFollower.traverseLink( linkDoc, sourceDoc, action(() => { @@ -54,7 +54,10 @@ export class LinkFollower { const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1); var count = 0; const allFinished = () => ++count === followLinks.length && finished?.(); - if (!followLinks.length) finished?.(); + if (!followLinks.length) { + finished?.(); + return false; + } followLinks.forEach(async linkDoc => { const target = ( sourceDoc === linkDoc.link_anchor_1 @@ -120,17 +123,18 @@ export class LinkFollower { allFinished(); } }); + return true; } } ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { SelectionManager.DeselectAll(); - LinkFollower.FollowLink(undefined, doc, altKey); + return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true }; }); export function FollowLinkScript() { - return ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); + return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' }); } export function IsFollowLinkScript(field: FieldResult<Field>) { - return ScriptCast(field)?.script.originalScript.includes('followLink('); + return ScriptCast(field)?.script.originalScript.includes('return followLink('); } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 0125331ec..4be9448b3 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -121,3 +121,6 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); return selected?.type === type || selected?.type_collection === type || !type; }); +ScriptingGlobals.add(function deselectAll() { + SelectionManager.DeselectAll(); +}); diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index b11e694ff..c9f86f0e1 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -2,10 +2,6 @@ width: 600px; // height: 360px; - .overlay { - transform: translate(-20px, -20px); - } - .select { text-align: justify; text-align-last: end @@ -33,12 +29,7 @@ } .share-copy-link { - display: inline; border-radius: 4px; - border: solid gray 1px; - font-size: x-small; - background: #E8E8E8; - color: black; margin-top: -15px; margin-bottom: 15px; width: fit-content; @@ -50,13 +41,22 @@ display: flex; margin-bottom: 20px; align-items: center; - height: 36; + max-height: 28; .user-search { + max-height: 30; width: 90%; + display: inline-flex; + flex-direction: row; + border: 2px solid; + border-radius: 4px; + border-color: inherit; + background: white; + color: black; input { - height: 30; + height: 20px; + width: 100px; } } @@ -68,14 +68,12 @@ text-align-last: end; font-size: 13px; min-width: 90px; - height: 36; + height: 28; margin-left: 2px; } .share-button { - height: 36; margin-left: 3%; - background-color: black; } } @@ -131,6 +129,7 @@ display: flex; top:0; flex-direction: column; + border-radius: 4px; .user-sort { text-align: left; @@ -147,11 +146,12 @@ .groups-list, .users-list { font-style: italic; - background: #e8e8e8; - border: 2px solid gray; + border: 2px solid; padding-left: 10px; padding-right: 10px; width: 97%; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; overflow-y: scroll; overflow-x: hidden; text-align: left; @@ -160,7 +160,6 @@ align-items: center; text-align: center; justify-content: center; - color: black; height: 250px; margin: 0 2; @@ -175,7 +174,6 @@ outline: none; border-radius: 5px; border: 0px; - color: #fcfbf7; text-transform: none; letter-spacing: 2px; font-size: 75%; @@ -194,11 +192,10 @@ font-size: 20px; text-align: left; margin: 0 0 20px 0; - color: black; } .hr-substitute { - border: solid black 0.5px; + border: solid 0.5px; margin-top: 20px; } @@ -223,11 +220,12 @@ height: 25px; padding-left: 2; width: 97%; + border-radius: 4px; margin-top: 10px; margin-left: -8px; font-size: 14; margin-bottom: -4; - border: 2px solid gray; + border: 2px solid; border-bottom: none; align-items: center; display: flex; @@ -237,11 +235,12 @@ height: 25px; padding-left: 2; width: 97%; + border-radius: 4px; margin-top: 10px; - margin-left: -.5px; + margin-left: -8px; font-size: 14; margin-bottom: -4; - border: 2px solid gray; + border: 2px solid; border-bottom: none; align-items: center; display: flex; @@ -264,7 +263,7 @@ font-weight: normal; padding: 3px; - border-bottom: 0.5px solid grey; + border-bottom: 0.5px solid; .group-info { cursor: pointer; @@ -368,35 +367,35 @@ margin-top: 20px; } - .link-container { - display: flex; - flex-direction: row; - margin-bottom: 10px; - margin-left: auto; - margin-right: auto; - - .link-box, - .copy { - padding: 10px; - border-radius: 10px; - padding: 10px; - border: solid black 1px; - } - - .link-box { - background: white; - color: blue; - text-decoration: underline; - } - - .copy { - margin-left: 20px; - cursor: alias; - border-radius: 50%; - width: 42px; - height: 42px; - transition: 1.5s all ease; - padding-top: 12px; - } - } + // .link-container { + // display: flex; + // flex-direction: row; + // margin-bottom: 10px; + // margin-left: auto; + // margin-right: auto; + + // .link-box, + // .copy { + // padding: 10px; + // border-radius: 10px; + // padding: 10px; + // border: solid black 1px; + // } + + // .link-box { + // background: white; + // color: blue; + // text-decoration: underline; + // } + + // .copy { + // margin-left: 20px; + // cursor: alias; + // border-radius: 50%; + // width: 42px; + // height: 42px; + // transition: 1.5s all ease; + // padding-top: 12px; + // } + // } }
\ No newline at end of file diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 33c9992d0..cadcb1f8a 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Colors } from 'browndash-components'; +import { Button, IconButton, Size, Type } from 'browndash-components'; import { concat, intersection } from 'lodash'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -80,7 +80,7 @@ export class SharingManager extends React.Component<{}> { @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component) @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component) private populating: boolean = false; // whether the list of users is populating or not - @observable private overrideNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is override + @observable private upgradeNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is upgrade all @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not @observable private _buttonDown = false; @@ -97,7 +97,7 @@ export class SharingManager extends React.Component<{}> { DictationOverlay.Instance.hasActiveModal = true; this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Augment; - this.overrideNested = true; + this.upgradeNested = true; }); }; @@ -162,7 +162,7 @@ export class SharingManager extends React.Component<{}> { const acl = `acl-${normalizeEmail(user.email)}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { - distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined); if (permission !== SharingPermissions.None) { Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); @@ -180,7 +180,7 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { - distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined); if (group instanceof Doc) { Doc.AddDocToList(group, 'docsShared', doc); @@ -525,14 +525,10 @@ export class SharingManager extends React.Component<{}> { const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : ( - <div key={groupKey} className={'container'}> + <div key={groupKey} className={'container'} style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}> <div className={'padding'}>{StrCast(group.title)}</div> - {group instanceof Doc ? ( - <div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}> - <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> - </div> - ) : null} + {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null} <div className={'edit-actions'}> {admin || this.myDocAcls ? ( <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}> @@ -551,34 +547,28 @@ export class SharingManager extends React.Component<{}> { return ( <div className="sharing-interface"> {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null} - <div className="sharing-contents" + <div + className="sharing-contents" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), - color: StrCast(Doc.UserDoc().userColor) - }} - > - <p className="share-title"> - <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')}> - <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} /> + color: StrCast(Doc.UserDoc().userColor), + }}> + <p className="share-title" style={{ color: StrCast(Doc.UserDoc().userColor) }}> + <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}> + <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} /> </div> <b>Share </b> {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} </p> - <button - className="share-copy-link" - style={{ background: this._buttonDown ? Colors.LIGHT_BLUE : undefined }} - onPointerDown={action(e => (this._buttonDown = true))} - onPointerUp={action(e => (this._buttonDown = false))} - onClick={this.copyURL}> - <FontAwesomeIcon title="Copy Public URL" icon="copy" size="sm" /> - Copy Public URL - </button> - <div className="close-button" onClick={this.close}> - <FontAwesomeIcon icon="times" color="black" size="lg" /> + <div className="share-copy-link"> + <Button type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} /> + </div> + <div className="close-button"> + <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} /> </div> {admin ? ( <div className="share-container"> - <div className="share-setup"> + <div className="share-setup" style={{ border: StrCast(Doc.UserDoc().userColor) }}> <Select className="user-search" placeholder="Enter user or group name..." @@ -590,9 +580,24 @@ export class SharingManager extends React.Component<{}> { onChange={this.handleUsersChange} value={this.selectedUsers} styles={{ + control: () => ({ + display: 'inline-flex', + width: '100%', + }), indicatorSeparator: () => ({ + display: 'inline-flex', visibility: 'hidden', }), + indicatorsContainer: () => ({ + display: 'inline-flex', + textDecorationColor: 'black', + }), + valueContainer: () => ({ + display: 'inline-flex', + fontStyle: StrCast(Doc.UserDoc().userColor), + color: StrCast(Doc.UserDoc().userColor), + width: '100%', + }), }} /> <div className="permissions-select"> @@ -600,9 +605,9 @@ export class SharingManager extends React.Component<{}> { {this.sharingOptions(true)} </select> </div> - <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> - Share - </button> + <div className="share-button"> + <Button text={'SHARE'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.share} /> + </div> </div> <div className="sort-checkboxes"> <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label> @@ -612,7 +617,7 @@ export class SharingManager extends React.Component<{}> { <div className="acl-container"> {Doc.noviceMode ? null : ( <div className="layoutDoc-acls"> - <input type="checkbox" onChange={action(() => (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} /> <label>Override Nested </label> + <input type="checkbox" onChange={action(() => (this.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> <label>Upgrade Nested </label> <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label> </div> )} @@ -627,12 +632,16 @@ export class SharingManager extends React.Component<{}> { </div> </div> )} - <div className="main-container"> + <div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}> <div className={'individual-container'}> <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> <div className="title-individual"> - Individuals - <FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} size="xs" /> + Individuals + <IconButton + icon={<FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} />} + size={Size.XSMALL} + color={StrCast(Doc.UserDoc().userColor)} + /> </div> </div> <div className="users-list">{userListContents}</div> @@ -640,12 +649,13 @@ export class SharingManager extends React.Component<{}> { <div className={'group-container'}> <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> <div className="title-group"> - Groups - <div className="group-info" onClick={action(() => GroupManager.Instance?.open())}> - <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> - </div> - - <FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} size="xs" /> + Groups + <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} /> + <IconButton + icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />} + size={Size.XSMALL} + color={StrCast(Doc.UserDoc().userColor)} + /> </div> </div> <div className={'groups-list'}>{groupListContents}</div> @@ -657,13 +667,6 @@ export class SharingManager extends React.Component<{}> { } render() { - return <MainViewModal - contents={this.sharingInterface} - isDisplayed={this.isOpen} - interactive={true} - dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} - overlayDisplayedOpacity={this.overlayOpacity} - closeOnExternalClick={this.close} - />; + return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />; } } diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 1fffb3dbc..8412a9aae 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -192,11 +192,11 @@ export class ContextMenu extends React.Component { } return this.filteredItems.map((value, index) => Array.isArray(value) ? ( - <div className="contextMenu-group" + <div + className="contextMenu-group" style={{ - background: StrCast(Doc.UserDoc().userVariantColor) - }} - > + background: StrCast(Doc.UserDoc().userVariantColor), + }}> <div className="contextMenu-description">{value.join(' -> ')}</div> </div> ) : ( @@ -219,17 +219,18 @@ export class ContextMenu extends React.Component { this._height = Number(getComputedStyle(r).height.replace('px', '')); } })} - style={{ - left: this.pageX, ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }), + style={{ + left: this.pageX, + ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }), background: StrCast(Doc.UserDoc().userBackgroundColor), - color: StrCast(Doc.UserDoc().userColor) + color: StrCast(Doc.UserDoc().userColor), }}> {!this.itemsNeedSearch ? null : ( <span className={'search-icon'}> <span className="icon-background"> <FontAwesomeIcon icon="search" size="lg" /> </span> - <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus /> + <input style={{ color: 'black' }} className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus /> </span> )} {this.menuItems} diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 1a5781df0..ae55c8ebf 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -199,7 +199,7 @@ export class DashboardView extends React.Component { <div className="dashboard-container" key={dashboard[Id]} - style={{ background: this.isUnviewedSharedDashboard(dashboard) && this.selectedDashboardGroup === DashboardGroup.SharedDashboards ? 'green' : shared ? 'blue' : '' }} + style={{ background: this.isUnviewedSharedDashboard(dashboard) && this.selectedDashboardGroup === DashboardGroup.SharedDashboards ? '#6CB982' : shared ? variant : '' }} onContextMenu={e => this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> <img diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index a41fc8ded..8fe5c2e01 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -145,7 +145,7 @@ 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); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3522830e5..956dac555 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -831,7 +831,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P {shareSymbolIcon + ' ' + shareMode} - {!Doc.noviceMode ? ( + {/* {!Doc.noviceMode ? ( <div className="checkbox"> <div className="checkbox-box"> <input type="checkbox" checked={this.showLayoutAcl} onChange={action(() => (this.showLayoutAcl = !this.showLayoutAcl))} /> @@ -839,7 +839,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P <div className="checkbox-text"> Layout </div> </div> ) : null} - + */} </div> </div> ) : ( @@ -911,7 +911,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/MainView.scss b/src/client/views/MainView.scss index 0c377730e..b3faff442 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -47,6 +47,10 @@ h1, align-items: center; justify-content: space-between; gap: 10px; + background: rgb(0, 0, 0); + border-radius: 8px; + padding-left: 5px; + padding-right: 5px; } .mainView-snapLines { @@ -190,10 +194,10 @@ h1, left: 0; position: absolute; z-index: 2; - background-color: linen;//$light-gray; + background-color: linen; //$light-gray; .editable-title { - background-color: linen;//$light-gray; + background-color: linen; //$light-gray; } } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5ab8a2f55..dc85fdfae 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -215,7 +215,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); } @@ -534,7 +534,7 @@ export class MainView extends React.Component { // 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) => { @@ -749,8 +749,7 @@ export class MainView extends React.Component { @computed get leftMenuPanel() { return ( - <div key="menu" className="mainView-leftMenuPanel" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), - display: LightboxView.LightboxDoc ? 'none' : undefined }}> + <div key="menu" className="mainView-leftMenuPanel" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), display: LightboxView.LightboxDoc ? 'none' : undefined }}> <DocumentView Document={Doc.MyLeftSidebarMenu} DataDoc={undefined} @@ -805,14 +804,17 @@ export class MainView extends React.Component { {this._hideUI ? null : this.leftMenuPanel} <div key="inner" className={`mainView-innerContent${this.colorScheme}`}> {this.flyout} - <div className="mainView-libraryHandle" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} onPointerDown={this.onFlyoutPointerDown}> + <div + className="mainView-libraryHandle" + style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} + onPointerDown={this.onFlyoutPointerDown}> <FontAwesomeIcon icon="chevron-left" color={StrCast(Doc.UserDoc().userColor)} style={{ opacity: '50%' }} size="sm" /> </div> <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}> {this.dockingContent} {this._hideUI ? null : ( - <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1, background : 'linen' }}> + <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1, background: 'linen' }}> <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" /> </div> )} @@ -879,7 +881,7 @@ export class MainView extends React.Component { @computed get docButtons() { return !Doc.MyDockedBtns ? null : ( - <div className="mainView-docButtons" ref={this._docBtnRef}> + <div className="mainView-docButtons" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }} ref={this._docBtnRef}> <CollectionLinearView Document={Doc.MyDockedBtns} DataDoc={undefined} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 6105cc1b5..2e3668268 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -56,24 +56,25 @@ export class PropertiesButtons extends React.Component<{}, {}> { propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => { const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedDoc; const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true); - return !targetDoc ? null : <Toggle - toggleStatus={BoolCast(targetDoc[property])} - text={label(targetDoc?.[property])} - color={StrCast(Doc.UserDoc().userColor)} - icon={icon(targetDoc?.[property] as any)} - iconPlacement={'left'} - align={'flex-start'} - fillWidth={true} - toggleType={ToggleType.BUTTON} - onClick={undoable(() => { - if (SelectionManager.Views().length > 1) { - SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property)); - } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property); - }, property)} - /> + return !targetDoc ? null : ( + <Toggle + toggleStatus={BoolCast(targetDoc[property])} + text={label(targetDoc?.[property])} + color={StrCast(Doc.UserDoc().userColor)} + icon={icon(targetDoc?.[property] as any)} + iconPlacement={'left'} + align={'flex-start'} + fillWidth={true} + toggleType={ToggleType.BUTTON} + onClick={undoable(() => { + if (SelectionManager.Views().length > 1) { + SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property)); + } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property); + }, property)} + /> + ); }; - // this implments a container pattern by marking the targetDoc (collection) as a lightbox // that always fits its contents to its container and that hides all other documents when // a link is followed that targets a 'lightbox' destination @@ -110,7 +111,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { (dv, doc) => { const tdoc = dv?.rootDoc || doc; const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : ''; - tdoc._layout_showTitle = newtitle; + tdoc._layout_showTitle = newtitle ? newtitle : undefined; } ); } @@ -148,7 +149,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get clustersButton() { return this.propertyToggleBtn( - on => (on ?'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'), + on => (on ? 'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'), '_freeform_useClusters', on => `${on ? 'Hide' : 'Show'} clusters`, on => <FaBraille /> @@ -163,12 +164,13 @@ export class PropertiesButtons extends React.Component<{}, {}> { ); } - @computed get forceActiveButton() { //select text + @computed get forceActiveButton() { + //select text return this.propertyToggleBtn( - on => on ? 'INACTIVE INTERACTION' : 'ACTIVE INTERACTION', + on => (on ? 'INACTIVE INTERACTION' : 'ACTIVE INTERACTION'), '_forceActive', on => `${on ? 'Select to activate' : 'Contents always active'} `, - on => <MdTouchApp/> // 'eye' + on => <MdTouchApp /> // 'eye' ); } @@ -218,7 +220,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @computed get captionButton() { - return this.propertyToggleBtn( //DEVELOPER + return this.propertyToggleBtn( + //DEVELOPER on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), //'Caption', '_layout_showCaption', on => `${on ? 'Hide' : 'Show'} caption footer`, @@ -227,17 +230,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { ); } - @computed get chromeButton() { // developer -- removing UI decoration + @computed get chromeButton() { + // developer -- removing UI decoration return this.propertyToggleBtn( - on => on ? 'ENABLE UI CONTROLS' : 'DISABLE UI CONTROLS', + on => (on ? 'ENABLE UI CONTROLS' : 'DISABLE UI CONTROLS'), '_chromeHidden', on => `${on ? 'Show' : 'Hide'} editing UI`, - on => on? <TbEditCircle/> : <TbEditCircleOff/> , // 'edit', + on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit', (dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden) ); } - @computed get layout_autoHeightButton() { // store previous dimensions to store old values + @computed get layout_autoHeightButton() { + // store previous dimensions to store old values return this.propertyToggleBtn( on => 'Auto\xA0Size', '_layout_autoHeight', @@ -251,7 +256,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { on => (on ? 'HIDE GRID' : 'DISPLAY GRID'), '_freeform_backgroundGrid', on => `Display background grid in collection`, - on => (on ? <MdGridOff /> :<MdGridOn /> ) //'border-all' + on => (on ? <MdGridOff /> : <MdGridOn />) //'border-all' ); } @@ -288,13 +293,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { // } // ); // } - @computed get snapButton() { // THESE ARE NOT COMING + @computed get snapButton() { + // THESE ARE NOT COMING return this.propertyToggleBtn( on => (on ? 'HIDE SNAP LINES' : 'SHOW SNAP LINES'), 'freeform_snapLines', on => `Display snapping lines when objects are dragged`, on => <TfiBarChart />, //'th', - undefined, + undefined ); } @@ -346,11 +352,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { const followLoc = this.selectedDoc._followLinkLocation; const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); - if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace' - else if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight' - else if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal' - else if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail' - else return 'nothing' + if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace'; + else if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight'; + else if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal'; + else if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail'; + else return 'nothing'; } @computed @@ -362,22 +368,20 @@ export class PropertiesButtons extends React.Component<{}, {}> { ['linkInPlace', 'Open Link in Lightbox'], ['linkOnRight', 'Open Link on Right'], ]; - + const items: IListItemProps[] = buttonList.map(value => { - return ( - { - text: value[1], - val: value[1], - } - ); + return { + text: value[1], + val: value[1], + }; }); - console.log("click val: ", this.onClickVal) + console.log('click val: ', this.onClickVal); return !this.selectedDoc ? null : ( <Dropdown - tooltip={'Choose onClick behavior'} + tooltip={'Choose onClick behavior'} items={items} selectedVal={this.onClickVal} - setSelectedVal={(val) => this.handleOptionChange(val as string)} + setSelectedVal={val => this.handleOptionChange(val as string)} title={'Choose onClick behaviour'} color={StrCast(Doc.UserDoc().userColor)} dropdownType={DropdownType.SELECT} diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 060b506e3..b79486167 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -42,16 +42,6 @@ padding: 0; margin-top: -5; } - - .propertiesView-acls-checkbox { - margin-top: -15px; - margin-bottom: -10px; - - .propertiesView-acls-checkbox-text { - display: inline; - font-size: 9px; - } - } } .change-buttons { @@ -68,14 +58,19 @@ } } + .propertiesView-acls-checkbox { + float: right; + margin-left: 50px; + } + .propertiesView-shareDropDown{ margin-right: 10px; min-width: 65px; & .propertiesView-shareDropDownNone{ - height: 16px; padding: 0px; padding-left: 3px; + padding-right: 3px; background: grey; color: rgb(71, 71, 71); border-radius: 6px; @@ -83,18 +78,18 @@ } & .propertiesView-shareDropDownEdit, .propertiesView-shareDropDownAdmin{ - height: 16px; padding: 0px; padding-left: 3px; + padding-right: 3px; background: rgb(254, 254, 199); color: rgb(75, 75, 5); border-radius: 6px; border: 1px solid rgb(75, 75, 5); } & .propertiesView-shareDropDownAugment{ - height: 16px; padding: 0px; padding-left: 3px; + padding-right: 3px; background: rgb(208, 255, 208); color:rgb(19, 80, 19); border-radius: 6px; @@ -102,18 +97,18 @@ } & .propertiesView-shareDropDownView{ - height: 16px; padding: 0px; padding-left: 3px; + padding-right: 3px; background: rgb(213, 213, 255); color: rgb(25, 25, 101); border-radius: 6px; border: 1px solid rgb(25, 25, 101); } & .propertiesView-shareDropDownNot-Shared{ - height: 16px; padding: 0px; padding-left: 3px; + padding-right: 3px; background: rgb(255, 207, 207); color: rgb(138, 47, 47); border-radius: 6px; @@ -177,19 +172,7 @@ } .expansion-button { - margin-left: -15px; - margin-right: 20px; - - .expansion-button-icon { - width: 11px; - height: 11px; - color: black; - - &:hover { - color: rgb(131, 131, 131); - cursor: pointer; - } - } + margin-right: 10px; } .propertiesView-sharingTable { @@ -238,6 +221,11 @@ } } + .propertiesView-permissions-select { + background: inherit; + border: none; + } + .propertiesView-field { display: flex; font-size: 7px; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index a6a99b3cc..27b9c3c7a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -3,7 +3,7 @@ import { IconLookup } from '@fortawesome/fontawesome-svg-core'; import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@material-ui/core'; -import { Button, Colors, EditableText, NumberInput, Size, Slider, Type } from 'browndash-components'; +import { Button, Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components'; import { concat } from 'lodash'; import { Lambda, action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -367,17 +367,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ @computed get expansionIcon() { return ( - <Tooltip title={<div className="dash-tooltip">Show more permissions</div>}> - <div - className="expansion-button" - onPointerDown={() => { + <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); } - }}> - <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" /> - </div> - </Tooltip> + })} + /> + </div> ); } @@ -417,7 +418,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div> <div className={'propertiesView-shareDropDown'}> <div className={`propertiesView-shareDropDown${permission}`}> - <div className="propertiesView-shareDropDown">{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> @@ -449,6 +450,7 @@ 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 @@ -512,28 +514,29 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } }); - // public permission - const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']); + // guest permission + const guestPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']); return ( <div> - <br /> - <div className="propertiesView-sharingTable">{<div> {individualTableEntries}</div>}</div> + <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> {individualTableEntries}</div>} + </div> {groupTableEntries.length > 0 ? ( <div> <div> - {' '} - <br></br> Groups with Access to this Document{' '} + <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> {groupTableEntries}</div>} </div> - <div className="propertiesView-sharingTable">{<div> {groupTableEntries}</div>}</div> </div> ) : null} - Guest - <div>{this.colorACLDropDown('Guest', true, publicPermission!, true)}</div> - <div> - {' '} - <br></br> Individual Users with Access to this Document{' '} - </div> + <br></br> Guest + <div>{this.colorACLDropDown('Guest', showAdmin, guestPermission!, true)}</div> </div> ); } @@ -1145,26 +1148,17 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return <PropertiesSection title="Sharing & Permissions" content={<> - <div className="propertiesView-buttonContainer"> - {!Doc.noviceMode ? ( - <div className="propertiesView-buttonContainer"> - <div className="propertiesView-acls-checkbox"> - <div className="propertiesView-acls-checkbox-text"> Show / Contol Layout Permissions </div> - <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" color="white" size="1x" /> - </button> - </Tooltip> */} - </div> - ) : null} + {/* <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> + {/* </div> */} {this.sharingTable} </>} isOpen={this.openSharing} diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 6f88f6727..22beb19de 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -8,7 +8,7 @@ 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 +252,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 +270,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 +288,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 +313,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..5135cfb57 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: StrCast(Doc.UserDoc().userBackgroundColor), + // borderColor: StrCast(Doc.UserDoc().userColor) + }}> {this.contMenuButtons} {hardCodedButtons} </div> 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..a5c276125 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> ); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index d904749b1..1098b56c2 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -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,8 @@ 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)) + 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 +578,10 @@ export class TreeView extends React.Component<TreeViewProps> { ); } return ( - <div style={{ - color: color - }}> + <div + style={{ + color: color, + }}> {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( <div className={'treeView-sorting'}> <IconButton @@ -725,26 +723,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 +767,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 +792,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 +806,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 = () => { @@ -1014,12 +1005,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/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 38922cb24..2990e2159 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -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) @@ -875,13 +874,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // 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)) ? 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; }; @@ -903,8 +896,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }; 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 + contentPointerEvents = () => ((!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); @@ -1236,7 +1229,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const renderDoc = this.renderDoc({ borderRadius: this.borderRounding, outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px', - border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined, + border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined, boxShadow, clipPath: borderPath?.clipPath, }); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 3ec53beac..da1b89200 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -86,10 +86,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 +124,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 +204,69 @@ 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') + console.log('selected'); if (selected) { if (StrCast(selected.type) === DocumentType.COL) { text = StrCast(selected._type_collection); - console.log("collection selected", text) + console.log('collection selected', text); } else { - console.log("doc selected", selected.title); + 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); + 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 +277,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 +293,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { tooltip={tooltip} label={this.label} /> - ) + ); } @computed get toggleButton() { @@ -315,25 +302,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,28 +330,24 @@ 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> ); @@ -403,7 +385,8 @@ export class FontIconBox extends DocComponent<ButtonProps>() { 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}/> + <IconButton tooltip={tooltip} + color={color} icon={this.Icon(color)!} label={this.label}/> ); break; case ButtonType.TextButton: @@ -412,7 +395,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { ); 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}/> + <IconButton tooltip={tooltip} onPointerDown={() => onClickScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false })} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/> ); break; } 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/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/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0fd93868a..1319a236d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -26,6 +26,7 @@ import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); import { GPTPopup } from './GPTPopup/GPTPopup'; +import { InkingStroke } from '../InkingStroke'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; @@ -519,7 +520,8 @@ export class PDFViewer extends React.Component<IViewerProps> { childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none'; - return 'all'; + const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString; + return isInk ? 'visiblePainted' : 'all'; } return this.props.styleProvider?.(doc, props, property); }; @@ -537,7 +539,7 @@ export class PDFViewer extends React.Component<IViewerProps> { NativeWidth={returnZero} NativeHeight={returnZero} setContentView={emptyFunction} // override setContentView to do nothing - pointerEvents={SnappingManager.GetIsDragging() && this.props.isContentActive() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. + pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. childPointerEvents={this.props.isContentActive() !== false ? 'all' : 'none'} // but freeform children need to get events to allow text editing, etc renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 79f41fe9d..ec4252eb8 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -134,14 +134,16 @@ export class TopBar extends React.Component { @computed get topbarRight() { return ( <div className="topbar-right"> - <Button - text={GetEffectiveAcl(Doc.ActiveDashboard) === AclAdmin ? 'Share' : 'View Original'} - type={Type.TERT} - color={this.variantColor} - onClick={() => { - SharingManager.Instance.open(undefined, Doc.ActiveDashboard); - }} - /> + {Doc.ActiveDashboard ? + <Button + text={GetEffectiveAcl(Doc.ActiveDashboard) === AclAdmin ? 'Share' : 'View Original'} + type={Type.TERT} + color={this.variantColor} + onClick={() => { + 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" />} /> diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index c38ee8ac9..c1934451c 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -149,23 +149,15 @@ function registerAuthenticationRoutes(server: express.Express) { function registerCorsProxy(server: express.Express) { server.use('/corsProxy', async (req, res) => { - const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : ''; - let requrlraw = decodeURIComponent(req.url.substring(1)); - const qsplit = requrlraw.split('?q='); - const newqsplit = requrlraw.split('&q='); + //const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : ''; + let requrl = decodeURIComponent(req.url.substring(1)); + const qsplit = requrl.split('?q='); + const newqsplit = requrl.split('&q='); if (qsplit.length > 1 && newqsplit.length > 1) { const lastq = newqsplit[newqsplit.length - 1]; - requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1]; - } - const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw; - // cors weirdness here... - // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, - // then we redirect again to the cors referer and just add the relative path. - if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) { - res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl); - } else { - proxyServe(req, requrl, res); + requrl = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1]; } + proxyServe(req, requrl, res); }); } @@ -184,7 +176,7 @@ function proxyServe(req: any, requrl: string, response: any) { const htmlText = htmlInputText .toString('utf8') .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>') - .replace(/href="https?([^"]*)"/g, httpsToCors) + // .replace(/href="https?([^"]*)"/g, httpsToCors) .replace(/data-srcset="[^"]*"/g, '') .replace(/srcset="[^"]*"/g, '') .replace(/target="_blank"/g, ''); |