diff options
author | eperelm2 <emily_perelman@brown.edu> | 2023-07-18 11:40:12 -0400 |
---|---|---|
committer | eperelm2 <emily_perelman@brown.edu> | 2023-07-18 11:40:12 -0400 |
commit | 5100a643fb0d98b6dd738e7024f4fe15f56ba1a8 (patch) | |
tree | 92fa39d2d5cc8f584e3346c8fe0efaa5b184a9e5 /src/client/views/PropertiesView.tsx | |
parent | c9779f31d9ce2363e61c3c9fa7e3446203622dde (diff) | |
parent | 16a1b7de3ec26187b3a426eb037a5e4f4b9fcc55 (diff) |
Merge branch 'master' into secondpropertiesmenu-emily
Diffstat (limited to 'src/client/views/PropertiesView.tsx')
-rw-r--r-- | src/client/views/PropertiesView.tsx | 1222 |
1 files changed, 577 insertions, 645 deletions
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 9f6c86502..31c03de4c 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1,51 +1,44 @@ import React = require('react'); import { IconLookup } from '@fortawesome/fontawesome-svg-core'; -import { faAnchor, faArrowRight, faListNumeric, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; +import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Checkbox, Icon, Tooltip } from '@material-ui/core'; -import { intersection } from 'lodash'; -import { action, computed, Lambda, observable } from 'mobx'; +import { Checkbox, Tooltip } from '@material-ui/core'; +import { Button, Colors, EditableText, NumberInput, Size, Slider, Type } from 'browndash-components'; +import { concat } from 'lodash'; +import { Lambda, action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, StrListCast } from '../../fields/Doc'; +import * as Icons from "react-icons/bs"; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" +import { GrCircleInformation } from 'react-icons/gr'; +import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; +import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc'; import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ComputedField } from '../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; -import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; +import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util'; import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; +import { GroupManager } from '../util/GroupManager'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { Transform } from '../util/Transform'; -import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; +import { UndoManager, undoBatch, undoable } from '../util/UndoManager'; import { EditableView } from './EditableView'; import { FilterPanel } from './FilterPanel'; -import { Colors } from './global/globalEnums'; import { InkStrokeProperties } from './InkStrokeProperties'; -import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; -import { KeyValueBox } from './nodes/KeyValueBox'; -import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; import { PropertiesButtons } from './PropertiesButtons'; import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector'; import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; +import { PropertiesSection } from './PropertiesSection'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; -import { RichTextField } from '../../fields/RichTextField'; -import { AiFillFileText } from "react-icons/ai" //* as Icons from "react-icons/ai" // -import * as Icons from "react-icons/bs" //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" -import { GrCircleInformation } from 'react-icons/gr' -import { CgBrowser} from "react-icons/cg" -import { ImageField, VideoField, WebField } from '../../fields/URLField'; -import { FaFileVideo } from 'react-icons/fa'; //* as Icons from "react-icons/fa"; // -import { IconButton } from 'browndash-components'; -import { IconBase } from 'react-icons'; -import { MdOutlineAddShoppingCart, MdOutlineMedicalServices } from 'react-icons/md'; -import { createPromiseCapability } from 'pdfjs-dist'; +import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; +import { KeyValueBox } from './nodes/KeyValueBox'; +import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -336,26 +329,22 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @undoBatch changePermissions = (e: any, user: string) => { const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc! : DocCast(doc)[DocData])); - SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); + SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls); }; /** * @returns the options for the permissions dropdown. */ - getPermissionsSelect(user: string, permission: string) { - const dropdownValues: string[] = Object.values(SharingPermissions); + getPermissionsSelect(user: string, permission: string, showGuestOptions: boolean) { + const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions); if (permission === '-multiple-') dropdownValues.unshift(permission); - if (user !== 'Override') dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1); return ( - <select className="permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}> - {dropdownValues - .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)) - .map(permission => ( - <option key={permission} value={permission}> - {' '} - {permission}{' '} - </option> - ))} + <select className="propertiesView-permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}> + {dropdownValues.map(permission => ( + <option className="propertiesView-permisssions-select" key={permission} value={permission}> + {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)} + </option> + ))} </select> ); } @@ -367,7 +356,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return ( <Tooltip title={<div className="dash-tooltip">Notify with message</div>}> <div className="notify-button"> - <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" /> + <FontAwesomeIcon className="notify-button-icon" icon="bell" size="sm" /> </div> </Tooltip> ); @@ -396,6 +385,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { * @returns a row of the permissions panel */ sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { + if (name == Doc.CurrentUserEmail) { + name = 'Me'; + } return ( <div className="propertiesView-sharingTable-item" @@ -409,58 +401,141 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </div> {/* {name !== "Me" ? this.notifyIcon : null} */} <div className="propertiesView-sharingTable-item-permission"> - {admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : permission} - {permission === 'Owner' || showExpansionIcon ? this.expansionIcon : null} + {this.colorACLDropDown(name, admin, permission, false)} + {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null} + </div> + </div> + ); + } + + /** + * @returns a colored dropdown bar reflective of the permission + */ + colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) { + var shareImage = ReverseHierarchyMap.get(permission)?.image; + return ( + <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> </div> </div> ); } /** + * Sorting algorithm to sort users. + */ + sortUsers = (u1: String, u2: String) => { + return u1 > u2 ? -1 : u1 === u2 ? 0 : 1; + }; + + /** + * Sorting algorithm to sort groups. + */ + sortGroups = (group1: Doc, group2: Doc) => { + const g1 = StrCast(group1.title); + const g2 = StrCast(group2.title); + return g1 > g2 ? -1 : g1 === g2 ? 0 : 1; + }; + + /** * @returns the sharing and permissions panel. */ @computed get sharingTable() { // all selected docs - const docs = - SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutDocAcls ? this.selectedDoc : this.dataDoc!] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DocData])); - + const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.rootDoc); const target = docs[0]; - // tslint:disable-next-line: no-unnecessary-callback-wrapper - const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); - const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); + const showAdmin = GetEffectiveAcl(target) == AclAdmin; + const individualTableEntries = []; + const usersAdded: string[] = []; // all shared users being added - organized by denormalized email + + const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc); + // adds each user to usersAdded + SharingManager.Instance.users.forEach(eachUser => { + var userOnDoc = true; + if (seldoc) { + if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) { + userOnDoc = false; + } + } + if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) { + usersAdded.push(eachUser.user.email); + } + }); - // users in common between all docs - const commonKeys: string[] = intersection(...docs.map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl]).filter(key => key !== 'acl-Me'))); + // sorts and then adds each user to the table + usersAdded.sort(this.sortUsers); + usersAdded.map(userEmail => { + const userKey = `acl-${normalizeEmail(userEmail)}`; + var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true); + var permission = StrCast(aclField); + individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user + }); - const tableEntries = []; + // adds current user + var userEmail = Doc.CurrentUserEmail; + if (userEmail == 'guest') userEmail = 'Guest'; + const userKey = `acl-${normalizeEmail(userEmail)}`; + if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) { + var permission; + if (this.layoutDocAcls) { + if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; + else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]); + else permission = StrCast(Doc.GetProto(target)?.[userKey]); + } else permission = StrCast(target[userKey]); + individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user + } - // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { - if (commonKeys.length) { - for (const key of commonKeys) { - const name = denormalizeEmail(key.substring(4)); - const uniform = docs.every(doc => doc?.[DocAcl]?.[key] === docs[0]?.[DocAcl]?.[key]); - if (name !== Doc.CurrentUserEmail && name !== target.author && name !== 'Public' && name !== 'Override' /* && sidebarUsersDisplayed![name] !== false*/) { - tableEntries.push(this.sharingItem(name, showAdmin, uniform ? HierarchyMapping.get(target[DocAcl][key])!.name : '-multiple-')); + // shift owner to top + individualTableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false); + + // adds groups + const groupTableEntries: JSX.Element[] = []; + const groupList = GroupManager.Instance?.allGroups || []; + groupList.sort(this.sortGroups); + groupList.map(group => { + if (group.title != 'Guest' && this.selectedDoc) { + const groupKey = 'acl-' + normalizeEmail(StrCast(group.title)); + if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) { + var permission; + if (this.layoutDocAcls) { + if (target[DocAcl][groupKey]) { + permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name; + } else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]); + else permission = StrCast(Doc.GetProto(target)?.[groupKey]); + } else permission = StrCast(target[groupKey]); + groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false)); } } - } + }); - const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); - // shifts the current user, owner, public to the top of the doc. - // tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); - if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner')); - tableEntries.unshift(this.sharingItem('Public', showAdmin, StrCast(docs.filter(doc => doc).every(doc => doc['acl-Public'] === target['acl-Public']) ? target['acl-Public'] || SharingPermissions.None : '-multiple-'))); - tableEntries.unshift( - this.sharingItem( - 'Me', - showAdmin, - docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? 'Owner' : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-', - !ownerSame - ) - ); + // public permission + const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']); - return <div className="propertiesView-sharingTable">{tableEntries}</div>; + return ( + <div> + <br /> + <div className="propertiesView-sharingTable">{<div> {individualTableEntries}</div>}</div> + {groupTableEntries.length > 0 ? ( + <div> + <div> + {' '} + <br></br> Groups with Access to this Document{' '} + </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> + </div> + ); } @computed get fieldsCheckbox() { @@ -471,19 +546,46 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @action toggleCheckbox = () => (this.layoutFields = !this.layoutFields); + @computed get color() { + return StrCast(Doc.UserDoc().userColor); + } + + @computed get backgroundColor() { + return StrCast(Doc.UserDoc().userBackgroundColor); + } + + @computed get variantColor() { + return StrCast(Doc.UserDoc().userVariantColor); + } + @computed get editableTitle() { const titles = new Set<string>(); - SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title); + SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); return ( - <div> - <div className="propertiesView-wordTitle">Title</div> - <div className="editable-title"> - <EditableView key="editableView" contents={title} height={25} fontSize={14} GetValue={() => title} SetValue={this.setTitle} /> - </div> - </div> - + <EditableText + val={title} + setVal={this.setTitle} + color={this.color} + type={Type.SEC} + formLabel={"Title"} + fillWidth + /> + ); + } + + @computed get type() { + const type = Utils.cleanDocumentType(StrCast(this.selectedDoc?.type) as DocumentType); + return ( + <Button + formLabel={"Type"} + text={type} + type={Type.SEC} + color={this.color} + align='flex-start' + fillWidth + /> ); } @@ -531,16 +633,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @undoBatch @action - setTitle = (value: string) => { + setTitle = (value: string | number) => { + console.log(value) if (SelectionManager.Views().length > 1) { SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, 'title', value, true)); - return true; } else if (this.dataDoc) { if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true); - else KeyValueBox.SetField(this.dataDoc, 'title', value, true); - return true; + else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true); } - return false; }; @undoBatch @@ -598,7 +698,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { className="inking-button-points" style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }} onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}> - <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" /> + <FontAwesomeIcon icon="bezier-curve" size="lg" /> </div> </Tooltip> </div> @@ -617,10 +717,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <input className="inputBox-input" type="text" value={value} onChange={e => setter(e.target.value)} onKeyPress={e => e.stopPropagation()} /> <div className="inputBox-button"> <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> - <FontAwesomeIcon icon="caret-up" color="white" size="sm" /> + <FontAwesomeIcon icon="caret-up" size="sm" /> </div> <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> - <FontAwesomeIcon icon="caret-down" color="white" size="sm" /> + <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> </div> @@ -874,16 +974,28 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <input className="inputBox-input" type="text" value={value} onChange={e => setter(e.target.value)} /> <div className="inputBox-button"> <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}> - <FontAwesomeIcon icon="caret-up" color="white" size="sm" /> + <FontAwesomeIcon icon="caret-up" size="sm" /> </div> <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}> - <FontAwesomeIcon icon="caret-down" color="white" size="sm" /> + <FontAwesomeIcon icon="caret-down" size="sm" /> </div> </div> </div> ); }; + @action + onDoubleClick = () => { + this.openContexts = false; + this.openLinks = false; + this.openOptions = false; + this.openTransform = false; + this.openFields = false; + this.openSharing = false; + this.openLayout = false; + this.openFilters = false; + } + @computed get widthAndDash() { return ( <div className="widthAndDash"> @@ -963,111 +1075,115 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { ); } + getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any) => { + return <div> + <NumberInput + formLabel={label} + formLabelPlacement={'left'} + type={Type.SEC} + unit={unit} + fillWidth + color={this.color} + number={number} + setNumber={setNumber} + min={min} + max={max} + /> + <Slider + multithumb={false} + color={this.color} + size={Size.XSMALL} + min={min} + max={max} + unit={unit} + number={number} + setNumber={setNumber} + fillWidth + /> + </div> + } + @computed get transformEditor() { return ( <div className="transform-editor"> {this.isInk ? this.controlPointsButton : null} - {this.hgtInput} - {this.XpsInput} + {this.getNumber( + "Height", + " px", + 0, + 1000, + Number(this.shapeHgt), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height') + )} + {this.getNumber( + "Width", + " px", + 0, + 1000, + Number(this.shapeWid), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width') + )} + {this.getNumber( + "X Coordinate", + " px", + -2000, + 2000, + Number(this.shapeXps), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord') + )} + {this.getNumber( + "Y Coordinate", + " px", + -2000, + 2000, + Number(this.shapeYps), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord') + )} </div> ); } @computed get optionsSubMenu() { - let isDouble = false; - - return ( - <div className="propertiesView-settings" onPointerEnter={action(() => (this.inOptions = true))} onPointerLeave={action(() => (this.inOptions = false))}> - <div className="propertiesView-settings-title" onClick ={action(() => { - if (!isDouble){ - this.openOptions = !this.openOptions - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = false; - this.openLinks = false; - this.openOptions = true; - this.openTransform = false; - this.openFields = false; - this.openSharing = false; - this.openLayout = false; - this.openFilters = false; - - - setTimeout(() => { - isDouble = false; - }, 300) - } - - )} - style={{ backgroundColor: this.openOptions ? 'black' : '' }}> - Options - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openOptions ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!this.openOptions ? null : ( - <div className="propertiesView-settings-content"> - <PropertiesButtons /> - </div> - )} - </div> - ); + return <PropertiesSection + title="Options" + content={<PropertiesButtons />} + inSection={this.inOptions} + isOpen={this.openOptions} + setInSection={(bool) => this.inOptions = bool} + setIsOpen={(bool) => this.openOptions = bool} + onDoubleClick={() => this.onDoubleClick()} + /> } @computed get sharingSubMenu() { - let isDouble:boolean = false; - - return ( - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" onClick ={action(() => { - if (!isDouble){ - this.openSharing = !this.openSharing - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = false; - this.openLinks = false; - this.openOptions = false; - this.openTransform = false; - this.openFields = false; - this.openSharing = true; - this.openLayout = false; - this.openFilters = false; - - - setTimeout(() => { - isDouble = false; - }, 300) - })} - style={{ backgroundColor: this.openSharing ? 'black' : '' }}> - Sharing {'&'} Permissions - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openSharing ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!this.openSharing ? null : ( - <div className="propertiesView-sharing-content"> + return <PropertiesSection + title="Sharing & Permissions" + content={<> + <div className="propertiesView-buttonContainer"> + {!Doc.noviceMode ? ( <div className="propertiesView-buttonContainer"> - {!Doc.noviceMode ? ( - <div className="propertiesView-acls-checkbox"> - <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> - <div className="propertiesView-acls-checkbox-text">Layout</div> - </div> - ) : null} - {/* <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 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> - {this.sharingTable} - </div> - )} - </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} + {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}> + <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}> + <FontAwesomeIcon icon="redo-alt" size="1x" /> + </button> + </Tooltip> */} + </div> + {this.sharingTable} + </>} + isOpen={this.openSharing} + setIsOpen={(bool) => this.openSharing = bool} + /> } /** @@ -1095,46 +1211,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { }; @computed get filtersSubMenu() { - let isDouble: boolean = false; - - return ( - <div className="propertiesView-filters"> - <div className="propertiesView-filters-title" onClick ={action(() => { - if (!isDouble){ - this.openFilters = !this.openFilters - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = false; - this.openLinks = false; - this.openOptions = false; - this.openTransform = false; - this.openFields = false; - this.openSharing = false; - this.openLayout = false; - this.openFilters = true; - - - setTimeout(() => { - isDouble = false; - }, 300) - } - - )} - style={{ backgroundColor: this.openFilters ? 'black' : '' }}> - Filters - <div className="propertiesView-filters-title-icon"> - <FontAwesomeIcon icon={this.openFilters ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!this.openFilters ? null : ( - <div className="propertiesView-filters-content" style={{ position: 'relative', height: 'auto' }}> - <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} /> - </div> - )} - </div> - ); + return <PropertiesSection + title="Filters" + content={<div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}> + <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} /> + </div>} + isOpen={this.openFilters} + setIsOpen={(bool) => this.openFilters = bool} + onDoubleClick={() => this.onDoubleClick()} + /> } @computed get inkSubMenu() { @@ -1142,217 +1227,66 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return ( <> - {!this.isInk ? null : ( - <div className="propertiesView-appearance"> - <div className="propertiesView-appearance-title" onPointerDown={action(() => (this.openAppearance = !this.openAppearance))} style={{ backgroundColor: this.openAppearance ? 'black' : '' }}> - Appearance - <div className="propertiesView-appearance-title-icon"> - <FontAwesomeIcon icon={this.openAppearance ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!this.openAppearance ? null : <div className="propertiesView-appearance-content">{this.appearanceEditor}</div>} - </div> - )} - - <div className="propertiesView-transform"> - <div className="propertiesView-transform-title" onClick ={action(() => { - if (!isDouble){ - this.openTransform = !this.openTransform - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = false; - this.openLinks = false; - this.openOptions = false; - this.openTransform = true; - this.openFields = false; - this.openSharing = false; - this.openLayout = false; - this.openFilters = false; - - - setTimeout(() => { - isDouble = false; - }, 300) - })} - style={{ backgroundColor: this.openTransform ? 'black' : '' }}> - Transform - <div className="propertiesView-transform-title-icon"> - <FontAwesomeIcon icon={this.openTransform ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {this.openTransform ? <div className="propertiesView-transform-content">{this.transformEditor}</div> : null} - </div> + <PropertiesSection + title="Appearance" + content={this.isInk ? this.appearanceEditor : null} + isOpen={this.openAppearance} + setIsOpen={(bool) => this.openAppearance = bool} + onDoubleClick={() => this.onDoubleClick()} + /> + <PropertiesSection + title="Transform" + content={this.transformEditor} + isOpen={this.openTransform} + setIsOpen={(bool) => this.openTransform = bool} + onDoubleClick={() => this.onDoubleClick()} + /> </> ); } @computed get fieldsSubMenu() { - let isDouble: boolean = false; - - return ( - <div className="propertiesView-fields"> - <div className="propertiesView-fields-title" onClick ={action(() => { - if (!isDouble){ - this.openFields = !this.openFields - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = false; - this.openLinks = false; - this.openOptions = false; - this.openTransform = false; - this.openFields = true; - this.openSharing = false; - this.openLayout = false; - this.openFilters = false; - - - setTimeout(() => { - isDouble = false; - }, 300) - } - - )} - style={{ backgroundColor: this.openFields ? 'black' : '' }}> - Fields {'&'} Tags - <div className="propertiesView-fields-title-icon"> - <FontAwesomeIcon icon={this.openFields ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!Doc.noviceMode && this.openFields ? ( - <div className="propertiesView-fields-checkbox"> - {this.fieldsCheckbox} - <div className="propertiesView-fields-checkbox-text">Embedding</div> - </div> - ) : null} - {!this.openFields ? null : <div className="propertiesView-fields-content">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>} - </div> - ); + return <PropertiesSection + title="Fields & Tags" + content={<div className="propertiesView-content fields">{ + Doc.noviceMode ? this.noviceFields : this.expandedField} + </div>} + isOpen={this.openFields} + setIsOpen={(bool) => this.openFields = bool} + onDoubleClick={() => this.onDoubleClick()} + /> } @computed get contextsSubMenu() { - let isDouble = false; - return ( - <div className="propertiesView-contexts"> - <div className="propertiesView-contexts-title" onClick ={action(() => { - if (!isDouble){ - this.openContexts = !this.openContexts - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = true; - this.openLinks = false; - this.openOptions = false; - this.openTransform = false; - this.openFields = false; - this.openSharing = false; - this.openLayout = false; - this.openFilters = false; - - - setTimeout(() => { - isDouble = false; - }, 300) - } - - )} - style={{ backgroundColor: this.openContexts ? 'black' : '' }}> - Other Contexts - <div className="propertiesView-contexts-title-icon"> - <FontAwesomeIcon icon={this.openContexts ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!this.openContexts ? null : this.contextCount > 0 ? <div className="propertiesView-contexts-content">{this.contexts}</div> : <div className="propertiesView-contexts-content">There are no other contexts.</div>} - </div> - ); + return <PropertiesSection + title="Other Contexts" + content={this.contexts} + isOpen={this.openContexts} + setIsOpen={(bool) => this.openContexts = bool} + onDoubleClick={() => this.onDoubleClick()} + /> } @computed get linksSubMenu() { -//onClick={action(() => (this.openLinks = !this.openLinks))} onDoubleClick={action(() => (this.openContexts = false, this.openOptions = false, this.openTransform = false, -//this.openFields = false, this.openSharing = false, this.openLayout = false, this.openFilters = false, this.openLinks = false ))} - - let isDouble = false; - - return ( - <div className="propertiesView-contexts"> - - <div className="propertiesView-contexts-title" onClick = {action(() => { - if (!isDouble){ - this.openLinks = !this.openLinks - } } )} - - onDoubleClick = {action(() => { - isDouble = true; - - this.openContexts = false; - this.openOptions = false; - this.openTransform = false; - this.openFields = false; - this.openSharing = false; - this.openLayout = false; - this.openFilters = false; - this.openLinks = true; - - setTimeout(() => { - isDouble = false; - }, 300) - } - - )} - style={{ backgroundColor: this.openLinks ? 'black' : '' }}> - Linked To - <div className="propertiesView-contexts-title-icon"> - <FontAwesomeIcon icon={this.openLinks ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {!this.openLinks ? null : this.linkCount > 0 ? <div className="propertiesView-contexts-content">{this.links}</div> : <div className="propertiesView-contexts-content">There are no current links.</div>} - </div> - ); + return <PropertiesSection + title="Linked To" + content={this.links} + isOpen={this.openLinks} + setIsOpen={(bool) => this.openLinks = bool} + onDoubleClick={() => this.onDoubleClick()} + /> } @computed get layoutSubMenu() { - let isDouble: boolean = false; - - return ( - <div className="propertiesView-layout"> - <div className="propertiesView-layout-title" onClick ={action(() => { - if (!isDouble){ - this.openLayout = !this.openLayout - } } )} - onDoubleClick={action(() => { - isDouble = true; - - this.openContexts = false; - this.openLinks = false; - this.openOptions = false; - this.openTransform = false; - this.openFields = false; - this.openSharing = false; - this.openLayout = true; - this.openFilters = false; - - - setTimeout(() => { - isDouble = false; - }, 300) - } - - )} - style={{ backgroundColor: this.openLayout ? 'black' : '' }}> - Layout - <div className="propertiesView-layout-title-icon"> - <FontAwesomeIcon icon={this.openLayout ? 'caret-down' : 'caret-right'} size="lg" color="white" /> - </div> - </div> - {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null} - </div> - ); + return <PropertiesSection + title="Layout" + content={this.layoutPreview} + isOpen={this.openLayout} + setIsOpen={(bool) => this.openLayout = bool} + onDoubleClick={() => this.onDoubleClick()} + /> } @computed get description() { @@ -1553,6 +1487,226 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { if (scale > 1) scale = 1; this.sourceAnchor && (this.sourceAnchor.followLinkZoomScale = scale); }; + + @computed get linkProperties() { + const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3)); + const targZoom = this.sourceAnchor?.followLinkZoom; + const indent = 30; + const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!); + + return <> + <div className="propertiesView-section" style={{ background: 'darkgray' }}> + <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> + <p>Relationship</p> + {this.editRelationship} + </div> + <div className="propertiesView-input" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> + <p>Description</p> + {this.editDescription} + </div> + <div className="propertiesView-input inline"> + <p>Show link</p> + <button + style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> + <p>Auto-move anchors</p> + <button + style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> + <p>Display arrow</p> + <button + style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + </div> + {!hasSelectedAnchor ? null : ( + <div className="propertiesView-section"> + <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}> + <p>Follow by</p> + <select onChange={e => this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}> + <option value={undefined}>Default</option> + <option value={OpenWhere.addLeft}>Opening in new left pane</option> + <option value={OpenWhere.addRight}>Opening in new right pane</option> + <option value={OpenWhere.replaceLeft}>Replacing left tab</option> + <option value={OpenWhere.replaceRight}>Replacing right tab</option> + <option value={OpenWhere.fullScreen}>Overlaying current tab</option> + <option value={OpenWhere.lightbox}>Opening in lightbox</option> + <option value={OpenWhere.add}>Opening in new tab</option> + <option value={OpenWhere.replace}>Replacing current tab</option> + <option value={OpenWhere.inParent}>Opening in same collection</option> + {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null} + </select> + </div> + <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}> + <p>Animation</p> + <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> + <option value="default">Default</option> + {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( + <option key={effect.toString()} value={effect.toString()}> + {effect.toString()} + </option> + ))} + </select> + <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}> + {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} + {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} + {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} + {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} + {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} + </div> + </div> + {PresBox.inputter( + '0.1', + '0.1', + '10', + NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, + true, + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), + indent + )}{' '} + <div + className={'slider-headers'} + style={{ + display: 'grid', + justifyContent: 'space-between', + width: `calc(100% - ${indent * 2}px)`, + marginLeft: indent, + marginRight: indent, + gridTemplateColumns: 'auto auto', + borderTop: 'solid', + }}> + <div className="slider-text">Fast</div> + <div className="slider-text">Slow</div> + </div>{' '} + <div className="propertiesView-input inline"> + <p>Play Target Audio</p> + <button + style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Zoom Text Selections</p> + <button + style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Toggle Follow to Outer Context</p> + <button + style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Toggle Target (Show/Hide)</p> + <button + style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Ease Transitions</p> + <button + style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Capture Offset to Target</p> + <button + style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => { + this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined); + this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined); + }} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Center Target (no zoom)</p> + <button + style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}> + <p>Zoom %</p> + <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}> + <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} /> + <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> + <FontAwesomeIcon icon={'caret-up'} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> + <FontAwesomeIcon icon={'caret-down'} /> + </div> + </div> + </div> + <button + style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} + <div + className={'slider-headers'} + style={{ + display: !targZoom ? 'none' : 'grid', + justifyContent: 'space-between', + width: `calc(100% - ${indent * 2}px)`, + marginLeft: indent, + marginRight: indent, + gridTemplateColumns: 'auto auto', + borderTop: 'solid', + }}> + <div className="slider-text">0%</div> + <div className="slider-text">100%</div> + </div>{' '} + </div> + )} + </> + } /** * Handles adding and removing members from the sharing panel @@ -1567,9 +1721,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { render() { const isNovice = Doc.noviceMode; - const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3)); - const targZoom = this.sourceAnchor?.followLinkZoom; - const indent = 30; const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!); if (!this.selectedDoc && !this.isPres) { return ( @@ -1585,9 +1736,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView" style={{ + background: StrCast(Doc.UserDoc().userBackgroundColor), + color: StrCast(Doc.UserDoc().userColor), width: this.props.width, minWidth: this.props.width, - //overflowY: this.scrolling ? "scroll" : "visible" }}> <div className = "propertiesView-propAndInfoGrouping"> <div className="propertiesView-title" style={{ width: this.props.width }}> @@ -1600,238 +1752,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-name">{this.editableTitle}</div> - <div className = "propertiesView-type"> {this.currentType} </div> - - + <div className="propertiesView-type">{this.type}</div> {this.contextsSubMenu} - {this.linksSubMenu} {!this.selectedDoc || !LinkManager.currentLink || (!hasSelectedAnchor && this.selectedDoc !== LinkManager.currentLink) ? null : ( - <> - <div className="propertiesView-section" style={{ background: 'darkgray' }}> - <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> - <p>Relationship</p> - {this.editRelationship} - </div> - <div className="propertiesView-input" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}> - <p>Description</p> - {this.editDescription} - </div> - <div className="propertiesView-input inline"> - <p>Show link</p> - <button - style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> - <p>Auto-move anchors</p> - <button - style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline" style={{ marginLeft: 10 }}> - <p>Display arrow</p> - <button - style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - </div> - {!hasSelectedAnchor ? null : ( - <div className="propertiesView-section"> - <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}> - <p>Follow by</p> - <select onChange={e => this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}> - <option value={undefined}>Default</option> - <option value={OpenWhere.addLeft}>Opening in new left pane</option> - <option value={OpenWhere.addRight}>Opening in new right pane</option> - <option value={OpenWhere.replaceLeft}>Replacing left tab</option> - <option value={OpenWhere.replaceRight}>Replacing right tab</option> - <option value={OpenWhere.fullScreen}>Overlaying current tab</option> - <option value={OpenWhere.lightbox}>Opening in lightbox</option> - <option value={OpenWhere.add}>Opening in new tab</option> - <option value={OpenWhere.replace}>Replacing current tab</option> - <option value={OpenWhere.inParent}>Opening in same collection</option> - {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null} - </select> - </div> - <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}> - <p>Animation</p> - <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> - <option value="default">Default</option> - {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( - <option key={effect.toString()} value={effect.toString()}> - {effect.toString()} - </option> - ))} - </select> - <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}> - {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} - {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} - {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} - {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} - {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} - </div> - </div> - {PresBox.inputter( - '0.1', - '0.1', - '10', - NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, - true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), - indent - )}{' '} - <div - className={'slider-headers'} - style={{ - display: 'grid', - justifyContent: 'space-between', - width: `calc(100% - ${indent * 2}px)`, - marginLeft: indent, - marginRight: indent, - gridTemplateColumns: 'auto auto', - borderTop: 'solid', - }}> - <div className="slider-text">Fast</div> - <div className="slider-text">Slow</div> - </div>{' '} - <div className="propertiesView-input inline"> - <p>Play Target Audio</p> - <button - style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Zoom Text Selections</p> - <button - style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Toggle Follow to Outer Context</p> - <button - style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Toggle Target (Show/Hide)</p> - <button - style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Ease Transitions</p> - <button - style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Capture Offset to Target</p> - <button - style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => { - this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined); - this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined); - }} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Center Target (no zoom)</p> - <button - style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}> - <p>Zoom %</p> - <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}> - <input className="presBox-input" style={{ width: '100%' }} readOnly={true} type="number" value={zoom} /> - <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> - <FontAwesomeIcon icon={'caret-up'} /> - </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> - <FontAwesomeIcon icon={'caret-down'} /> - </div> - </div> - </div> - <button - style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }} - onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} - <div - className={'slider-headers'} - style={{ - display: !targZoom ? 'none' : 'grid', - justifyContent: 'space-between', - width: `calc(100% - ${indent * 2}px)`, - marginLeft: indent, - marginRight: indent, - gridTemplateColumns: 'auto auto', - borderTop: 'solid', - }}> - <div className="slider-text">0%</div> - <div className="slider-text">100%</div> - </div>{' '} - </div> - )} - </> + this.linkProperties )} - {this.inkSubMenu} - {this.optionsSubMenu} - {this.fieldsSubMenu} - {isNovice ? null : this.sharingSubMenu} - {isNovice ? null : this.filtersSubMenu} - {isNovice ? null : this.layoutSubMenu} </div> ); @@ -1843,7 +1775,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type; return ( <div className="propertiesView" style={{ width: this.props.width }}> - <div className="propertiesView-title" style={{ width: this.props.width }}> + <div className="propertiesView-sectionTitle" style={{ width: this.props.width }}> Presentation </div> <div className="propertiesView-name" style={{ borderBottom: 0 }}> @@ -1859,7 +1791,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Transitions <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" color="white" /> + <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openPresTransitions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.transitionDropdown}</div> : null} @@ -1873,7 +1805,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Visibilty <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" color="white" /> + <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openPresVisibilityAndDuration ? <div className="propertiesView-presTrails-content">{PresBox.Instance.visibiltyDurationDropdown}</div> : null} @@ -1884,7 +1816,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))} style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> Progressivize <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" color="white" /> + <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null} @@ -1895,7 +1827,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))} style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}> <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'} <div className="propertiesView-presTrails-title-icon"> - <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" color="white" /> + <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" /> </div> </div> {this.openSlideOptions ? <div className="propertiesView-presTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null} |