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, StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { SelectionManager } from '../util/SelectionManager'; import { undoBatch } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { DocumentView } from './nodes/DocumentView'; import { VideoBox } from './nodes/VideoBox'; 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={undoBatch(() => { if (SelectionManager.Views().length > 1) { SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property)); } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, 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', 'isInkMask', on => (on ? 'Make plain ink' : 'Make highlight mask'), on => 'paint-brush', (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc) ); } @computed get clustersButton() { return this.propertyToggleBtn( 'Clusters', '_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', '_fitContentsToBox', on => `${on ? "Don't" : 'Do'} fit content to container visible area`, on => 'eye' ); } // this implments a container pattern by marking the targetDoc (collection) as an inPlace container, // and then making the contained collection be a "menu" such that when any of its contents are clicked, // they will open their targets in the outer container. To get back to the "menu", you click on the main container. @computed get inPlaceContainerButton() { return this.propertyToggleBtn( 'In Place', 'isInPlaceContainer', on => `${on ? 'Make' : 'Remove'} in place container flag`, on => 'window', onClick => { SelectionManager.Views().forEach(dv => { const containerDoc = dv.rootDoc; containerDoc.followAllLinks = containerDoc.noShadow = containerDoc.noHighlighting = containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer; containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined; containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined; const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement(); if (menuDoc) { menuDoc.hideDecorations = menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer; if (!dv.allLinks.find(link => link.anchor1 === menuDoc || link.anchor2 === menuDoc)) { DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); } DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => { menuItem.followLinkAudio = menuItem.followAllLinks = menuItem._isLinkButton = true; menuItem._followLinkLocation = 'inPlace'; }); } }); } ); } @computed get fitWidthButton() { return this.propertyToggleBtn( 'Fit\xA0Width', '_fitWidth', on => `${on ? "Don't" : 'Do'} fit content to width of container`, on => 'arrows-alt-h' ); } @computed get captionButton() { return this.propertyToggleBtn( 'Caption', '_showCaption', on => `${on ? 'Hide' : 'Show'} caption footer`, on => 'closed-captioning', (dv, doc) => ((dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._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', '_showTitle', on => 'Switch between title styles', on => 'text-width', (dv, doc) => ((dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? 'title' : (dv?.rootDoc || doc)._showTitle === 'title' ? 'title:hover' : undefined) ); } @computed get autoHeightButton() { return this.propertyToggleBtn( 'Auto\xA0Size', '_autoHeight', on => `Automatical vertical sizing to show all content`, on => 'arrows-alt-v' ); } @computed get gridButton() { return this.propertyToggleBtn( 'Grid', '_backgroundGridShow', 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; } ); } @computed get freezeThumb() { return this.propertyToggleBtn( 'FreezeThumb', '_thumb-frozen', on => `${on ? 'Freeze' : 'Unfreeze'} thumbnail`, on => 'arrows-alt-h', (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 && VideoBox.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename))); }) ); } } ); } @computed get snapButton() { return this.propertyToggleBtn( 'Snap\xA0Lines', 'showSnapLines', on => `Display snapping lines when objects are dragged`, on => 'border-all', 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._viewType = e.target.value); SelectionManager.Views() .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => (docView.layoutDoc._viewType = e.target.value)); }; @undoBatch @action handleOptionChange = (onClick: string) => { this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick); SelectionManager.Views() .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => { docView.noOnClick(); switch (onClick) { case 'enterPortal': docView.makeIntoPortal(); break; case 'toggleDetail': docView.setToggleDetail(); break; case 'linkInPlace': docView.toggleFollowLink('inPlace', false, false); break; case 'linkOnRight': docView.toggleFollowLink('add:right', false, false); 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 in Place'], ['linkOnRight', 'Open Link on Right'], ]; const currentSelection = this.selectedDoc.onClickBehavior; // Get items to place into the list const list = buttonList.map(value => { const click = () => { this.handleOptionChange(value[0]); }; 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 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?._viewType === CollectionViewType.Stacking || this.selectedDoc?._viewType === CollectionViewType.NoteTaking; const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform; const isTree = this.selectedDoc?._viewType === 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.fitWidthButton)} {toggle(this.freezeThumb)} {toggle(this.forceActiveButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.inPlaceContainerButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} {toggle(this.maskButton, { display: !isInk ? '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' : '' })}
); } }