import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { Utils } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { undoable, undoBatch } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; import { pasteImageBitmap } from './nodes/WebBoxRenderer'; import './PropertiesButtons.scss'; import React = require('react'); const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; enum UtilityButtonState { Default, OpenRight, OpenExternally, } @observer export class PropertiesButtons extends React.Component<{}, {}> { @observable public static Instance: PropertiesButtons; @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc; } @computed get selectedTabView() { return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost; } propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt, doc: Doc, property: string) => void, useUserDoc?: boolean) => { const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedDoc; const onPropToggle = (dv: Opt, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true); return !targetDoc ? null : ( {tooltip(targetDoc?.[property])} } placement="top">
e.stopPropagation()} 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)}>
{label}
); }; @computed get lockButton() { return this.propertyToggleBtn( 'No\xA0Drag', '_lockedPosition', on => `${on ? 'Unlock' : 'Lock'} position to prevent dragging`, on => 'thumbtack' ); } @computed get maskButton() { return this.propertyToggleBtn( 'Mask', 'stroke_isInkMask', on => (on ? 'Make plain ink' : 'Make highlight mask'), on => 'paint-brush', (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc) ); } @computed get hideImageButton() { return this.propertyToggleBtn( 'Background', '_hideImage', on => (on ? 'Show Image' : 'Show Background'), on => 'portrait' ); } @computed get clustersButton() { return this.propertyToggleBtn( 'Clusters', '_freeform_useClusters', on => `${on ? 'Hide' : 'Show'} clusters`, on => 'braille' ); } @computed get panButton() { return this.propertyToggleBtn( 'Lock\xA0View', '_lockedTransform', on => `${on ? 'Unlock' : 'Lock'} panning of view`, on => 'lock' ); } @computed get forceActiveButton() { return this.propertyToggleBtn( 'Active', '_forceActive', on => `${on ? 'Select to activate' : 'Contents always active'} `, on => 'eye' ); } @computed get fitContentButton() { return this.propertyToggleBtn( 'View All', '_freeform_fitContentsToBox', on => `${on ? "Don't" : 'Do'} fit content to container visible area`, on => 'object-group' ); } // 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 @computed get isLightboxButton() { return this.propertyToggleBtn( 'Lightbox', 'isLightbox', on => `${on ? 'Set' : 'Remove'} lightbox flag`, on => 'window-restore', onClick => { SelectionManager.Views().forEach(dv => { const containerDoc = dv.rootDoc; //containerDoc.followAllLinks = // containerDoc.noShadow = // containerDoc.layout_disableBrushing = // containerDoc._forceActive = //containerDoc._freeform_fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox; //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false))); }); } ); } @computed get layout_fitWidthButton() { return this.propertyToggleBtn( 'Fit\xA0Width', '_layout_fitWidth', on => `${on ? "Don't" : 'Do'} fit content to width of container`, on => 'arrows-alt-h' ); } @computed get captionButton() { return this.propertyToggleBtn( 'Caption', '_layout_showCaption', on => `${on ? 'Hide' : 'Show'} caption footer`, on => 'closed-captioning', (dv, doc) => ((dv?.rootDoc || doc)._layout_showCaption = (dv?.rootDoc || doc)._layout_showCaption === undefined ? 'caption' : undefined) ); } @computed get chromeButton() { return this.propertyToggleBtn( 'Controls', '_chromeHidden', on => `${on ? 'Show' : 'Hide'} editing UI`, on => 'edit', (dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden) ); } @computed get titleButton() { return this.propertyToggleBtn( 'Title', '_layout_showTitle', on => 'Switch between title styles', on => 'text-width', (dv, doc) => { const tdoc = dv?.rootDoc || doc; const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : ''; tdoc._layout_showTitle = newtitle; } ); } @computed get layout_autoHeightButton() { return this.propertyToggleBtn( 'Auto\xA0Size', '_layout_autoHeight', on => `Automatical vertical sizing to show all content`, on => 'arrows-alt-v' ); } @computed get gridButton() { return this.propertyToggleBtn( 'Grid', '_freeform_backgroundGrid', on => `Display background grid in collection`, on => 'border-all' ); } @computed get groupButton() { return this.propertyToggleBtn( 'Group', 'isGroup', on => `Display collection as a Group`, on => 'object-group', (dv, doc) => { doc.isGroup = !doc.isGroup; doc.forceActive = doc.isGroup; doc.dragWhenActive = doc.isGroup; } ); } @computed get freezeThumb() { return this.propertyToggleBtn( 'FreezeThumb', '_thumb-frozen', on => `${on ? 'Freeze' : 'Unfreeze'} thumbnail`, on => 'snowflake', (dv, doc) => { if (doc['thumb-frozen']) doc['thumb-frozen'] = undefined; else { document.body.focus(); // so that we can access the clipboard without an error setTimeout(() => pasteImageBitmap((data_url: any, error: any) => { error && console.log(error); data_url && Utils.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename))); }) ); } } ); } @computed get snapButton() { return this.propertyToggleBtn( 'Snap\xA0Lines', 'freeform_snapLines', on => `Display snapping lines when objects are dragged`, on => 'th', undefined, true ); } @computed get onClickButton() { return !this.selectedDoc ? null : ( Choose onClick behavior} placement="top">
e.stopPropagation()}>
onclick
); } @computed get perspectiveButton() { return !this.selectedDoc ? null : ( Choose view perspective} placement="top">
e.stopPropagation()}>
Perspective
); } @undoBatch handlePerspectiveChange = (e: any) => { this.selectedDoc && (this.selectedDoc._type_collection = e.target.value); SelectionManager.Views() .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => (docView.layoutDoc._type_collection = e.target.value)); }; @undoBatch @action handleOptionChange = (onClick: string) => { SelectionManager.Views() .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => { const linkButton = IsFollowLinkScript(docView.props.Document.onClick); docView.noOnClick(); switch (onClick) { case 'enterPortal': docView.makeIntoPortal(); break; case 'toggleDetail': docView.setToggleDetail(); break; case 'linkInPlace': docView.toggleFollowLink(false, false); docView.props.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined; break; case 'linkOnRight': docView.toggleFollowLink(false, false); docView.props.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined; break; } }); }; @undoBatch editOnClickScript = () => { if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, 'onClick')); else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick'); }; @computed get onClickFlyout() { const buttonList = [ ['nothing', 'Select Document'], ['enterPortal', 'Enter Portal'], ['toggleDetail', 'Toggle Detail'], ['linkInPlace', 'Open Link in Lightbox'], ['linkOnRight', 'Open Link on Right'], ]; const list = buttonList.map(value => { const click = () => this.handleOptionChange(value[0]); const linkButton = IsFollowLinkScript(this.selectedDoc.onClick); const followLoc = this.selectedDoc._followLinkLocation; const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); let active = false; // prettier-ignore switch (value[0]) { case 'linkInPlace': active = linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView(); break; case 'linkOnRight': active = linkButton && followLoc === OpenWhere.addRight; break; case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break; case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break; case 'nothing': active = !linkButton && this.selectedDoc.onClick === undefined;break; } return (
{value[1]}
); }); return (
{list}
{Doc.noviceMode ? null : (
{' '} Edit onClick Script
)}
); } @computed get onPerspectiveFlyout() { const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; const makeLabel = (value: string, label: string) => (
); return (
{Object.values(CollectionViewType) .filter(type => !excludedViewTypes.includes(type)) .map(type => makeLabel(type, type))}
); } render() { const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; const isText = layoutField instanceof RichTextField; const isInk = layoutField instanceof InkField; const isImage = layoutField instanceof ImageField; const isMap = this.selectedDoc?.type === DocumentType.MAP; const isCollection = this.selectedDoc?.type === DocumentType.COL; //TODO: will likely need to create separate note-taking view type here const isStacking = this.selectedDoc?._type_collection === CollectionViewType.Stacking || this.selectedDoc?._type_collection === CollectionViewType.NoteTaking; const isFreeForm = this.selectedDoc?._type_collection === CollectionViewType.Freeform; const isTree = this.selectedDoc?._type_collection === CollectionViewType.Tree; const isTabView = this.selectedTabView; const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => (
{' '} {ele}{' '}
); const isNovice = Doc.noviceMode; return !this.selectedDoc ? null : (
{toggle(this.titleButton)} {toggle(this.captionButton)} {toggle(this.lockButton)} {toggle(this.onClickButton)} {toggle(this.layout_fitWidthButton)} {toggle(this.freezeThumb)} {toggle(this.forceActiveButton)} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} {toggle(this.maskButton, { display: !isInk ? 'none' : '' })} {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })} {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })} {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })} {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })} {toggle(this.snapButton, { display: !isCollection ? 'none' : '' })} {toggle(this.clustersButton, { display: !isFreeForm ? 'none' : '' })} {toggle(this.panButton, { display: !isFreeForm ? 'none' : '' })} {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? 'none' : '' })}
); } }