import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from 'browndash-components'; import { action, runInAction, untracked } from 'mobx'; import { extname } from 'path'; import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PropertiesView } from './PropertiesView'; import './StyleProvider.scss'; import React = require('react'); export enum StyleProp { TreeViewIcon = 'treeView_Icon', TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view Opacity = 'opacity', // opacity of the document view BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views BorderRounding = 'borderRounding', // border radius of the document view Color = 'color', // foreground color of Document view items BackgroundColor = 'backgroundColor', // background color of a document view FillColor = 'fillColor', // fill color of an ink stroke or shape WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note HideLinkBtn = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that ShowCaption = 'layout_showCaption', TitleHeight = 'titleHeight', // Height of Title area ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) BorderPath = 'customBorder', // border path for document view FontSize = 'fontSize', // size of text font FontFamily = 'fontFamily', // font family of text FontWeight = 'fontWeight', // font weight of text Highlighting = 'highlighting', // border highlighting } function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch( () => runInAction(() => { doc._lockedPosition = !doc._lockedPosition; doc._pointerEvents = doc._lockedPosition ? 'none' : undefined; }), 'toggleBackground' ); } export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps { return toBeDetermined?.isContentActive ? toBeDetermined : undefined; } export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${ pw * (1 - inset) } ${ph * 0.4} ${pw} ${ph * (1 - 2 * inset)} ${pw * (1 - inset)} ${ph * (1 - inset)} C ${pw * (1 - 2 * inset)} ${ph} ${pw * 0.6} ${ph * (1 - inset)} ${pw * 0.5} ${ph * (1 - inset)} C ${pw * 0.3} ${ph * (1 - inset)} ${pw * (2 * inset)} ${ph} ${ pw * inset } ${ph * (1 - inset)} C 0 ${ph * (1 - 2 * inset)} ${pw * inset} ${ph * 0.8} ${pw * inset} ${ph * 0.75} C ${pw * inset} ${ph * 0.7} 0 ${ph * (2 * inset)} ${pw * inset} ${ph * inset} C ${pw * (2 * inset)} 0 ${pw * 0.25} ${ph * inset} ${ pw * 0.5 } ${ph * inset}`; } // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab // export function DefaultStyleProvider(doc: Opt, props: Opt, property: string): any { const remoteDocHeader = 'author;author_date;noMargin'; const docProps = testDocProps(props) ? props : undefined; const selected = property.includes(':selected'); const isCaption = property.includes(':caption'); const isAnchor = property.includes(':anchor'); const isAnnotated = property.includes(':annotated'); const isInk = () => doc && doc._layout_isSvg && !props?.LayoutTemplateString; const isOpen = property.includes(':open'); const isEmpty = property.includes(':empty'); const boxBackground = property.includes(':box'); const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : ''; const lockedPosition = () => doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const color = () => props?.styleProvider?.(doc, props, StyleProp.Color); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle); // prettier-ignore switch (property.split(':')[0]) { case StyleProp.TreeViewIcon: const img = ImageCast(doc?.icon, ImageCast(doc?.data)); if (img) { const ext = extname(img.url.href); const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext); return ; } return Doc.toIcon(doc, isEmpty ? undefined : isOpen); case StyleProp.TreeViewSortings: const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {}; allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: }; allSorts[TreeSort.AlphaUp] = { color: 'crimson', icon: }; if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' }; allSorts[TreeSort.WhenAdded] = { color: 'darkgray', icon: }; return allSorts; case StyleProp.Highlighting: if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined; if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) { const selected = SelectionManager.Views().some(dv => dv.rootDoc === doc); const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0); const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex]; if (highlightIndex) { return { highlightStyle, highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK || doc.type === DocumentType.LINK, }; } } return undefined; case StyleProp.DocContents:return undefined; case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey'; case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); case StyleProp.HideLinkBtn:return props?.hideLinkButton || (!selected && doc?.layout_hideLinkButton); case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._text_fontSize, StrCast(Doc.UserDoc().fontSize))); case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily))); case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight))); case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent')); case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption); case StyleProp.TitleHeight:return 15; case StyleProp.ShowTitle: return ( (doc && !props?.LayoutTemplateString && !doc.presentation_targetDoc && !props?.LayoutTemplateString?.includes(KeyValueBox.name) && props?.layout_showTitle?.() !== '' && StrCast( doc._layout_showTitle, props?.layout_showTitle?.() || (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ? doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().layout_showTitle) : remoteDocHeader : '') )) || '' ); case StyleProp.Color: if (DocumentView.LastPressedSidebarBtn === doc) return SettingsManager.userBackgroundColor; if (Doc.IsSystem(doc!)) return SettingsManager.userColor; if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor; const docColor: Opt = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); if (docColor) return docColor; const docView = props?.DocumentView?.(); const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor); return backColor ? lightOrDark(backColor) : undefined; case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : '')); case StyleProp.BorderPath: const borderPath = Doc.IsComicStyle(doc) && props?.renderDepth && ![DocumentType.INK, DocumentType.LINK].includes(doc?.type as any) && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 }; return !borderPath ? null : { clipPath: `path('${borderPath.path}')`, jsx: (
), }; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) || (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) || doc?.type === DocumentType.LABEL) && layout_showTitle() && !StrCast(doc?.layout_showTitle).includes(':hover') ? 15 : 0; case StyleProp.BackgroundColor: { if (DocumentView.LastPressedSidebarBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item let docColor: Opt = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')); // prettier-ignore switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || ""; break; case DocumentType.PRES: docColor = docColor || 'transparent'; break; case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break; case DocumentType.RTF: docColor = docColor || Colors.LIGHT_GRAY; break; case DocumentType.LINK: docColor = (isAnchor ? docColor : undefined); break; case DocumentType.INK: docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break; case DocumentType.EQUATION: docColor = docColor || 'transparent'; break; case DocumentType.LABEL: docColor = docColor || Colors.LIGHT_GRAY; break; case DocumentType.BUTTON: docColor = docColor || Colors.LIGHT_GRAY; break; case DocumentType.IMG: case DocumentType.WEB: case DocumentType.PDF: case DocumentType.MAP: case DocumentType.SCREENSHOT: case DocumentType.VID: docColor = docColor || (Colors.LIGHT_GRAY); break; case DocumentType.COL: docColor = docColor || (Doc.IsSystem(doc) ? SettingsManager.userBackgroundColor : doc.annotationOn ? '#00000010' // faint interior for collections on PDFs, images, etc : doc?._isGroup ? undefined : doc._type_collection === CollectionViewType.Stacking ? (Colors.DARK_GRAY) : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY)); break; //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (Colors.WHITE); } return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor; } case StyleProp.BoxShadow: { if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow) if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW; if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em'); switch (doc?.type) { case DocumentType.COL: return StrCast( doc?.layout_boxShadow, doc?._type_collection === CollectionViewType.Pile ? '4px 4px 10px 2px' : lockedPosition() || doc?._isGroup || docProps?.LayoutTemplateString ? undefined // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide) : `${Colors.MEDIUM_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}` ); case DocumentType.LABEL: if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px'; default: return doc.z ? `#9c9396 ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow : props?.docViewPath().lastElement()?.rootDoc._freeform_useClusters ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : NumCast(doc.group, -1) !== -1 ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent : lockedPosition() ? undefined // if it's a background & has a cluster color, make the shadow spread really big : StrCast(doc.layout_boxShadow, ''); } } case StyleProp.PointerEvents: if (!props?.treeViewDoc && doc?._isGroup && (!doc.backgroundColor || doc.backgroundColor === 'transparent') && !props?.DocumentView?.().docView?.isContentActive?.()) return 'none'; // groups with no backgrounds that are not active (eg selected, or child selected), don't directly respond to pointer events (but their children can to allow the group to be selected or dragged) if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.(); if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all'; if (props?.pointerEvents?.() === 'none') return 'none'; if (opacity() === 0) return 'none'; if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all'; return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active case StyleProp.Decorations: const lock = () => { if (props?.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform) { return doc?.pointerEvents !== 'none' ? null : (
toggleLockedPosition(doc)}>
); } }; const filter = () => { const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard)); const showFilterIcon = StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length ? 'green' // #18c718bd' //'hasFilter' : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || docProps?.childFiltersByRanges().length ? 'orange' //'inheritsFilter' : undefined; return !showFilterIcon ? null : (
} closeOnSelect={true} setSelectedVal={ action((dv) => { (dv as any).select(false); (SettingsManager.propertiesWidth = 250); setTimeout(action(() => { if (PropertiesView.Instance) { PropertiesView.Instance.CloseAll(); PropertiesView.Instance.openFilters = true; } })); }) } size={Size.XSMALL} width={15} height={15} title={showFilterIcon === 'green' ? "This view is filtered. Click to view/change filters": "this view inherits filters from one of its parents"} color={SettingsManager.userColor} background={showFilterIcon} items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])] .filter(dv => StrListCast(dv.rootDoc.childFilters).length || StrListCast(dv.rootDoc.childRangeFilters).length) .map(dv => ({ text: StrCast(dv.rootDoc.title), val: dv as any, style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor}, } as IListItemProps)) } />
); }; const audio = () => { const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped'); const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length; if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null; const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' }; return ( {StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}}>
DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}>
); }; return ( <> {lock()} {filter()} {audio()} ); } } export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) { const color = SettingsManager.userColor; return ( } onClick={undoBatch( action((e: React.MouseEvent) => { e.stopPropagation(); clickFunc ? clickFunc() : (doc[field] = doc[field] ? undefined : true); }) )} /> ); } /** * add lock and hide button decorations for the "Dashboards" flyout TreeView */ export function DashboardStyleProvider(doc: Opt, props: Opt, property: string) { if (doc && property.split(':')[0] === StyleProp.Decorations) { return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc) ? null : DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn))); } return DefaultStyleProvider(doc, props, property); }