import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { AiOutlineColumnWidth } from 'react-icons/ai'; import { BiHide, BiShow } from 'react-icons/bi'; import { BsGrid3X3GapFill } from 'react-icons/bs'; import { CiGrid31 } from 'react-icons/ci'; import { FaBraille, FaLock, FaLockOpen } from 'react-icons/fa'; import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtitles, MdSubtitlesOff, MdTouchApp } from 'react-icons/md'; import { RxWidth } from 'react-icons/rx'; import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb'; import { TfiBarChart } from 'react-icons/tfi'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, ScriptCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocUtils } from '../documents/Documents'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { undoBatch, undoable } from '../util/UndoManager'; import { InkingStroke } from './InkingStroke'; import './PropertiesButtons.scss'; import { Colors } from './global/globalEnums'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @observer export class PropertiesButtons extends React.Component<{}, {}> { @observable public static Instance: PropertiesButtons; @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.Document; } @computed get selectedLayoutDoc() { return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.layoutDoc; } @computed get selectedTabView() { return !SelectionManager.SelectedSchemaDoc && SelectionManager.Views.lastElement()?.topMost; } propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt, doc: Doc, property: string) => void, useUserDoc?: boolean) => { const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc; const onPropToggle = (dv: Opt, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true); return !targetDoc ? null : ( { if (SelectionManager.Views.length > 1) { SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, 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 @computed get isLightboxButton() { return this.propertyToggleBtn( on => 'Lightbox', 'isLightbox', on => `${on ? 'Set' : 'Remove'} lightbox flag`, on => 'window-restore', onClick => { SelectionManager.Views.forEach(dv => { const containerDoc = dv.Document; //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[Doc.LayoutFieldKey(containerDoc)]); //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' }); containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false))); }); } ); } @computed get titleButton() { return this.propertyToggleBtn( on => (!on ? 'SHOW TITLE' : this.selectedDoc?.['_layout_showTitle'] === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'), '_layout_showTitle', on => 'Switch between title styles', on => (on ? : ), // {currentIcon}, //(on ? :) , //,'text-width', on ? : , (dv, doc) => { const tdoc = dv?.Document || doc; const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : ''; tdoc._layout_showTitle = newtitle ? newtitle : undefined; } ); } @computed get lockButton() { return this.propertyToggleBtn( on => (on ? 'UNLOCK' : 'LOCK'), // 'No\xA0Drag', '_lockedPosition', on => `${on ? 'Unlock' : 'Lock'} position to prevent dragging`, on => (on ? : ) // on => 'thumbtack' ); } @computed get maskButton() { //highlight text while going down and reading through return this.propertyToggleBtn( on => (on ? 'PLAIN INK' : 'HIGHLIGHTER MASK'), 'stroke_isInkMask', on => (on ? 'Make plain ink' : 'Make highlight mask'), on => (on ? : ), // ,// 'paint-brush', (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc) ); } @computed get hideImageButton() { // put in developer -- can trace on top of object and drawing is still there return this.propertyToggleBtn( on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), //'Background', '_hideImage', on => (on ? 'Show Image' : 'Show Background'), on => (on ? : ) //'portrait' ); } @computed get clustersButton() { return this.propertyToggleBtn( on => (on ? 'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'), '_freeform_useClusters', on => `${on ? 'Hide' : 'Show'} clusters`, on => ); } @computed get panButton() { return this.propertyToggleBtn( on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), //'Lock\xA0View', '_lockedTransform', on => `${on ? 'Unlock' : 'Lock'} panning of view`, on => (on ? : ) //'lock' ); } @computed get forceActiveButton() { //select text return this.propertyToggleBtn( on => (on ? 'SELECT TO INTERACT' : 'ALWAYS INTERACTIVE'), '_forceActive', on => `${on ? 'Document must be selected to interact with its contents' : 'Contents always active (respond to click/drag events)'} `, on => // 'eye' ); } @computed get verticalAlignButton() { //select text return this.propertyToggleBtn( on => (on ? 'ALIGN TOP' : 'ALIGN CENTER'), '_layout_centered', on => `${on ? 'Text is aligned with top of document' : 'Text is aligned with center of document'} `, on => // 'eye' ); } @computed get flashcardButton() { return this.propertyToggleBtn( on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'), 'layout_textPainted', on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `, on => , (dv, doc) => { const on = doc.onPaint ? true : false; doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' }); doc[DocData].layout_textPainted = on ? undefined : ``; } ); } @computed get fitContentButton() { return this.propertyToggleBtn( on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All', '_freeform_fitContentsToBox', on => `${on ? "Don't" : 'Do'} fit content to container visible area`, on => (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() { // developer // return this.propertyToggleBtn( // on => 'Lightbox', // 'isLightbox', // on => `${on ? 'Set' : 'Remove'} lightbox flag`, // on => 'window-restore', // onClick => { // SelectionManager.Views.forEach(dv => { // const containerDoc = dv.Document; // //containerDoc.followAllLinks = // // containerDoc.noShadow = // // containerDoc.disableDocBrushing = // // 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.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' }); // containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false))); // }); // } // ); // } @computed get layout_fitWidthButton() { return this.propertyToggleBtn( on => (on ? 'SCALED VIEW' : 'READING VIEW'), //'Fit\xA0Width', '_layout_fitWidth', on => on ? "Scale document so it's width and height fit container (no effect when document is viewed on freeform canvas)" : "Scale document so it's width fits container and its height expands/contracts to fit available space (no effect when document is viewed on freeform canvas)", on => (on ? : ) // 'arrows-alt-h' ); } @computed get captionButton() { return this.propertyToggleBtn( //DEVELOPER on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), //'Caption', '_layout_showCaption', on => `${on ? 'Hide' : 'Show'} caption footer`, on => (on ? : ), //'closed-captioning', (dv, doc) => ((dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined) ); } @computed get chromeButton() { // developer -- removing UI decoration return this.propertyToggleBtn( on => (on ? 'ENABLE UI CONTROLS' : 'DISABLE UI CONTROLS'), '_chromeHidden', on => `${on ? 'Show' : 'Hide'} editing UI`, on => (on ? : ), // 'edit', (dv, doc) => ((dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden) ); } @computed get layout_autoHeightButton() { // store previous dimensions to store old values return this.propertyToggleBtn( on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'), '_layout_autoHeight', on => `Automatical vertical sizing to show all content`, on => ); } @computed get gridButton() { return this.propertyToggleBtn( on => (on ? 'HIDE GRID' : 'DISPLAY GRID'), '_freeform_backgroundGrid', on => `Display background grid in collection`, on => (on ? : ) //'border-all' ); } // @computed get groupButton() { //developer // return this.propertyToggleBtn( // on => '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 => '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() { // 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 => , //'th', undefined ); } // @computed // get onClickButton() { // return !this.selectedDoc ? null : ( // Choose onClick behavior} placement="top"> //
//
// //
e.stopPropagation()}> // //
//
//
//
onclick
//
//
// ); // } // @computed // get perspectiveButton() { // gone // 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.forEach(docView => (docView.layoutDoc._type_collection = e.target.value)); }; @computed get onClickVal() { 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); 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 get onClickButton() { const buttonList = [ ['nothing', 'Select Document'], ['enterPortal', 'Enter Portal'], ['toggleDetail', 'Toggle Detail'], ['linkInPlace', 'Open Link in Lightbox'], ['linkOnRight', 'Open Link on Right'], ]; const items: IListItemProps[] = buttonList.map(value => { return { text: value[1], val: value[1], }; }); return !this.selectedDoc ? null : ( this.handleOptionChange(val as string)} title={'Choose onClick behaviour'} color={SettingsManager.userColor} dropdownType={DropdownType.SELECT} type={Type.SEC} fillWidth /> // Choose onClick behavior} placement="top"> //
//
// //
e.stopPropagation()}> // //
//
//
//
onclick
//
//
); } @undoBatch @action handleOptionChange = (onClick: string) => { SelectionManager.Views.forEach(docView => { const linkButton = IsFollowLinkScript(docView.Document.onClick); docView.noOnClick(); switch (onClick) { case 'enterPortal': DocUtils.makeIntoPortal(docView.Document, docView.layoutDoc, docView.allLinks); break; case 'toggleDetail': docView.setToggleDetail(); break; case 'linkInPlace': docView.toggleFollowLink(false, false); docView.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined; break; case 'linkOnRight': docView.toggleFollowLink(false, false); docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined; break; } }); }; @undoBatch editOnClickScript = () => { if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, 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
)}
); } render() { const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox; const isInk = this.selectedDoc?.layout_isSvg; const isImage = layoutField instanceof ImageField; const isMap = this.selectedDoc?.type === DocumentType.MAP; const isCollection = this.selectedDoc?.type === DocumentType.COL; const isStacking = [CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.NoteTaking].includes(this.selectedDoc?._type_collection as any); 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.verticalAlignButton, { display: !isText ? 'none' : '' })} {toggle(this.flashcardButton, { display: !isText ? 'none' : '' })} {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: isNovice || !isInk ? 'none' : '' })} {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })} {toggle(this.chromeButton, { display: 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' : '' })}
); } }