diff options
Diffstat (limited to 'src')
46 files changed, 1827 insertions, 675 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index d03ae1486..ab5c157eb 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -86,6 +86,7 @@ export function returnEmptyDoclist() { return [] as any[]; } +// eslint-disable-next-line @typescript-eslint/no-namespace export namespace ClientUtils { export const CLICK_TIME = 300; export const DRAG_THRESHOLD = 4; @@ -238,6 +239,40 @@ export namespace ClientUtils { return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')'; } + export function hexToHsv(hex: string): [number, number, number] { + if (!hex) return [0, 0, 0]; // Default to black if hex is not defined + const r = parseInt(hex.slice(1, 3), 16) / 255; + const g = parseInt(hex.slice(3, 5), 16) / 255; + const b = parseInt(hex.slice(5, 7), 16) / 255; + const max = Math.max(r, g, b), + min = Math.min(r, g, b); + const d = max - min; + let h: number; + const s = max === 0 ? 0 : d / max; + const v = max; + + switch (max) { + case min: + h = 0; + break; + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + default: + h = 0; + break; + } + h /= 6; + return [h, s, v]; + }; + + export function HSLtoRGB(h: number, s: number, l: number) { // Must be fractions of 1 // s /= 100; @@ -449,30 +484,29 @@ export function smoothScrollHorizontal(duration: number, element: HTMLElement | animateScroll(); } -export function addStyleSheet(styleType: string = 'text/css') { +export function addStyleSheet() { const style = document.createElement('style'); - style.type = styleType; const sheets = document.head.appendChild(style); - return (sheets as any).sheet; + return sheets.sheet; } -export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { +export function addStyleSheetRule(sheet: CSSStyleSheet | null, selector: string, css: string | {[key:string]: string}, selectorPrefix = '.') { const propText = typeof css === 'string' ? css : Object.keys(css) .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p])) .join(';'); - return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); + return sheet?.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); } -export function removeStyleSheetRule(sheet: any, rule: number) { - if (sheet.rules.length) { +export function removeStyleSheetRule(sheet: CSSStyleSheet|null, rule: number) { + if (sheet?.rules.length) { sheet.removeRule(rule); return true; } return false; } -export function clearStyleSheetRules(sheet: any) { - if (sheet.rules.length) { +export function clearStyleSheetRules(sheet: CSSStyleSheet|null) { + if (sheet?.rules.length) { numberRange(sheet.rules.length).map(() => sheet.removeRule(0)); return true; } diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 05007960d..d95e564c7 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -12,6 +12,7 @@ enum GPTCallType { DESCRIBE = 'describe', MERMAID = 'mermaid', DATA = 'data', + RUBRIC = 'rubric' } type GPTCallOpts = { @@ -26,6 +27,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' }, edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' }, flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' }, + completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." }, mermaid: { model: 'gpt-4-turbo', @@ -42,8 +44,9 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { sort: { model: 'gpt-4o', maxTokens: 2048, - temp: 0.5, - prompt: "I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok.", + temp: 0.25, + prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by shared content. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas" + // prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:" }, describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' }, chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' }, @@ -53,6 +56,13 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { temp: 0, prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct', }, + + rubric: { + model: 'gpt-4-turbo', + maxTokens: 1024, + temp: 0, + prompt: "Provide a definition for the vollowing term. It will be used as a rubric to evaluate the user's understanding of the topic", + }, }; let lastCall = ''; @@ -70,7 +80,12 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: a try { lastCall = inputText; - const usePrompt = prompt ? opts.prompt + prompt : opts.prompt; + // const configuration: ClientOptions = { + // apiKey: process.env.OPENAI_KEY, + // dangerouslyAllowBrowser: true, + // }; + + const usePrompt = prompt ? prompt + opts.prompt : opts.prompt; const messages: ChatCompletionMessageParam[] = [ { role: 'system', content: usePrompt }, { role: 'user', content: inputText }, diff --git a/src/client/documents/DocFromField.ts b/src/client/documents/DocFromField.ts index b65bbbdf5..9acb9c225 100644 --- a/src/client/documents/DocFromField.ts +++ b/src/client/documents/DocFromField.ts @@ -1,5 +1,4 @@ import { Doc, DocListCast } from '../../fields/Doc'; -import { InkField } from '../../fields/InkField'; import { List } from '../../fields/List'; import { StrCast } from '../../fields/Types'; import { AudioField, ImageField, PdfField, VideoField } from '../../fields/URLField'; @@ -12,7 +11,17 @@ export function ResetLayoutFieldKey(doc: Doc, fieldKey: string) { doc.layout = StrCast(doc.layout).replace(/={'.*'}/, `={'${fieldKey}'}`); return doc; } -export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { +/** + * Creates a new document based on the type of (and containing the) data in the specified field of an existing document. + * If the field contains a list, then it may be useful to specify a classProto to indicate the type of + * collection Doc that gets created. + * @param target document to retrive field from + * @param fieldKey field key to retrieve + * @param classProto optionally a class proto to set on the new document + * @param options metadata configuration for new document + * @returns + */ +export function DocumentFromField(target: Doc, fieldKey: string, classProto?: Doc, options?: DocumentOptions): Doc | undefined { const field = target[fieldKey]; const resolved = options ?? {}; const nonDocFieldToDoc = () => { @@ -20,12 +29,11 @@ export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, op if (field instanceof VideoField) return Docs.Create.VideoDocument(field.url.href, resolved); if (field instanceof PdfField) return Docs.Create.PdfDocument(field.url.href, resolved); if (field instanceof AudioField) return Docs.Create.AudioDocument(field.url.href, resolved); - if (field instanceof InkField) return Docs.Create.InkDocument(field.inkData, resolved); if (field instanceof List && field[0] instanceof Doc) return Docs.Create.StackingDocument(DocListCast(field), resolved); return Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved }); }; const created = field instanceof Doc ? field : ResetLayoutFieldKey(nonDocFieldToDoc(), fieldKey); created.title = fieldKey; - proto && created.proto && (created.proto = Doc.GetProto(proto)); + classProto && created.proto && (created.proto = classProto); return created; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3737aa0b5..612629f89 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -5,7 +5,7 @@ import { reaction } from 'mobx'; import { basename } from 'path'; import { ClientUtils, OmitKeys } from '../../ClientUtils'; import { DateField } from '../../fields/DateField'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc'; +import { CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc'; import { Initializing } from '../../fields/DocSymbols'; import { HtmlField } from '../../fields/HtmlField'; import { InkField } from '../../fields/InkField'; @@ -485,9 +485,16 @@ export class DocumentOptions { userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); - cardSort_customField?: STRt = new StrInfo('field key used for sorting cards'); - cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown) + // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why') + // cardSort_customField?: STRt = new StrInfo('field key used for sorting cards'); + cardSort_activeIcons?: List<string>; //icons each card is tagges with + // cardSort_visibleSortGroups?: List<string>; // which sorting values are being filtered (shown) + + // cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown) + cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); + // test?: STRt = new StrInfo('testing for filtering') keywords?: MAPt = new MapInfo('keywords', true); + } export const DocOptions = new DocumentOptions(); @@ -844,18 +851,7 @@ export namespace Docs { return linkDoc; } - export function InkDocument( - points: PointData[], - options: DocumentOptions = {}, - strokeWidth = ActiveInkWidth(), - color = ActiveInkColor(), - strokeBezier = ActiveInkBezierApprox(), - fillColor = ActiveFillColor(), - arrowStart = ActiveArrowStart(), - arrowEnd = ActiveArrowEnd(), - dash = ActiveDash(), - isInkMask = ActiveIsInkMask() - ) { + export function InkDocument(points: PointData[], options: DocumentOptions = {}, strokeWidth: number, color: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, isInkMask: boolean) { const ink = InstanceFromProto(Prototypes.get(DocumentType.INK), '', { title: 'ink', ...options }); const I = Doc.GetProto(ink); // I.layout_hideOpenButton = true; // don't show open full screen button when selected @@ -871,6 +867,7 @@ export namespace Docs { I.text_align = 'center'; I.rotation = 0; I.defaultDoubleClick = 'ignore'; + I.keepZWhenDragged = true; I.author_date = new DateField(); I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; // I.acl_Override = SharingPermissions.Unset; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index cb3d9df62..67f8dafc4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -39,7 +39,7 @@ import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; -interface Button { +export interface Button { // DocumentOptions fields a button can set title?: string; toolTip?: string; @@ -667,32 +667,67 @@ pie title Minerals in my tap water } static cardTools(): Button[] { return [ + // { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]), + // title: "Sort Type", toolTip: "Card Sort Type", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setCardSort(value, _readOnly_); }'}}, + // { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, + // btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, { title: "Time", icon:"hourglass-half", toolTip:"Sort by most recent document creation", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"time", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "AI Sort", icon:"robot", toolTip:"Have Chat GPT sort your cards for you !", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"chat", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Pile", icon:"layer-group", toolTip:"View the cards as a pile in the free form view !", btnType: ButtonType.ClickButton, expertMode: false, toolType:"pile", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Chat Popup", icon:"lightbulb", toolTip:"Toggle the chat popup's visibility!", width: 45 ,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, + + { title: "Sort", toolTip: "Manage sort order / lock status", icon: "sort" , btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true, + subMenu: [ + { title: "Ascending", toolTip: "Sort the cards in ascending order", btnType: ButtonType.ToggleButton, icon: "sort-up", toolType:"up", ignoreClick: true, scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}' }}, + { title: "Descending", toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true, scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, + ]}, + + // { title: "Filter", icon:"Filter", toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + + + + + + + // // { title: "AIs", icon:"Visibility", toolTip:"Filter AI labels", subMenu: this.cardGroupTools("robot"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"heart", toolTip:"Add Like labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"like", funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "1st", icon:"Visibility", toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"star", toolTip:"Add Star labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "2nd", icon:"Visibility", toolTip:"Filter stars", width: 150, subMenu: this.cardGroupTools("star"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"cloud", toolTip:"Add Idea labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"idea", funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "3rd", icon:"Visibility", toolTip:"Filter ideas", width: 150, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, ] } - static labelTools(): Button[] { + // static labelTools(): Button[] { + // return [ + // // { title: "Smart", icon:"robot", toolTip:"Have ChatGPT Label and sort your cards for you!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"chat", funcs: {hidden:`showFreeform ("chat", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "AIs", icon:"AI Sort", toolTip:"Filter AI labels", subMenu: this.cardGroupTools("robot"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"heart", toolTip:"Add Like labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"like", funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "1st", icon:"1st", toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"star", toolTip:"Add Star labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "2nd", icon:"2nd", toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"cloud", toolTip:"Add Idea labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"idea", funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "3rd", icon:"3rd", toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // ] + // } + static tagGroupTools(): Button[] { return [ - { title: "AI", icon:"robot", toolTip:"Add AI labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"chat", funcs: {hidden:`showFreeform ("chat", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "AIs", icon:"AI Sort", toolTip:"Filter AI labels", subMenu: this.cardGroupTools("chat"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, - { title: "Like", icon:"heart", toolTip:"Add Like labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"like", funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Likes", icon:"Likes", toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, - { title: "Star", icon:"star", toolTip:"Add Star labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Stars", icon:"Stars", toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, - { title: "Idea", icon:"satellite", toolTip:"Add Idea labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"idea", funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Ideas", icon:"Ideas", toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("satellite"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, - ] - } - static cardGroupTools(icon: string): Button[] { - return [ - { title: "1", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "2", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "3", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "4", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"4", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "5", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "6", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "7", icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Star", icon: StrCast(Doc.UserDoc().myFilterHotKeyIcons) ?? "Star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Options", icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + + + // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Group 4", icon, toolTip:"Click to toggle group 4's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"4", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "", icon, toolTip:"Click to toggle group 5's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "", icon, toolTip:"Click to toggle group 6's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "", icon, toolTip:"Click to toggle group 7's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, ] } static viewTools(): Button[] { @@ -801,8 +836,31 @@ pie title Minerals in my tap water { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Card", icon: "Sort", toolTip: "Card sort", subMenu: CurrentUserUtils.cardTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Label", icon: "Label", toolTip: "Assign card labels", subMenu: CurrentUserUtils.labelTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + + + // { title: "SortType", btnList: new List<string>(["Time", "Type", "Color", "ChatGPT", "Custom 1", "Custom 2", "Custom 3" ]), + // toolTip: "Card Sort Type", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return showFreeform(value, _readOnly_); }' }, + // // funcs: {hidden: `!SelectedDocType("card", this.expertMode)`} + // }, + // { title: "Visibility", icon:"Visibility", toolTip:"Filter AI labels", subMenu: this.cardGroupTools("robot"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"heart", toolTip:"Add Like labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"like", funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Visibility", icon:"Visibility", toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"star", toolTip:"Add Star labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Visibility", icon:"Visibility", toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + // // { title: "Custom", icon:"cloud", toolTip:"Add Idea labels", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"idea", funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Visibility", icon:"Visibility", toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`,width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, + { title: "Filter", icon:"=", toolTip:"Filter cards by tags", btnType: ButtonType.MultiToggleButton, width: 150, ignoreClick: true,toolType:DocumentType.COL, + subMenu: this.tagGroupTools(), funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, + // [ + // { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + // { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + + // ] + }, + { title: "Card", icon: "Card", toolTip: "Card View Tools", subMenu: CurrentUserUtils.cardTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available + // { title: "Create", icon: "Create", toolTip: "Assign card labels", subMenu: CurrentUserUtils.labelTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected @@ -1116,4 +1174,4 @@ ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.import // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() }); +ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() });
\ No newline at end of file diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index d3c10f9f4..278931cdd 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -29,7 +29,7 @@ export enum ColorScheme { } @observer -export class SettingsManager extends React.Component<{}> { +export class SettingsManager extends React.Component<object> { // eslint-disable-next-line no-use-before-define public static Instance: SettingsManager; static _settingsStyle = addStyleSheet(); @@ -123,7 +123,7 @@ export class SettingsManager extends React.Component<{}> { 'change color scheme' ); - constructor(props: {}) { + constructor(props: object) { super(props); makeObservable(this); SettingsManager.Instance = this; diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 1337d271f..cc0366c5b 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -11,6 +11,7 @@ export class SnappingManager { return SnappingManager._manager ?? new SnappingManager(); } + @observable _longPress = false; @observable _shiftKey = false; @observable _ctrlKey = false; @observable _metaKey = false; @@ -43,6 +44,7 @@ export class SnappingManager { public static get HorizSnapLines() { return this.Instance._horizSnapLines; } // prettier-ignore public static get VertSnapLines() { return this.Instance._vertSnapLines; } // prettier-ignore + public static get LongPress() { return this.Instance._longPress; } // prettier-ignore public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore @@ -58,6 +60,7 @@ export class SnappingManager { public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore public static get PrintToConsole() { return this.Instance._printToConsole; } // prettier-ignore + public static SetLongPress = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts index 0f98a2710..48cb6e3a5 100644 --- a/src/client/util/request-image-size.ts +++ b/src/client/util/request-image-size.ts @@ -54,6 +54,7 @@ module.exports = function requestImageSize(options: any) { } } catch (err) { /* empty */ + console.log("Error: ", err) } }); diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index 11614d627..374d6ecd3 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -153,6 +153,10 @@ $linkGap: 3px; &:hover { background-color: $black; + + .documentButtonBar-pinTypes { + display: flex; + } } } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a75c7098c..c8df66535 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -286,6 +286,42 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( @computed get keywordButton() { const targetDoc = this.view0?.Document; + + const metaBtn = (name: string, icon: IconProp) => { + const tooltip = `Show ${name}`; + return ( + <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}> + <div className="documentButtonBar-pinIcon"> + <FontAwesomeIcon + className="documentdecorations-icon" + style={{ width: 20 }} + key={icon.toString()} + size="sm" + icon={icon} + // onPointerEnter={action(() => { + // this.subPin = + // (pinLayoutView ? 'Layout' : '') + + // (pinLayoutView && pinContentView ? ' &' : '') + + // (pinContentView ? ' Content View' : '') + + // (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''); + // })} + // onPointerLeave={action(() => { + // this.subPin = ''; + // })} + onClick={e => { + name === 'tags' ? targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags) : 'hi' + + + }} + /> + </div> + </Tooltip> + ); + }; + + + + return !targetDoc ? null : ( <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}> <div @@ -294,6 +330,14 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( onClick={() => { targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels; }}> + + <div className="documentButtonBar-pinTypes"> + { + metaBtn('tags', "tag") + /* {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} */} + </div> <FontAwesomeIcon className="documentdecorations-icon" icon="tag" /> </div> </Tooltip> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index bd6952620..b5d819b97 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -832,7 +832,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora <div className="link-button-container" style={{ - top: `${doc[DocData].showLabels ? 4 + (doc[DocData].keywordHeight as number) : 4}px`, + top: `${(doc[DocData].showLabels || doc[DocData].showIconTags) ? 4 + (doc._keywordHeight as number) : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> <DocumentButtonBar views={() => DocumentView.Selected()} /> diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index c97edd7f0..332a89a96 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -19,6 +19,16 @@ import './FilterPanel.scss'; import { DocumentView } from './nodes/DocumentView'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { Button } from '../util/CurrentUserUtils'; +import { ButtonType } from './nodes/FontIconBox/FontIconBox'; +import { DocCast } from '../../fields/Types'; +// import { Docs } from '../../documents/Documents'; +import { Docs } from '../documents/Documents'; +import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { DocumentOptions } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; +import { dropActionType } from '../util/DropActionTypes'; + interface filterProps { Document: Doc; @@ -217,12 +227,89 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; + addHotkey() { + + const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); + const filter = buttons['Filter'] + const filter2 = DocCast(filter) + const but2 = Doc.UserDoc().myContextMenuBtns + + // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), { + // _width: 250, + // _height: 200, + // _layout_fitWidth: false, + // _layout_autoHeight: true, + // }); + + // CurrentUserUtils.setupContextMenuButtons() + + // const hm = Docs.Create.FontIconDocument() + + // DocumentView.getDocumentView(filter2)?.ComponentView?.addDocument?.(hm) + + // console.log(DocumentView.getDocumentView(filter2) + "hiiiii") + + // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm) + + const hi = CurrentUserUtils.contextMenuTools() + + const newKey: Button = { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} + + + const heyy = [...hi, newKey] + + Doc.UserDoc().myFilterHotKeyIcons = "palette" + Doc.UserDoc().myFilterHotKeyTitles = "hi" + + CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc()); + + + + Doc.UserDoc().hi = Docs.Create.FontIconDocument( + { title: '', + icon: 'map-pin', + + backgroundColor: '#ACCEF7', + layout_hideAllLinks: true, + _width: 15, + _height: 15, + _xPadding: 0, + } + ) + + + // buttons['Filter'] + + + + + const reqdCtxtOpts:DocumentOptions = { title: "hi", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; + const ctxtMenuBtns = CurrentUserUtils.setupContextMenuBtn(newKey, buttons); + // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns); + + + DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]); + + + // newCol && dv.ComponentView?.addDocument?.(newCol); + + // console.log(but2 + "omgg") + + + + // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); + + + + // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc]) + } + render() { return ( <div className="filterBox-treeView"> <div className="filterBox-select"> <div style={{ width: '100%' }}> - <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo]} /> + <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} /> </div> {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */} {/* <div className="filterBox-select-bool"> @@ -283,6 +370,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { ) )} </div> + <div> + <div className="filterBox-select"> + <div style={{ width: '100%' }}> + <FieldsDropdown Document={this.Document} selectFunc={this.addHotkey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} /> + </div> + </div> + + </div> </div> ); } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 2f26bdaef..e3e252593 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -4,25 +4,23 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; +import { Doc, Opt } from '../../fields/Doc'; +import { InkData, InkField, InkTool } from '../../fields/InkField'; +import { NumCast } from '../../fields/Types'; import { ActiveArrowEnd, ActiveArrowScale, ActiveArrowStart, ActiveDash, - ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, - Doc, - Opt, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, -} from '../../fields/Doc'; -import { InkData, InkField, InkTool } from '../../fields/InkField'; -import { NumCast } from '../../fields/Types'; +} from './nodes/DocumentView'; // import MobileInkOverlay from '../../mobile/MobileInkOverlay'; import { Gestures } from '../../pen-gestures/GestureTypes'; import { GestureUtils } from '../../pen-gestures/GestureUtils'; @@ -32,7 +30,7 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import './GestureOverlay.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { DocumentView } from './nodes/DocumentView'; +import { ActiveFillColor, DocumentView } from './nodes/DocumentView'; export enum ToolglassTools { InkToText = 'inktotext', diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 716edc22d..f552540b8 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -90,7 +90,7 @@ export class MainView extends ObservableReactComponent<{}> { @observable private _windowWidth: number = 0; @observable private _windowHeight: number = 0; - @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) + @observable _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons @observable private _panelContent: string = 'none'; @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel; @@ -549,6 +549,9 @@ export class MainView extends ObservableReactComponent<{}> { fa.faRobot, fa.faSatellite, fa.faStar, + fa.faCloud, + fa.faBolt, + fa.faLightbulb ] ); } @@ -556,16 +559,16 @@ export class MainView extends ObservableReactComponent<{}> { private longPressTimer: NodeJS.Timeout | undefined; globalPointerClick = action(() => { this.longPressTimer && clearTimeout(this.longPressTimer); - DocumentView.LongPress = false; + SnappingManager.SetLongPress(false); }); globalPointerMove = action((e: PointerEvent) => { if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer); }); globalPointerDown = action((e: PointerEvent) => { - DocumentView.LongPress = false; + SnappingManager.SetLongPress(false); this.longPressTimer = setTimeout( action(() => { - DocumentView.LongPress = true; + SnappingManager.SetLongPress(true); }), 1000 ); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 024db82a4..58bb67120 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -58,7 +58,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps private _widthUndo?: UndoManager.Batch; // eslint-disable-next-line no-use-before-define - public static Instance: PropertiesView | undefined; + public static Instance: PropertiesView; constructor(props: any) { super(props); makeObservable(this); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index f4d73cd1d..55e6217ff 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -30,6 +30,7 @@ import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; +import { IconTagBox } from './nodes/IconTagBox'; function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); @@ -155,7 +156,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & case StyleProp.DocContents: return undefined; case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey'; case StyleProp.Opacity: return componentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); - case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], StrCast(Doc.UserDoc().fontColor, color())); + case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], isCaption ? lightOrDark(backgroundCol()) : StrCast(Doc.UserDoc().fontColor, color())); case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize)); case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily)); case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight)); @@ -377,6 +378,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & return (<KeywordBox isEditing={false} doc={doc}></KeywordBox>) } } + + const iconTags = () => { + if (doc && doc![DocData].showIconTags) + {return (<IconTagBox doc= {doc}></IconTagBox>)} + } return ( <> {paint()} @@ -384,6 +390,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & {filter()} {audio()} {keywords()} + {iconTags()} </> ); } diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index a089b248d..5869f89e1 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -22,27 +22,45 @@ transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); } -.card-button-container { - display: flex; - padding: 3px; - // width: 300px; - background-color: rgb(218, 218, 218); /* Background color of the container */ - border-radius: 50px; /* Rounds the corners of the container */ - transform: translateY(75px); - // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ - align-items: center; /* Centers buttons vertically */ - justify-content: start; /* Centers buttons horizontally */ -} +// .card-button-container { +// display: flex; +// padding: 3px; +// // width: 300px; +// // height:100px; +// pointer-events: none; /* This ensures the container does not capture hover events */ + +// background-color: rgb(218, 218, 218); /* Background color of the container */ +// border-radius: 50px; /* Rounds the corners of the container */ +// transform: translateY(25px); +// // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ +// align-items: center; /* Centers buttons vertically */ +// justify-content: start; /* Centers buttons horizontally */ + +// button { +// pointer-events: auto; /* Re-enable pointer events for the buttons */ + +// width: 70px; +// height: 70px; +// border-radius: 50%; +// background-color: $dark-gray; +// // border-color: $medium-blue; +// margin: 5px; // transform: translateY(-50px); +// background-color: transparent; +// } +// } + +.no-card-span{ + position: relative; + width: fit-content; + text-align: center; + font-size: 65px; + + -button { - width: 35px; - height: 35px; - border-radius: 50%; - background-color: $dark-gray; - // border-color: $medium-blue; - margin: 5px; // transform: translateY(-50px); } + + // button:hover { // transform: translateY(-50px); // } @@ -74,11 +92,18 @@ button { flex-direction: column; } +// .card-item:hover { +// box-shadow: 0 20px 20px $medium-blue; +// transform: scale(1.05); + + +// } + .card-item-inactive { opacity: 0.5; } .card-item-active { - position: absolute; + // position: absolute; z-index: 100; } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index de46180e6..736cc2354 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -2,8 +2,8 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils'; -import { numberRange } from '../../../Utils'; -import { Doc, NumListCast } from '../../../fields/Doc'; +import { emptyFunction, numberRange } from '../../../Utils'; +import { Doc, NumListCast, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { BoolCast, Cast, DateCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; @@ -19,12 +19,22 @@ import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView } from './CollectionSubView'; +import { FieldsDropdown } from '../FieldsDropdown'; +import { Button, IconButton } from 'browndash-components'; +import { faStar } from '@fortawesome/free-solid-svg-icons'; +import { FaStar, FaHeart, FaRobot, FaCloud } from 'react-icons/fa'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { SettingsManager } from '../../util/SettingsManager'; +import { Tooltip } from '@mui/material'; +import { dropActionType } from '../../util/DropActionTypes'; +import { List } from '../../../fields/List'; enum cardSortings { Time = 'time', Type = 'type', Color = 'color', Custom = 'custom', + Chat = 'chat', None = '', } @observer @@ -35,10 +45,15 @@ export class CollectionCardView extends CollectionSubView() { private _textToDoc = new Map<string, Doc>(); @observable _forceChildXf = false; - @observable _isLoading = false; + // @observable _isLoading = false; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap<Doc, DocumentView>(); + _draggerRef = React.createRef<HTMLDivElement>(); @observable _maxRowCount = 10; + @observable _docDraggedIndex: number = -1; + @observable _isACardBeingDragged: boolean = false; + @observable overIndex: number = -1; + static getButtonGroup(groupFieldKey: 'chat' | 'star' | 'idea' | 'like', doc: Doc): number | undefined { return Cast(doc[groupFieldKey], 'number', null); @@ -61,23 +76,32 @@ export class CollectionCardView extends CollectionSubView() { } }; - protected createDashEventsTarget = (ele: HTMLDivElement | null) => { - this._dropDisposer?.(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); - } - }; constructor(props: any) { super(props); makeObservable(this); + this.setRegenerateCallback(); } + setRegenerateCallback() { + GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc); + } + + @action + childPairStringListAndUpdateSortDesc = async () => { + const sortDesc = await this.childPairStringList(); // Await the promise to get the string result + GPTPopup.Instance.setSortDesc(sortDesc.join()); + GPTPopup.Instance.onSortComplete = (sortResult: string) => this.processGptOutput(sortResult); + }; + componentDidMount(): void { + this.Document.childFilters_boolean = 'OR' this._disposers.sort = reaction( - () => ({ cardSort: this.cardSort, field: this.cardSort_customField }), - ({ cardSort, field }) => (cardSort === cardSortings.Custom && field === 'chat' ? this.openChatPopup() : GPTPopup.Instance.setVisible(false)) + () => ({ cardSort: this.cardSort }), + ({ cardSort}) => (cardSort === cardSortings.Chat ? this.openChatPopup() : GPTPopup.Instance.setVisible(false)) ); + + } componentWillUnmount() { @@ -92,6 +116,7 @@ export class CollectionCardView extends CollectionSubView() { @computed get cardSort() { return StrCast(this.Document.cardSort) as any as cardSortings; } + /** * how much to scale down the contents of the view so that everything will fit */ @@ -100,42 +125,31 @@ export class CollectionCardView extends CollectionSubView() { return (this._childDocumentWidth * length) / this._props.PanelWidth(); } - @computed get translateWrapperX() { - let translate = 0; - - if (this.inactiveDocs().length !== this.childDocsWithoutLinks.length && this.inactiveDocs().length < 10) { - translate += this.panelWidth() / 2; - } - return translate; - } - /** * The child documents to be rendered-- either all of them except the Links or the docs in the currently active * custom group */ @computed get childDocsWithoutLinks() { const regularDocs = this.childDocs.filter(l => l.type !== DocumentType.LINK); - const activeGroups = NumListCast(this.Document.cardSort_visibleSortGroups); + const activeGroups = StrListCast(this.Document.cardSort_visibleSortGroups); - if (activeGroups.length > 0 && this.cardSort === cardSortings.Custom) { + if (activeGroups.length > 0) { return regularDocs.filter(doc => { - // Get the group number for the current index - const groupNumber = CollectionCardView.getButtonGroup(this.cardSort_customField, doc); - // Check if the group number is in the active groups - return groupNumber !== undefined && activeGroups.includes(groupNumber); - }); + const activeTags = StrListCast(doc.cardSort_activeIcons) + return activeTags !== undefined && activeTags.some(tag => activeGroups.includes(tag)); + }) } // Default return for non-custom cardSort or other cases, filtering out links return regularDocs; } + + /** - * Determines the order in which the cards will be rendered depending on the current sort type + * Number of rows of cards to be rendered */ - @computed get sortedDocs() { - return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc)); - } + @computed get numRows() { return Math.ceil(this.sortedDocs.length / 10); } @action setHoveredNodeIndex = (index: number) => { @@ -170,6 +184,8 @@ export class CollectionCardView extends CollectionSubView() { * @returns */ rotate = (amCards: number, index: number) => { + if (amCards == 1) return 0; + const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2)); const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); @@ -204,27 +220,76 @@ export class CollectionCardView extends CollectionSubView() { return Math.abs(stepMag * (apex - index - 1)) - rowOffset; }; - /** - * Translates the selected node to the middle fo the screen - * @param index - * @returns - */ - translateSelected = (index: number): number => { - // if (this.isSelected(index)) { - const middleOfPanel = this._props.PanelWidth() / 2; - const scaledNodeWidth = this.panelWidth() * 1.25; + findCardDropIndex = (mouseX: number, mouseY: number, direction: 'left' | 'right') => { + + const amCardsTotal = this.sortedDocs.length + let index = 0; + const cardWidth = amCardsTotal < this._maxRowCount ? this._props.PanelWidth() / amCardsTotal : this._props.PanelWidth() / this._maxRowCount; + + // Calculate the adjusted X position accounting for the initial offset + let adjustedX = mouseX - // Calculate the position of the node's left edge before scaling - const nodeLeftEdge = index * this.panelWidth(); - // Find the center of the node after scaling - const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2; + const amRows = Math.ceil(amCardsTotal / this._maxRowCount); + const rowHeight = this._props.PanelHeight( ) / amRows + const currRow = Math.floor((mouseY - 100) / rowHeight) //rows start at 0 - // Calculate the translation needed to align the scaled node's center with the panel's center - const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4; + if (adjustedX < 0) { + return 0; // Before the first column + } + + if (amCardsTotal < this._maxRowCount) { + index = Math.floor(adjustedX / cardWidth); + } - return translation; + else if (currRow != amRows -1 ){ + index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount) + } + + else { + // console.log(amRows + "am rows") + const rowAmCards = amCardsTotal - (currRow * this._maxRowCount) + const offset = ((this._maxRowCount - rowAmCards ) / 2) * cardWidth + adjustedX = mouseX - offset + + index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount); + } + return index; }; + @action + onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => { + if (DragManager.docsBeingDragged.length != 0 ) { + this._isACardBeingDragged = true + + const direction = e.movementX > 0 ? 'right' : 'left'; + const newIndex = this.findCardDropIndex(e.clientX, e.clientY, direction); + + if (newIndex !== this._docDraggedIndex && newIndex != -1) { + this._docDraggedIndex = newIndex; + } + } + }; + + + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + this._isACardBeingDragged = false; + this._docDraggedIndex = -1; + e.stopPropagation() + const draggedDocs = de.complete.docDragData?.draggedDocuments; + return true; + } + return false; + }; + + + @computed get sortedDocs() { + // console.log("hi hi hi") + // console.log(this.layoutDoc.cardSort_isDesc + "layoutdoc desc") + console.log(this.cardSort + "card sort") + + return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.cardSort_isDesc), this._docDraggedIndex); + } /** * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the * front, latter cards to the back @@ -233,38 +298,65 @@ export class CollectionCardView extends CollectionSubView() { * @param isDesc * @returns */ - sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean) => { - if (sortType === cardSortings.None) return docs; - docs.sort((docA, docB) => { - const [typeA, typeB] = (() => { + sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => { + // if (sortType === cardSortings.None) return docs; + + // if(sortType !== cardSortings.None){ + docs.sort((docA, docB) => { + + const [typeA, typeB] = (() => { switch (sortType) { case cardSortings.Time: - return [DateCast(docA.author_date)?.date ?? Date.now(), - DateCast(docB.author_date)?.date ?? Date.now()]; + return [DateCast(docA.author_date)?.date ?? Date.now(), + DateCast(docB.author_date)?.date ?? Date.now()]; case cardSortings.Color: - return [DashColor(StrCast(docA.backgroundColor)).hsv().toString(), // If docA.type is undefined, use an empty string - DashColor(StrCast(docB.backgroundColor)).hsv().toString()]; // If docB.type is undefined, use an empty string + return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)), + ClientUtils.hexToHsv(StrCast(docB.backgroundColor))]; case cardSortings.Custom: - return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA)??0, - CollectionCardView.getButtonGroup(this.cardSort_customField, docB)??0]; - default: return [StrCast(docA.type), // If docA.type is undefined, use an empty string - StrCast(docB.type)]; // If docB.type is undefined, use an empty string - } // prettier-ignore + return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA) ?? 9999, + CollectionCardView.getButtonGroup(this.cardSort_customField, docB) ?? 9999]; + case cardSortings.Chat: + return [NumCast(docA.chat) ?? 9999, + NumCast(docB.chat) ?? 9999] + + default: + return [StrCast(docA.type), StrCast(docB.type)] + } })(); - + + // console.log(`Sorting ${sortType}: ${typeA} vs ${typeB}`); const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0; - return isDesc ? -out : out; // Reverse the sort order if descending is true - }); + // console.log(`Comparison result: ${out} (isDesc: ${isDesc})`); + + if (isDesc){ + return out + } + return -out + + }); + // } + + if (dragIndex != -1) { + const draggedDoc = DragManager.docsBeingDragged[0]; + const originalIndex = docs.findIndex(doc => doc === draggedDoc); + + docs.splice(originalIndex, 1); + docs.splice(dragIndex, 0, draggedDoc); + } + return docs; }; + + + displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => ( <DocumentView // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} ref={action((r: DocumentView) => r?.ContentDiv && this._docRefs.set(doc, r))} - Document={doc} + Document={doc} NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={returnFalse} @@ -273,10 +365,13 @@ export class CollectionCardView extends CollectionSubView() { LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot - isContentActive={this.isChildContentActive} + isContentActive={emptyFunction} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight(doc)} + dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType} + dontHideOnDrag + // pointerEvents={this.blockPointerEventsWhenDragging(doc)} /> ); @@ -286,11 +381,11 @@ export class CollectionCardView extends CollectionSubView() { * @returns */ overflowAmCardsCalc = (index: number) => { - if (this.inactiveDocs().length < this._maxRowCount) { - return this.inactiveDocs().length; + if (this.sortedDocs.length < this._maxRowCount) { + return this.sortedDocs.length; } // 13 - 3 = 10 - const totalCards = this.inactiveDocs().length; + const totalCards = this.sortedDocs.length; // if 9 or less if (index < totalCards - (totalCards % 10)) { return this._maxRowCount; @@ -323,21 +418,17 @@ export class CollectionCardView extends CollectionSubView() { * @returns */ calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { - if (isSelected) return 50 * this.fitContentScale; - const trans = isHovered ? this.translateHover(realIndex) : 0; + const rowHeight = this._props.PanelHeight() * this.fitContentScale / this.numRows; + const rowIndex = Math.trunc(realIndex / this._maxRowCount); + const rowToCenterShift = (this.numRows / 2) - rowIndex; + if (isSelected) return rowToCenterShift * rowHeight - rowHeight / 2; + if (amCards == 1) return 50 * this.fitContentScale; + // const trans = isHovered ? this.translateHover(realIndex) : 0; + const trans = 0; return trans + this.translateY(amCards, calcRowIndex, realIndex); }; - /** - * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt - * @param childPairIndex - * @param buttonID - * @param doc - */ - toggleButton = undoable((buttonID: number, doc: Doc) => { - this.cardSort_customField && (doc[this.cardSort_customField] = buttonID); - }, 'toggle custom button'); - + /** * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words. * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. This string is @@ -355,6 +446,7 @@ export class CollectionCardView extends CollectionSubView() { }; const docTextPromises = this.childDocsWithoutLinks.map(async doc => { const docText = (await docToText(doc)) ?? ''; + doc['gptInputText'] = docText this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc); return `======${docText.replace(/\n/g, ' ').trim()}======`; }); @@ -378,6 +470,8 @@ export class CollectionCardView extends CollectionSubView() { return response; // Return the response from gptImageLabel } catch (error) { console.log('bad things have happened'); + + console.log(error); } return ''; }; @@ -386,28 +480,44 @@ export class CollectionCardView extends CollectionSubView() { * Converts the gpt output into a hashmap that can be used for sorting. lists are seperated by ==== while elements within the list are seperated by ~~~~~~ * @param gptOutput */ - processGptOutput = (gptOutput: string) => { + @action processGptOutput = (gptOutput: string) => { + + console.log("HIIII") + console.log(StrCast(this.Document.cardSort) + "cardSort") // Split the string into individual list items const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); + // console.log(listItems + " LISTT"); + + // Debug: print the map contents + // console.log("Map contents:", Array.from(this._textToDoc.entries())); + listItems.forEach((item, index) => { - // Split the item by '~~~~~~' to get all descriptors - const parts = item.split('~~~~~~').map(part => part.trim()); - - parts.forEach(part => { - // Find the corresponding Doc in the textToDoc map - const doc = this._textToDoc.get(part); - if (doc) { - doc.chat = index; - } - }); + // Normalize the item (trim whitespace) + const normalizedItem = item.trim(); + // console.log("Normalized item:", normalizedItem); + + // Find the corresponding Doc in the textToDoc map + const doc = this._textToDoc.get(normalizedItem); + // console.log("DOC:", doc); + // console.log("ITEM:", normalizedItem); + + if (doc) { + doc.chat = index; + } else { + console.warn(`No matching document found for item: ${normalizedItem}`); + } }); - }; + + } + + // ); + // }; /** * Opens up the chat popup and starts the process for smart sorting. */ openChatPopup = async () => { GPTPopup.Instance.setVisible(true); - GPTPopup.Instance.setMode(GPTPopupMode.SORT); + GPTPopup.Instance.setMode(GPTPopupMode.CARD); const sortDesc = await this.childPairStringList(); // Await the promise to get the string result GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded GPTPopup.Instance.setSortDesc(sortDesc.join()); @@ -416,40 +526,111 @@ export class CollectionCardView extends CollectionSubView() { /** * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups - * @param childPairIndex * @param doc + * @param cardSort * @returns */ - renderButtons = (doc: Doc, cardSort: cardSortings) => { - if (cardSort !== cardSortings.Custom) return ''; - const amButtons = Math.max(4, this.childDocs?.reduce((set, d) => this.cardSort_customField && set.add(NumCast(d[this.cardSort_customField])), new Set<number>()).size ?? 0); - const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc); - const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6; + renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => { + // if (cardSort !== cardSortings.Custom) return null; + + const amButtons = 4 + + // const amButtons = Math.max( + // 4, + // this.childDocs?.reduce((set, d) => { + // if (this.cardSort_customField) { + // set.add(NumCast(d[this.cardSort_customField])); + // } + // return set; + // }, new Set<number>()).size ?? 0 + // ); + + // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc); + + const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6; + + const iconMap: { [key: number]: any } = { + 0: 'star', + 1: 'heart', + 2: 'cloud', + 3: 'bolt' + }; + return ( - <div className="card-button-container" style={{ width: `${totalWidth}px` }}> + <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}> {numberRange(amButtons).map(i => ( - // eslint-disable-next-line jsx-a11y/control-has-associated-label - <button - key={i} - type="button" - style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} // - onClick={() => this.toggleButton(i, doc)} - /> + <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}> + <button type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}> + {this.getButtonIcon(doc, iconMap[i])} + </button> + </Tooltip> ))} </div> ); }; + + /** + * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt + * @param childPairIndex + * @param buttonID + * @param doc + */ + toggleButton = undoable((doc: Doc, icon: string) => { + + + + // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID); + + // doc.cardSort_activeIcons = new List<string>() + + + // const list = StrListCast(doc.cardSort_activeIcons); + // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); + + BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true + + + + // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID]) + }, 'toggle card tag'); + + + getButtonIcon = (doc: Doc, icon: any): JSX.Element => { + + // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon) + const isActive = doc[icon] + + // console.log(StrListCast(doc.cardSort_activeIcons)) + const color = isActive ? '#4476f7' : '#323232'; + + return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />; + }; + /** * Actually renders all the cards */ renderCards = () => { const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc)); + const isEmpty = this.childDocsWithoutLinks.length === 0; + const isDesc = BoolCast(this.Document.cardSort_isDesc) + + console.log(this.childDocsWithoutLinks.length + "length") + + if (isEmpty) { + return ( + <span className="no-card-span" style={{ width: ` ${this._props.PanelWidth()}px`, height: ` ${this._props.PanelHeight()}px` }}> + Sorry ! There are no cards in this group + </span> + ); + } + // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { - const realIndex = this.sortedDocs.filter(sortDoc => !DocumentView.SelectedDocs().includes(sortDoc)).indexOf(doc); + const realIndex = this.sortedDocs.indexOf(doc); const calcRowIndex = this.overflowIndexCalc(realIndex); const amCards = this.overflowAmCardsCalc(realIndex); const isSelected = DocumentView.SelectedDocs().includes(doc); + const isDragging = DragManager.docsBeingDragged.includes(doc); const childScreenToLocal = () => { this._forceChildXf; @@ -460,6 +641,12 @@ export class CollectionCardView extends CollectionSubView() { .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore }; + const translateIfSelected = () => { + const indexInRow = index % this._maxRowCount; + const rowIndex = Math.trunc(index / this._maxRowCount); + const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount)/2; + return (rowCenterIndex - indexInRow) * 100 - 50; + } return ( <div key={doc[Id]} @@ -472,17 +659,17 @@ export class CollectionCardView extends CollectionSubView() { SnappingManager.SetIsResizing(undefined); this._forceChildXf = !this._forceChildXf; }), - 700 + 900 ); }} style={{ width: this.panelWidth(), - height: 'max-content', // this.panelHeight(childPair.layout)(), + height: 'max-content', transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px) - translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px) + translateX(calc(${(isSelected ? translateIfSelected() : 0) + "% + " + this.translateOverflowX(realIndex, amCards)+"px"})) rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) - scale(${isSelected ? 1.25 : 1})`, - }} + scale(${isSelected ? 2 : this._hoveredNodeIndex === index ? 1.05 : 1})`, + }} onMouseEnter={() => this.setHoveredNodeIndex(index)}> {this.displayDoc(doc, childScreenToLocal)} {this.renderButtons(doc, this.cardSort)} @@ -490,11 +677,17 @@ export class CollectionCardView extends CollectionSubView() { ); }); }; + render() { + const isEmpty = this.childDocsWithoutLinks.length === 0; + const transformValue = `scale(${1 / this.fitContentScale})`; + const heightValue = `${100 * this.fitContentScale}%`; + return ( <div + onPointerMove={e => this.onPointerMove(e)} className="collectionCardView-outer" - ref={this.createDashEventsTarget} + ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), @@ -502,8 +695,9 @@ export class CollectionCardView extends CollectionSubView() { <div className="card-wrapper" style={{ - transform: ` scale(${1 / this.fitContentScale}) translateX(${this.translateWrapperX}px)`, - height: `${100 * this.fitContentScale}%`, + ...(!isEmpty && { transform: transformValue }), + ...(!isEmpty && { height: heightValue }), + gridAutoRows: `${100 / this.numRows}%` }} onMouseLeave={() => this.setHoveredNodeIndex(-1)}> {this.renderCards()} @@ -511,4 +705,5 @@ export class CollectionCardView extends CollectionSubView() { </div> ); } + } diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 27c85533f..38f681e87 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -17,6 +17,7 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView } from './CollectionSubView'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 2adad68e0..4884db709 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -33,7 +33,7 @@ enum practiceVal { export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore - get starField() { return this.fieldKey + "_star"; } // prettier-ignore + get starField() { return "star"; } // prettier-ignore constructor(props: any) { super(props); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 73179a266..2a36e96bf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -58,7 +58,7 @@ export class CollectionDockingView extends CollectionSubView() { return this._goldenLayout._maximisedItem !== null; } private _goldenLayout: any = null; - static _highlightStyleSheet: any = addStyleSheet(); + static _highlightStyleSheet = addStyleSheet(); constructor(props: any) { super(props); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 3eb3008c4..b2f0280a5 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -17,7 +17,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; @@ -47,7 +47,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { private _docBtnRef = React.createRef<HTMLDivElement>(); - constructor(props: any) { + constructor(props: CollectionMenuProps) { super(props); makeObservable(this); CollectionMenu.Instance = this; @@ -279,8 +279,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu initialize: (button: Doc) => { const activeDash = Doc.ActiveDashboard; if (activeDash) { - button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined; - button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined; + button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as ObjectField) : undefined; + button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs) : undefined; } }, }; @@ -338,15 +338,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : ''); }); - @undoBatch - viewChanged = (e: React.ChangeEvent) => { - const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto); - target._type_collection = (e.target as any).selectedOptions[0].value; - }; - - commandChanged = (e: React.ChangeEvent) => { + commandChanged = (e: React.ChangeEvent<HTMLSelectElement>) => { runInAction(() => { - this._currentKey = (e.target as any).selectedOptions[0].value; + this._currentKey = e.target.selectedOptions[0].value; }); }; @@ -485,7 +479,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi }; @action - onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + onKeyChange = (e: React.FormEvent<Element>, { newValue }: { newValue: string }) => { this._currentKey = newValue; }; @@ -538,7 +532,6 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi autosuggestProps: { inputProps: { value: this._currentKey, - // @ts-ignore onChange: this.onKeyChange, }, getSuggestionValue: this.getSuggestionValue, @@ -733,7 +726,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu onChange={this.changeCompactType} value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}> {['vertical', 'horizontal', 'none'].map(type => ( - <option className="collectionGridViewChrome-viewOption" onPointerDown={stopPropagation} value={type}> + <option key={type} className="collectionGridViewChrome-viewOption" onPointerDown={stopPropagation} value={type}> {this.resize ? type[0].toUpperCase() + type.substring(1) : 'Compact: ' + type} </option> ))} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index c39df2c76..285598600 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -52,7 +52,6 @@ export type collectionTreeViewProps = { export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() { public static AddTreeFunc = 'addTreeFolder(this.embedContainer)'; private _treedropDisposer?: DragManager.DragDropDisposer; - private _mainEle?: HTMLDivElement; private _titleRef?: HTMLDivElement | HTMLInputElement | null; private _disposers: { [name: string]: IReactionDisposer } = {}; private _isDisposing = false; // notes that instance is in process of being disposed @@ -83,8 +82,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @observable _titleHeight = 0; // height of the title bar - MainEle = () => this._mainEle; - // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; whenChildContentsActiveChanged = action((isActive: boolean) => { @@ -134,7 +131,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree }; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - this._mainEle = ele; if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this)); }; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index b82421e6b..f69aea2a7 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -469,14 +469,12 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return false; } - refTransform = (ref: HTMLDivElement | undefined | null) => { + refTransform = (ref: HTMLElement | undefined | null) => { if (!ref) return this.ScreenToLocalTransform(); - const { translateX, translateY } = ClientUtils.GetScreenTransform(ref); - const outerXf = ClientUtils.GetScreenTransform(this.treeView.MainEle()); - const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.ScreenToLocalTransform().translate(offset[0], offset[1]); + const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(ref); + return new Transform(-translateX, -translateY, 1).scale(1/scale); }; - docTransform = () => this.refTransform(this._dref?.ContentRef?.current); + docTransform = () => this.refTransform(this._dref?.ContentDiv); getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b6e1fca77..5b7f09be3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,7 +9,8 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; -import { ActiveEraserWidth, ActiveInkWidth, Doc, DocListCast, Field, FieldType, Opt, SetActiveInkColor, SetActiveInkWidth } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveEraserWidth, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveInkColor, SetActiveInkWidth } from '../../nodes/DocumentView'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField'; @@ -38,7 +39,7 @@ import { ContextMenu } from '../../ContextMenu'; import { InkingStroke } from '../../InkingStroke'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; -import { DocumentView } from '../../nodes/DocumentView'; +import { ActiveFillColor, DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -87,8 +88,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } private _clusters = new CollectionFreeFormClusters(this); - private _oldWheel: any; - private _panZoomTransitionTimer: any; + private _oldWheel: HTMLDivElement | null = null; + private _panZoomTransitionTimer: NodeJS.Timeout | undefined = undefined; + private _brushtimer: NodeJS.Timeout | undefined = undefined; + private _brushtimer1: NodeJS.Timeout | undefined = undefined; private _lastX: number = 0; private _lastY: number = 0; private _downX: number = 0; @@ -97,8 +100,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _disposers: { [name: string]: IReactionDisposer } = {}; private _renderCutoffData = observable.map<string, boolean>(); private _batch: UndoManager.Batch | undefined = undefined; - private _brushtimer: any; - private _brushtimer1: any; private _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation. private _presEaseFunc: string = 'ease'; @@ -129,7 +130,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _eraserX: number = 0; @observable _eraserY: number = 0; @observable _showEraserCircle: boolean = false; // to determine whether the radius eraser should show - constructor(props: any) { + constructor(props: collectionFreeformViewProps) { super(props); makeObservable(this); } @@ -550,7 +551,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _width: B.width + inkWidth, _height: B.height + inkWidth, stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore - inkWidth + inkWidth, + ActiveInkColor(), + ActiveInkBezierApprox(), + ActiveFillColor(), + ActiveArrowStart(), + ActiveArrowEnd(), + ActiveDash(), + ActiveIsInkMask() ); if (Doc.ActiveTool === InkTool.Write) { this.unprocessedDocs.push(inkDoc); @@ -634,7 +642,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _width: B.width + inkWidth, _height: B.height + inkWidth, stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore - inkWidth + inkWidth, + ActiveInkColor(), + ActiveInkBezierApprox(), + ActiveFillColor(), + ActiveArrowStart(), + ActiveArrowEnd(), + ActiveDash(), + ActiveIsInkMask() ); }); newStrokes && this.addDocument?.(newStrokes); @@ -684,8 +699,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return false; }; - forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => { - this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text)); + forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData) => { + this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points))); }; onPointerMove = (e: PointerEvent) => { @@ -1639,7 +1654,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection * since rendering a large collection of documents can be slow, at startup, docs are rendered in batches. * each doc's render() method will call the cutoff provider which will let the doc know if it should render itself yet, or wait */ - renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + ''))); + renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc || this.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + ''))); doEngineLayout( poolData: Map<string, PoolData>, @@ -1673,7 +1688,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection .forEach(entry => elements.push({ ele: this.getChildDocView(entry[1]), - bounds: (entry[1].opacity === 0 ? { ...entry[1], width: 0, height: 0 } : { ...entry[1] }) as any, + bounds: (entry[1].opacity === 0 ? { payload:undefined, type:"", ...entry[1], width: 0, height: 0 } : { payload:undefined, type:"",...entry[1] }), inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, }) ); @@ -1795,10 +1810,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection Object.values(this._disposers).forEach(disposer => disposer?.()); } - updateIcon = () => - UpdateIcon( + updateIcon = () => { + const contentDiv = this.DocumentView?.().ContentDiv; + contentDiv && UpdateIcon( this.layoutDoc[Id] + '-icon' + new Date().getTime(), - this.DocumentView?.().ContentDiv!, + contentDiv, NumCast(this.layoutDoc._width), NumCast(this.layoutDoc._height), this._props.PanelWidth(), @@ -1813,6 +1829,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.dataDoc.icon_nativeHeight = nativeHeight; } ); + } @action onCursorMove = (e: React.PointerEvent) => { @@ -1993,7 +2010,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection incrementalRender = action(() => { if (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.())) { const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); - const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5; + const loadIncrement = this.Document.isTemplateDoc || this.Document.isTemplateForField ? Number.MAX_VALUE : 5; for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) { this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true); } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 7c2cfd15f..6bea53355 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -32,6 +32,7 @@ import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore export const FInfotoColType: { [key: string]: ColumnType } = { diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 7730ed385..a48e3f9d9 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -1,23 +1,7 @@ import { Colors } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { aggregateBounds } from '../../../Utils'; -import { - ActiveFillColor, - ActiveEraserWidth, - ActiveInkColor, - ActiveInkHideTextLabels, - ActiveInkWidth, - ActiveIsInkMask, - Doc, - DocListCast, - Opt, - SetActiveFillColor, - SetActiveInkColor, - SetActiveInkHideTextLabels, - SetActiveInkWidth, - SetActiveIsInkMask, - SetEraserWidth, -} from '../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; @@ -31,16 +15,35 @@ import { GestureOverlay } from '../GestureOverlay'; import { InkingStroke } from '../InkingStroke'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocumentView } from '../nodes/DocumentView'; +import { + ActiveEraserWidth, + ActiveFillColor, + ActiveInkColor, + ActiveInkHideTextLabels, + ActiveInkWidth, + ActiveIsInkMask, + DocumentView, + SetActiveFillColor, + SetActiveInkColor, + SetActiveInkHideTextLabels, + SetActiveInkWidth, + SetActiveIsInkMask, + SetEraserWidth, +} from '../nodes/DocumentView'; import { ImageBox } from '../nodes/ImageBox'; import { VideoBox } from '../nodes/VideoBox'; import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { NumListCast } from '../../../fields/Doc'; +import { NumListCast, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; - +import { CollectionViewType } from '../../documents/DocumentTypes'; // import { InkTranscription } from '../InkTranscription'; - +import { Docs } from '../../documents/Documents'; +import { CollectionSubView } from '../collections/CollectionSubView'; +import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; +import { PropertiesView } from '../PropertiesView'; +import { MainView } from '../MainView'; +import { SnappingManager } from '../../util/SnappingManager'; // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function IsNoneSelected() { return DocumentView.Selected().length <= 0; @@ -59,7 +62,7 @@ ScriptingGlobals.add(function setView(view: string, getSelected: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { const selectedViews = DocumentView.Selected(); - if (Doc.ActiveTool !== InkTool.None) { + if (Doc.ActiveTool !== InkTool.None && !selectedViews.lastElement()?.Document._layout_isSvg) { if (checkResult) { return ActiveFillColor(); } @@ -137,20 +140,25 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { const selected = DocumentView.SelectedDocs().lastElement(); + + function isAttrFiltered(attr: string) { + return StrListCast(selected._childFilters).some(filter => filter.includes(attr)); + } + // prettier-ignore - const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', + const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat', { waitForRender?: boolean; checkResult: (doc: Doc) => any; setDoc: (doc: Doc, dv: DocumentView) => void; }> = new Map([ ['grid', { - checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), - setDoc: (doc:Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; }, + checkResult: (doc: Doc) => BoolCast(doc?._freeform_backgroundGrid, false), + setDoc: (doc: Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; }, }], ['snaplines', { - checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false), - setDoc: (doc:Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; }, + checkResult: (doc: Doc) => BoolCast(doc?._freeform_snapLines, false), + setDoc: (doc: Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; }, }], ['viewAll', { checkResult: (doc: Doc) => BoolCast(doc?._freeform_fitContentsToBox, false), @@ -161,13 +169,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' }, }], ['center', { - checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false), - setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; }, + checkResult: (doc: Doc) => BoolCast(doc?._stacking_alignCenter, false), + setDoc: (doc: Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; }, }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire - checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false), - setDoc: (doc:Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; }, + checkResult: (doc: Doc) => BoolCast(doc?._freeform_useClusters, false), + setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; }, }], ['flashcards', { checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), @@ -175,77 +183,289 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' }], ['time', { checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "time", - setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "time", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "time" ? doc.cardSort = '' : doc.cardSort = 'time', }], ['docType', { checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "type", - setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "type", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "type" ? doc.cardSort = '' : doc.cardSort = 'type', }], ['color', { checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color", - setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "color", - }], - ['links', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links", - setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color', }], - ['like', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "like", + // ['heart', { + // checkResult: (doc: Doc) => isAttrFiltered('heart'), + // setDoc: (doc: Doc, dv: DocumentView) => { + // isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match'); + + // } + // }], + // ['star', { + // checkResult: (doc: Doc) => isAttrFiltered('star'), + + // setDoc: (doc: Doc, dv: DocumentView) => { + // isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match'); + // } + // }], + // ['bolt', { + // checkResult: (doc: Doc) => isAttrFiltered('bolt'), + // setDoc: (doc: Doc, dv: DocumentView) => { + // isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match'); + + // } + // }], + // ['cloud', { + // checkResult: (doc: Doc) => isAttrFiltered('cloud'), + // setDoc: (doc: Doc, dv: DocumentView) => { + // isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match'); + + // } + // }], + ['chat', { + checkResult: (doc: Doc) => { + + if (StrCast(doc?.cardSort) === "chat"){ + return true + }} , setDoc: (doc: Doc, dv: DocumentView) => { - doc.cardSort = "custom"; - doc.cardSort_customField = "like"; - doc.cardSort_visibleSortGroups = new List<number>(); - } + doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat'; + }, }], - ['star', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "star", + ['up', { + checkResult: (doc: Doc) => BoolCast(!doc?.cardSort_isDesc), setDoc: (doc: Doc, dv: DocumentView) => { - doc.cardSort = "custom"; - doc.cardSort_customField = "star"; - doc.cardSort_visibleSortGroups = new List<number>(); - } + doc.cardSort_isDesc = false; + }, }], - ['idea', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "idea", + ['down', { + checkResult: (doc: Doc) => BoolCast(doc?.cardSort_isDesc), setDoc: (doc: Doc, dv: DocumentView) => { - doc.cardSort = "custom"; - doc.cardSort_customField = "idea"; - doc.cardSort_visibleSortGroups = new List<number>(); - } + doc.cardSort_isDesc = true; + }, }], - ['chat', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "chat", + ['toggle-chat', { + checkResult: (doc: Doc) => GPTPopup.Instance.visible, setDoc: (doc: Doc, dv: DocumentView) => { - doc.cardSort = "custom"; - doc.cardSort_customField = "chat"; - doc.cardSort_visibleSortGroups = new List<number>(); + GPTPopup.Instance.setVisible(!GPTPopup.Instance.visible); + GPTPopup.Instance.setMode(GPTPopupMode.SORT); + }, }], - ]); - for (let i = 0; i < 8; i++) { - map.set((i + 1 + '') as any, { - checkResult: (doc: Doc) => NumListCast(doc?.cardSort_visibleSortGroups).includes(i), + ['pile', { + checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform, setDoc: (doc: Doc, dv: DocumentView) => { - const list = NumListCast(doc.cardSort_visibleSortGroups); - doc.cardSort_visibleSortGroups = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]); + doc._type_collection = CollectionViewType.Freeform; + const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), { + _width: 250, + _height: 200, + _layout_fitWidth: false, + _layout_autoHeight: true, + }); + + + const iconMap: { [key: number]: any } = { + 0: 'star', + 1: 'heart', + 2: 'cloud', + 3: 'bolt' + }; + + for (let i=0; i<4; i++){ + if (isAttrFiltered(iconMap[i])){ + newCol[iconMap[i]] = true + } + } + + newCol && dv.ComponentView?.addDocument?.(newCol); + DocumentView.showDocument(newCol, { willZoomCentered: true }) + }, - }); - } + }], + ]); if (checkResult) { return map.get(attr)?.checkResult(selected); } + const filters = StrListCast(selected._childFilters).concat(StrListCast(selected?._childFiltersByRanges).filter((filter, i) => !(i % 3))); + + // console.log(filters.some(filter => filter.includes('star'))+ "SUOOOOPPP") + const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv)); setTimeout(() => batch.end(), 100); return undefined; }); -ScriptingGlobals.add(function cardHasLabel(label: string) { + +ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) { const selected = DocumentView.SelectedDocs().lastElement(); - const labelNum = Number(label) - 1; - return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum)); -}, ''); + + function isAttrFiltered(attr: string) { + return StrListCast(selected._childFilters).some(filter => filter.includes(attr)); + } + + if (checkResult) { + return value=== 'opts' ? PropertiesView.Instance.openFilters : isAttrFiltered(value) + } + + if (value != 'opts'){ + isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match'); + } + else { + SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0); + SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0); + + PropertiesView.Instance.CloseAll() + PropertiesView.Instance.openFilters = true + } + + + return undefined; + }, ''); + + + +// ScriptingGlobals.add(function cardHasLabel(label: string) { +// const selected = DocumentView.SelectedDocs().lastElement(); +// const labelNum = Number(label) - 1; +// return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum)); +// }, ''); + +// ScriptingGlobals.add(function setCardSort(attr: "Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3", value?: any, checkResult?: boolean) { +// // const editorView = RichTextMenu.Instance?.TextView?.EditorView; +// const selected = DocumentView.SelectedDocs().lastElement(); + +// // prettier-ignore +// const map: Map<"Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3", { checkResult: (doc: Doc) => any; setDoc: (doc: Doc) => void;}> = new Map([ +// ['Time', { + +// checkResult: (doc: Doc) => {StrCast(doc?.cardSort); +// console.log(StrCast(doc?.cardSort + "card sort"))}, +// setDoc: (doc: Doc) => {doc.cardSort = "time" +// console.log("hewwo")} + +// , +// }], +// ['Type', { +// checkResult: (doc: Doc) => StrCast(doc?.cardSort), +// setDoc: (doc: Doc) => doc.cardSort = "type", +// }], +// ['Color', { +// checkResult: (doc: Doc) => StrCast(doc?.cardSort), +// setDoc: (doc: Doc) => doc.cardSort = "color", +// }], +// // ['links', { +// // checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links", +// // setDoc: (doc: Doc) => doc.cardSort = "links", +// // }], +// ['Custom 1', { +// checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 1", +// setDoc: (doc: Doc) => { +// doc.cardSort = "custom"; +// doc.cardSort_customField = "like"; +// doc.cardSort_visibleSortGroups = new List<number>(); +// } +// }], +// ['Custom 2', { +// checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 2", +// setDoc: (doc: Doc) => { +// doc.cardSort = "custom"; +// doc.cardSort_customField = "star"; +// doc.cardSort_visibleSortGroups = new List<number>(); +// } +// }], +// ['Custom 3', { +// checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 3", +// setDoc: (doc: Doc) => { +// doc.cardSort = "custom"; +// doc.cardSort_customField = "idea"; +// doc.cardSort_visibleSortGroups = new List<number>(); +// } +// }], +// ['ChatGPT', { +// checkResult: (doc: Doc) => StrCast(doc?.cardSort_customField), +// setDoc: (doc: Doc) => { +// doc.cardSort = "custom"; +// doc.cardSort_customField = "chat"; +// doc.cardSort_visibleSortGroups = new List<number>(); +// }, +// }], +// ]); + +// for (let i = 0; i < 8; i++) { +// map.set((i + 1 + '') as any, { +// checkResult: (doc: Doc) => NumListCast(doc?.cardSort_visibleSortGroups).includes(i), +// setDoc: (doc: Doc) => { +// const list = NumListCast(doc.cardSort_visibleSortGroups); +// doc.cardSort_visibleSortGroups = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]); +// }, +// }); +// } + +// if (checkResult) { +// console.log(attr + "attricute") +// console.log(map.get(attr)?.checkResult(selected) + "check result") +// return map.get(attr)?.checkResult(selected); +// } + +// console.log(attr + "attricute lol") + +// // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; +// DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc)); +// // setTimeout(() => batch.end(), 100); +// return undefined; + +// // map.get(attr)?.setDoc?.(); +// // return undefined; +// }); + +// ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) { +// // const editorView = RichTextMenu.Instance?.TextView?.EditorView; +// const selected = DocumentView.SelectedDocs().lastElement(); +// if (checkResult) { +// // console.log(attr + "attricute") +// // console.log(map.get(attr)?.checkResult(selected) + "check result") +// console.log(StrCast(selected?.cardSort) + 'check'); +// const hi = StrCast(selected?.cardSort); +// return StrCast(selected?.cardSortForDropDown) ?? 'Time'; +// } +// function docFields(doc: Doc): void { +// switch (value) { +// case 'Custom 1': +// doc.cardSort_customField = 'like'; +// break; +// case 'Custom 2': +// doc.cardSort_customField = 'star'; +// break; +// case 'Custom 3': +// doc.cardSort_customField = 'idea'; +// break; +// case 'Chat GPT': +// doc.cardSort = 'custom'; +// doc.cardSort_customField = 'chat'; +// break; +// default: +// break; +// } + +// doc.cardSort_visibleSortGroups = new List<number>(); +// } + +// // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; +// DocumentView.Selected().map(dv => { +// dv.Document.cardSortForDropDown = value; + +// if (value != 'Chat GPT') { +// dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase(); +// } +// docFields(dv.Document); +// }); + +// return undefined; + +// // map.get(attr)?.setDoc?.(); +// // return undefined; +// }); // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) { // // const editorView = RichTextMenu.Instance?.TextView?.EditorView; @@ -313,6 +533,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh ]); if (checkResult) { + // console.log(map.get(attr)?.checkResult() + "font check result") return map.get(attr)?.checkResult(); } map.get(attr)?.setDoc?.(); @@ -450,15 +671,19 @@ function setActiveTool(tool: InkTool | Gestures, keepPrim: boolean, checkResult? GestureOverlay.Instance.InkShape = tool as Gestures; } } else if (tool) { - if ([InkTool.StrokeEraser, InkTool.RadiusEraser, InkTool.SegmentEraser].includes(tool as any)) { - Doc.UserDoc().activeEraserTool = tool; - } - // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { + if (Doc.UserDoc().ActiveTool === tool) { Doc.ActiveTool = InkTool.None; } else { - Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = undefined; + if ([InkTool.StrokeEraser, InkTool.RadiusEraser, InkTool.SegmentEraser].includes(tool as any)) { + Doc.UserDoc().activeEraserTool = tool; + } + // pen or eraser + if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { + Doc.ActiveTool = InkTool.None; + } else { + Doc.ActiveTool = tool as any; + GestureOverlay.Instance.InkShape = undefined; + } } } else { Doc.ActiveTool = InkTool.None; diff --git a/src/client/views/nodes/ChatBox/MessageComponent.scss b/src/client/views/nodes/ChatBox/MessageComponent.scss new file mode 100644 index 000000000..6fcc0e5e7 --- /dev/null +++ b/src/client/views/nodes/ChatBox/MessageComponent.scss @@ -0,0 +1,10 @@ +MessageComponent-citation { + color: lightblue; + vertical-align: super; + font-size: smaller; +} +MessageComponent-file_path { + color: lightblue; + vertical-align: baseline; + font-size: inherit; +} diff --git a/src/client/views/nodes/ChatBox/MessageComponent.tsx b/src/client/views/nodes/ChatBox/MessageComponent.tsx index fced0b4d5..f27a18891 100644 --- a/src/client/views/nodes/ChatBox/MessageComponent.tsx +++ b/src/client/views/nodes/ChatBox/MessageComponent.tsx @@ -1,11 +1,25 @@ +/* eslint-disable jsx-a11y/control-has-associated-label */ /* eslint-disable react/require-default-props */ -import React from 'react'; -import { observer } from 'mobx-react'; import { MathJax, MathJaxContext } from 'better-react-mathjax'; +import { observer } from 'mobx-react'; +import React from 'react'; +import * as Tb from 'react-icons/tb'; import ReactMarkdown from 'react-markdown'; -import { TbCircle0Filled, TbCircle1Filled, TbCircle2Filled, TbCircle3Filled, TbCircle4Filled, TbCircle5Filled, TbCircle6Filled, TbCircle7Filled, TbCircle8Filled, TbCircle9Filled } from 'react-icons/tb'; +import './MessageComponent.scss'; import { AssistantMessage } from './types'; +const TbCircles = [ + Tb.TbCircleNumber0Filled, + Tb.TbCircleNumber1Filled, + Tb.TbCircleNumber2Filled, + Tb.TbCircleNumber3Filled, + Tb.TbCircleNumber4Filled, + Tb.TbCircleNumber5Filled, + Tb.TbCircleNumber6Filled, + Tb.TbCircleNumber7Filled, + Tb.TbCircleNumber8Filled, + Tb.TbCircleNumber9Filled, +]; interface MessageComponentProps { message: AssistantMessage; toggleToolLogs: (index: number) => void; @@ -17,89 +31,41 @@ interface MessageComponentProps { isCurrent?: boolean; } -const MessageComponent: React.FC<MessageComponentProps> = function ({ message, toggleToolLogs, expandedLogIndex, goToLinkedDoc, index, showModal, setCurrentFile, isCurrent = false }) { - // const messageClass = `${message.role} ${isCurrent ? 'current-message' : ''}`; - - const LinkRenderer = ({ href, children }: { href: string; children: React.ReactNode }) => { - // console.log(href + " " + children) - const regex = /([a-zA-Z0-9_.!-]+)~~~(citation|file_path)/; - const matches = href.match(regex); - // console.log(href) - // console.log(matches) - const url = matches ? matches[1] : href; - const linkType = matches ? matches[2] : null; - if (linkType === 'citation') { - switch (children) { - case '0': - children = <TbCircle0Filled />; - break; - case '1': - children = <TbCircle1Filled />; - break; - case '2': - children = <TbCircle2Filled />; - break; - case '3': - children = <TbCircle3Filled />; - break; - case '4': - children = <TbCircle4Filled />; - break; - case '5': - children = <TbCircle5Filled />; - break; - case '6': - children = <TbCircle6Filled />; - break; - case '7': - children = <TbCircle7Filled />; - break; - case '8': - children = <TbCircle8Filled />; - break; - case '9': - children = <TbCircle9Filled />; - break; - default: - break; - } - } - // console.log(linkType) - const style = { - color: 'lightblue', - verticalAlign: linkType === 'citation' ? 'super' : 'baseline', - fontSize: linkType === 'citation' ? 'smaller' : 'inherit', - }; - - return ( - <a +const LinkRendererWrapper = (goToLinkedDoc: (url: string) => void, showModal: () => void, setCurrentFile: (file: { url: string }) => void) => + function LinkRenderer({ href, children }: { href?: string; children?: React.ReactNode }) { + const Children = TbCircles[Number(children)]; // pascal case variable needed to convert IconType to JSX.Element tag + const [, aurl, linkType] = href?.match(/([a-zA-Z0-9_.!-]+)~~~(citation|file_path)/) ?? [undefined, href, null]; + const renderType = (content: JSX.Element | null, click: (url: string) => void):JSX.Element => ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + <a className={`MessageComponent-${linkType}`} href="#" onClick={e => { e.preventDefault(); - if (linkType === 'citation') { - goToLinkedDoc(url); - } else if (linkType === 'file_path') { - showModal(); - setCurrentFile({ url }); - } - }} - style={style}> - {children} - </a> - ); + aurl && click(aurl); + }}> + {content} + </a> + ); // prettier-ignore + switch (linkType) { + case 'citation': return renderType(<Children />, (url: string) => goToLinkedDoc(url)); + case 'file_path': return renderType(null, (url: string) => { showModal(); setCurrentFile({ url }); }); + default: return null; + } // prettier-ignore }; +const MessageComponent: React.FC<MessageComponentProps> = function ({ message, toggleToolLogs, expandedLogIndex, goToLinkedDoc, index, showModal, setCurrentFile, isCurrent = false }) { + // const messageClass = `${message.role} ${isCurrent ? 'current-message' : ''}`; return ( <div className={`message ${message.role}`}> <MathJaxContext> <MathJax dynamic hideUntilTypeset="every"> - <ReactMarkdown components={{ a: LinkRenderer }}>{message.text ? message.text : ''}</ReactMarkdown> + <ReactMarkdown components={{ a: LinkRendererWrapper(goToLinkedDoc, showModal, setCurrentFile) }}>{message.text}</ReactMarkdown> </MathJax> </MathJaxContext> {message.image && <img src={message.image} alt="" />} <div className="message-footer"> {message.tool_logs && ( - <button className="toggle-logs-button" onClick={() => toggleToolLogs(index)}> + <button type="button" className="toggle-logs-button" onClick={() => toggleToolLogs(index)}> {expandedLogIndex === index ? 'Hide Code Interpreter Logs' : 'Show Code Interpreter Logs'} </button> )} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 034a38e9c..ee67dd305 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -20,6 +20,11 @@ import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldViewProps } from './FieldView'; import { OpenWhere } from './OpenWhere'; +export enum GroupActive { // flags for whether a view is activate because of its relationship to a group + group = 'group', // this is a group that is activated because it's on an active canvas, but is not part of some other group + child = 'child', // this is a group child that is activated because its containing group is activated + inactive = 'inactive', // this is a group child but it is not active +} /// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface const freeFormPropsKeys = ['x', 'y', 'z', 'zIndex', 'rotation', 'opacity', 'backgroundColor', 'color', 'highlight', 'width', 'height', 'autoDim', 'transition']; @@ -274,7 +279,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF if (this._props.isAnyChildContentActive()) return undefined; const backColor = this.BackgroundColor; const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent'); - return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined; + return isGroup ? (this._props.isDocumentActive?.() ? GroupActive.group : + this._props.isGroupActive?.() ? GroupActive.child : GroupActive.inactive) : + this._props.isGroupActive?.() ? GroupActive.child : undefined; // prettier-ignore }; localRotation = () => this._props.rotation; render() { @@ -313,6 +320,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) { +ScriptingGlobals.add(function gotoFrame(doc: Doc, newFrame: number) { CollectionFreeFormDocumentView.gotoKeyFrame(doc, newFrame); }); diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index 093b9c004..08d9e6010 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -49,7 +49,6 @@ left: 0; height: 100%; overflow: hidden; - transition: 200ms; .beforeBox-cont { height: 100%; @@ -63,7 +62,6 @@ width: 3px; display: inline-block; background: white; - transition: 200ms; .slide-handle { position: absolute; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index adb380f12..efaf6807a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -30,41 +30,23 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return FieldView.LayoutString(ComparisonBox, fieldKey); } private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined]; + private _closeRef = React.createRef<HTMLDivElement>(); + @observable _inputValue = ''; + @observable _outputValue = ''; + @observable _loading = false; + @observable _errorMessage = ''; + @observable _outputMessage = ''; + @observable _animating = ''; + constructor(props: FieldViewProps) { super(props); makeObservable(this); } - @observable inputValue = ''; - @observable outputValue = ''; - @observable loading = false; - @observable errorMessage = ''; - @observable outputMessage = ''; - - @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { - this.inputValue = e.target.value; - console.log(this.inputValue); - }; - - @observable _animating = ''; - - @computed get clipWidth() { - return NumCast(this.layoutDoc[this.clipWidthKey], 50); - } - get clipWidthKey() { - return '_' + this._props.fieldKey + '_clipWidth'; - } - - @computed get clipHeight() { - return NumCast(this.layoutDoc[this.clipHeightKey], 200); - } - get clipHeightKey() { - return '_' + this._props.fieldKey + '_clipHeight'; - } - componentDidMount() { this._props.setContentViewBox?.(this); } + protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => { this._disposers[disposerId]?.(); if (ele) { @@ -72,7 +54,19 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } }; - private internalDrop = undoable((e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { + @computed get revealOp() { return this.layoutDoc[`_${this.fieldKey}_revealOp`] as ('flip'|'hover'|undefined); } // prettier-ignore + @computed get clipWidth() { return NumCast(this.layoutDoc[`_${this.fieldKey}_clipWidth`], 50); } // prettier-ignore + set clipWidth(width: number) { this.layoutDoc[`_${this.fieldKey}_clipWidth`] = width; } // prettier-ignore + @computed get useAlternate() { return this.layoutDoc[`_${this.fieldKey}_usePath`] === 'alternate'; } // prettier-ignore + set useAlternate(alt: boolean) { this.layoutDoc[`_${this.fieldKey}_usePath`] = alt ? 'alternate' : undefined; } // prettier-ignore + + animateClipWidth = action((clipWidth: number, duration = 200 /* ms */) => { + this._animating = `all ${duration}ms`; // turn on clip animation transition, then turn it off at end of animation + setTimeout(action(() => { this._animating = ''; }), duration); // prettier-ignore + this.clipWidth = clipWidth; + }); + + internalDrop = undoable((e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { if (dropEvent.complete.docDragData) { const { droppedDocuments } = dropEvent.complete.docDragData; const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey)); @@ -84,45 +78,34 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return undefined; }, 'internal drop'); - private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => { + registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => { if (e.button !== 2) { setupMoveUpEvents( this, e, this.onPointerMove, emptyFunction, - action((moveEv, doubleTap) => { + action((clickEv, doubleTap) => { if (doubleTap) { this._isAnyChildContentActive = true; if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + // DocumentView.addViewRenderedCb(DocCast(this.dataDoc[this.fieldKey + '_1']), dv => { + // dv?.select(false); + // }); } }), - false, + true, undefined, - action(() => { - if (this._isAnyChildContentActive) return; - this._animating = 'all 200ms'; - // on click, animate slider movement to the targetWidth - this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); - // this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight(); - - setTimeout( - action(() => { - this._animating = ''; - }), - 200 - ); - }) + () => !this._isAnyChildContentActive && this.animateClipWidth((targetWidth * 100) / this._props.PanelWidth()) ); } }; - @action - private onPointerMove = ({ movementX }: PointerEvent) => { + onPointerMove = ({ movementX }: PointerEvent) => { const width = movementX * this.ScreenToLocalBoxXf().Scale + (this.clipWidth / 100) * this._props.PanelWidth(); - if (width && width > 5 && width < this._props.PanelWidth()) { - this.layoutDoc[this.clipWidthKey] = (width * 100) / this._props.PanelWidth(); + if (width > 5 && width < this._props.PanelWidth()) { + this.clipWidth = (width * 100) / this._props.PanelWidth(); } return false; }; @@ -180,8 +163,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); }; docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => { - if (property === StyleProp.PointerEvents) return 'none'; - return this._props.styleProvider?.(doc, props, property); + switch (property) { + case StyleProp.PointerEvents: return 'none'; + default: return this._props.styleProvider?.(doc, props, property); + } // prettier-ignore }; moveDoc1 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true); moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); @@ -220,36 +205,31 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return layoutTemplateString; }; - _closeRef = React.createRef<HTMLDivElement>(); - /** * Flips a flashcard to the alternate side for the user to view. */ flipFlashcard = () => { - const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; + this.useAlternate = !this.useAlternate; }; /** * Changes the view option to hover for a flashcard. */ - hoverFlip = (side: string | undefined) => { - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; + hoverFlip = (alternate: boolean) => { + if (this.revealOp === 'hover') this.useAlternate = alternate; }; /** * Creates the button used to flip the flashcards. */ @computed get overlayAlternateIcon() { - const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( <Tooltip title={<div className="dash-tooltip">flip</div>}> <div className="formattedTextBox-alternateButton" onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { - console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); - if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'flip') { + if (!this.revealOp || this.revealOp === 'flip') { this.flipFlashcard(); console.log('Print Front of cards: ' + (RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text ?? '')); console.log('Print Back of cards: ' + (RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text ?? '')); @@ -257,8 +237,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() }) } style={{ - background: usepath === 'alternate' ? 'white' : 'black', - color: usepath === 'alternate' ? 'black' : 'white', + background: this.useAlternate ? 'white' : 'black', + color: this.useAlternate ? 'black' : 'white', }}> <FontAwesomeIcon icon="turn-up" size="sm" /> </div> @@ -268,14 +248,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @action handleRenderGPTClick = () => { // Call the GPT model and get the output - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; - this.outputValue = ''; - if (this.inputValue) this.askGPT(); + this.useAlternate = true; + this._outputValue = ''; + if (this._inputValue) this.askGPT(); }; @action handleRenderClick = () => { // Call the GPT model and get the output - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; + this.useAlternate = false; }; /** @@ -285,7 +265,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() askGPT = async (): Promise<string | undefined> => { const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); - const queryText = questionText + ' UserAnswer: ' + this.inputValue + '. ' + rubricText; + const queryText = questionText + ' UserAnswer: ' + this._inputValue + '. ' + rubricText; try { const res = await gptAPICall(queryText, GPTCallType.QUIZ); @@ -293,8 +273,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() console.error('GPT call failed'); return; } - this.outputValue = res; - console.log(res); + this._outputValue = res; } catch (err) { console.error('GPT call failed'); } @@ -355,7 +334,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); if (this.Document._layout_isFlashcard) { - const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; + const side = this.useAlternate ? 1 : 0; // add text box to each side when comparison box is first created if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] === 'empty')) { @@ -383,17 +362,19 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */} <div className="input-box"> <textarea - value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.outputValue : this.inputValue} - onChange={this.handleInputChange} - readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'} + value={this.useAlternate ? this._outputValue : this._inputValue} + onChange={action(e => { + this._inputValue = e.target.value; + })} + readOnly={this.useAlternate} /> </div> - <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'none' : 'flex' }}> + <div className="submit-button" style={{ display: this.useAlternate ? 'none' : 'flex' }}> <button type="button" onClick={this.handleRenderGPTClick}> Submit </button> </div> - <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'flex' : 'none' }}> + <div className="submit-button" style={{ display: this.useAlternate ? 'flex' : 'none' }}> <button type="button" onClick={this.handleRenderClick}> Edit Your Response </button> @@ -407,12 +388,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */ style={{ display: 'flex', flexDirection: 'column' }} - onMouseEnter={() => { - this.hoverFlip('alternate'); - }} - onMouseLeave={() => { - this.hoverFlip(undefined); - }}> + onMouseEnter={() => this.hoverFlip(true)} + onMouseLeave={() => this.hoverFlip(false)}> {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)} {this.overlayAlternateIcon} </div> @@ -430,9 +407,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() className="slide-bar" style={{ left: `calc(${this.clipWidth + '%'} - 0.5px)`, + transition: this._animating, cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, }} - onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ + onPointerDown={e => this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ > <div className="slide-handle" /> </div> diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index a1deb1625..d2e82284e 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -18,6 +18,7 @@ import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; import './Chart.scss'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore interface TableBoxProps { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9ff96c692..f09e0c73d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -43,6 +43,7 @@ import { ObserverJsxParser } from '../ObservableReactComponent'; import { PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { ViewBoxInterface } from '../ViewBoxInterface'; +import { GroupActive } from './CollectionFreeFormDocumentView'; import { DocumentContentsView } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; @@ -52,7 +53,7 @@ import { OpenWhere, OpenWhereMod } from './OpenWhere'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails/PresEnums'; import SpringAnimation from './trails/SlideEffect'; -import { SpringSettings, SpringType, springMappings } from './trails/SpringUtils'; +import { SpringType, springMappings } from './trails/SpringUtils'; interface Window { MediaRecorder: MediaRecorder; @@ -153,7 +154,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document @computed get disableClickScriptFunc() { const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; - return (DocumentView.LongPress || + return (SnappingManager.LongPress || onScriptDisable === 'always' || (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))); // prettier-ignore } @@ -297,17 +298,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document e.stopPropagation(); }; onClick = action((e: React.MouseEvent | React.PointerEvent) => { - if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; - const documentView = this._docView; - if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + if (this._props.isGroupActive?.() === GroupActive.child && !this._props.isDocumentActive?.()) return; + if (this._docView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; - !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document); const scriptProps = { this: this.Document, _readOnly_: false, scriptContext: this._props.scriptContext, - documentView, + documentView: this._docView, clientX: e.clientX, clientY: e.clientY, shiftKey: e.shiftKey, @@ -317,44 +316,39 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }; if (this._doubleTap) { const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick; - if (this.onDoubleClickHdlr?.script) { - UndoManager.RunInBatch(() => this.onDoubleClickHdlr.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title); - } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') { - UndoManager.RunInBatch(() => this._props.addDocTab(this.Document, OpenWhere.lightboxAlways), 'double tap'); - DocumentView.DeselectAll(); - Doc.UnBrushDoc(this.Document); - } else { - this._singleClickFunc?.(); - } + undoable(() => { + if (this.onDoubleClickHdlr?.script) { + this.onDoubleClickHdlr.script.run(scriptProps, console.log).result?.select && this._props.select(false); + } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') { + this._props.addDocTab(this.Document, OpenWhere.lightboxAlways); + DocumentView.DeselectAll(); + Doc.UnBrushDoc(this.Document); + } else this._singleClickFunc?.(); + }, 'on double click: ' + this.Document.title)(); this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = undefined; this._singleClickFunc = undefined; } else { - let clickFunc: undefined | (() => any); - if (!this.disableClickScriptFunc && this.onClickHdlr?.script) { - clickFunc = undoable(() => { - this.onClickHdlr?.script.run(scriptProps, console.log).result?.select && this._props.select(false); - }, 'click ' + this.Document.title); - } else { - // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part - if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) { - stopPropagate = false; - } - preventDefault = false; - } - const sendToBack = e.altKey ? () => documentView._props.bringToFront?.(this.Document, true) : undefined; + const sendToBack = e.altKey ? () => this._props.bringToFront?.(this.Document, true) : undefined; const selectFunc = () => { + !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document); // selecting a view that is part of a template proxies the selection back to the root of the template const templateRoot = !(e.ctrlKey || e.button > 0) && this._props.docViewPath?.().reverse().find(dv => !dv._props.TemplateDataDocument); // prettier-ignore (templateRoot || this._docView)?.select(e.ctrlKey || e.shiftKey, e.metaKey); }; - this._singleClickFunc = clickFunc ?? sendToBack ?? selectFunc; - const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; - if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { + const clickFunc = this.onClickFunc?.()?.script ? () => (this.onClickFunc?.()?.script.run(scriptProps, console.log).result as Opt<{ select: boolean }>)?.select && this._props.select(false) : undefined; + if (!clickFunc) { + // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part + if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false; + preventDefault = false; + } + this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.title); + const waitForDblClick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; + if ((clickFunc && waitForDblClick !== 'never') || waitForDblClick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); // eslint-disable-next-line no-use-before-define - } else if (!DocumentView.LongPress) { + } else if (!SnappingManager.LongPress) { this._singleClickFunc(); this._singleClickFunc = undefined; } @@ -365,9 +359,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }); onPointerDown = (e: React.PointerEvent): void => { - if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; + if (this._props.isGroupActive?.() === GroupActive.child && !this._props.isDocumentActive?.()) return; // eslint-disable-next-line no-use-before-define - this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000); + this._longPressSelector = setTimeout(() => SnappingManager.LongPress && this._props.select(false), 1000); if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView; this._downX = e.clientX; @@ -418,7 +412,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected } // eslint-disable-next-line no-use-before-define - if (DocumentView.LongPress) e.preventDefault(); + if (SnappingManager.LongPress) e.preventDefault(); }; toggleFollowLink = undoable((): void => { @@ -975,8 +969,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document * @returns a function that will wrap a JSX animation element wrapping any JSX element */ public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) { - let dir = (presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) as PresEffectDirection; - const transitionTime = presEffectDoc?.presentation_transition ? NumCast(presEffectDoc?.presentation_transition) : 500; + const dir = ((presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) || PresEffectDirection.Center) as PresEffectDirection; + const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)); const effectProps = { left: dir === PresEffectDirection.Left, right: dir === PresEffectDirection.Right, @@ -984,26 +978,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document bottom: dir === PresEffectDirection.Bottom, opposite: true, delay: 0, - duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)), + duration, }; const timing = StrCast(presEffectDoc?.presentation_effectTiming); - let timingConfig: SpringSettings | undefined; - if (timing) { - timingConfig = JSON.parse(timing); - } - - if (!timingConfig) { - timingConfig = { - type: SpringType.GENTLE, - ...springMappings.gentle, - }; - } - - if (!dir) { - dir = PresEffectDirection.Center; - } - + const timingConfig = (timing ? JSON.parse(timing) : undefined) ?? { + type: SpringType.GENTLE, + ...springMappings.gentle, + }; switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { case PresEffect.Expand: return <SpringAnimation doc={root} startOpacity={0} dir={dir} presEffect={PresEffect.Expand} springSettings={timingConfig}>{renderDoc}</SpringAnimation> case PresEffect.Flip: return <SpringAnimation doc={root} startOpacity={0} dir={dir} presEffect={PresEffect.Flip} springSettings={timingConfig}>{renderDoc}</SpringAnimation> @@ -1085,15 +1067,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done. ) => Promise<void>; public static linkCommonAncestor: (link: Doc) => DocumentView | undefined; - // pin func + /** + * Pins a Doc to the current presentation trail. (see TabDocView for implementation) + */ public static PinDoc: (docIn: Doc | Doc[], pinProps: PinProps) => void; - // gesture - public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to. - // media playing - @observable public static CurrentlyPlaying: DocumentView[] = []; + /** + * The DocumentView below the cursor at the start of a gesture (that receives the pointerDown event). Used by GestureOverlay to determine the doc a gesture should apply to. + */ + public static DownDocView: DocumentView | undefined; public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore - public ContentRef = React.createRef<HTMLDivElement>(); private _htmlOverlayEffect: Opt<Doc>; private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewTimer: NodeJS.Timeout | undefined; @@ -1124,7 +1107,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { @observable private _htmlOverlayText: Opt<string> = undefined; @observable private _isHovering = false; @observable private _selected = false; - @observable public static LongPress = false; + @observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing @computed private get shouldNotScale() { return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.(); @@ -1471,7 +1454,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { {!this.Document || !this._props.PanelWidth() ? null : ( <div className="contentFittingDocumentView-previewDoc" - ref={this.ContentRef} style={{ transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`, @@ -1568,6 +1550,56 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } } +export function ActiveFillColor(): string { + const dv = DocumentView.Selected().lastElement() ?.Document._layout_isSvg ? DocumentView.Selected().lastElement() : undefined; + return StrCast(dv?.Document.fillColor, StrCast(ActiveInkPen()?.activeFillColor, "")); +} // prettier-ignore +export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore +export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore +export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore +export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore +export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore +export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore +export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore +export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore +export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore +export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore +export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth); } // prettier-ignore + +export function SetActiveInkWidth(width: string): void { + !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); +} +export function SetActiveBezierApprox(bezier: string): void { + ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier); +} +export function SetActiveInkColor(value: string) { + ActiveInkPen() && (ActiveInkPen().activeInkColor = value); +} +export function SetActiveIsInkMask(value: boolean) { + ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value); +} +export function SetActiveInkHideTextLabels(value: boolean) { + ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value); +} +export function SetActiveFillColor(value: string) { + ActiveInkPen() && (ActiveInkPen().activeFillColor = value); +} +export function SetActiveArrowStart(value: string) { + ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); +} +export function SetActiveArrowEnd(value: string) { + ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); +} +export function SetActiveArrowScale(value: number) { + ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); +} +export function SetActiveDash(dash: string): void { + !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); +} +export function SetEraserWidth(width: number): void { + ActiveInkPen() && (ActiveInkPen().eraserWidth = width); +} + // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function DocFocusOrOpen(docIn: Doc, optionsIn?: FocusViewOptions, containingDoc?: Doc) { return DocumentView.FocusOrOpen(docIn, optionsIn, containingDoc); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index ffb668b03..8317d62a6 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -192,7 +192,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { } else { text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result; // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - getStyle = (val: string) => ({ fontFamily: val }); + // getStyle = (val: string) => ({ fontFamily: val }); } // Get items to place into the list diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss new file mode 100644 index 000000000..8c0f92c90 --- /dev/null +++ b/src/client/views/nodes/IconTagBox.scss @@ -0,0 +1,30 @@ +@import '../global/globalCssVariables.module.scss'; + +.card-button-container { + display: flex; + padding: 3px; + position: absolute; + // width: 300px; + // height:100px; + pointer-events: none; /* This ensures the container does not capture hover events */ + + background-color: rgb(218, 218, 218); /* Background color of the container */ + border-radius: 50px; /* Rounds the corners of the container */ + transform: translateY(25px); + // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ + align-items: center; /* Centers buttons vertically */ + justify-content: start; /* Centers buttons horizontally */ + + button { + pointer-events: auto; /* Re-enable pointer events for the buttons */ + transform: translateY(-7.5px); + + width: 30px; + height: 30px; + border-radius: 50%; + background-color: $dark-gray; + // border-color: $medium-blue; + margin: 5px; // transform: translateY(-50px); + background-color: transparent; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx new file mode 100644 index 000000000..9a2273c3a --- /dev/null +++ b/src/client/views/nodes/IconTagBox.tsx @@ -0,0 +1,168 @@ +import React from "react"; +import { observer } from "mobx-react"; +import { computed } from "mobx"; + +import { ObservableReactComponent } from "../ObservableReactComponent"; +import { NumCast } from "../../../fields/Types"; +import { makeObservable } from "mobx"; +import { Doc } from "../../../fields/Doc"; +import { Reaction } from "mobx"; +import { reaction } from "mobx"; +import { numberRange } from "../../../Utils"; +import { Tooltip } from "@mui/material"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { undoable } from "../../util/UndoManager"; +import { BoolCast } from "../../../fields/Types"; +import { DocCast } from "../../../fields/Types"; +import './IconTagBox.scss'; +import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../fields/DocSymbols'; + + +export interface IconTagProps { + doc: Doc; + + +} + +@observer +export class IconTagBox extends ObservableReactComponent<IconTagProps> { + private ref: React.RefObject<HTMLDivElement>; + private height: number = 0; + + + @computed + get currentScale() { + return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1); + } + + constructor(props: any) { + super(props); + makeObservable(this); + this.ref = React.createRef(); + } + + componentDidMount(): void { + this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc._keywordHeight = this.height; + + reaction( + () => this.currentScale, + () => { + if (this.currentScale < 1) { + this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc._keywordHeight = this.height; + } + } + ); + } + + componentDidUpdate(prevProps: Readonly<IconTagProps>): void { + this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc._keywordHeight = this.height; + } + + /** + * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups + * @param doc + * @param cardSort + * @returns + */ + renderButtons = (doc: Doc): JSX.Element | null => { + // if (cardSort !== cardSortings.Custom) return null; + + const amButtons = 4 + + // const amButtons = Math.max( + // 4, + // this.childDocs?.reduce((set, d) => { + // if (this.cardSort_customField) { + // set.add(NumCast(d[this.cardSort_customField])); + // } + // return set; + // }, new Set<number>()).size ?? 0 + // ); + + // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc); + + const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6; + + const iconMap: { [key: number]: any } = { + 0: 'star', + 1: 'heart', + 2: 'cloud', + 3: 'bolt' + }; + + return ( + <div + className="card-button-container" + style={{ + transformOrigin: 'top left', + transform: `scale(${1 / this.currentScale}) translateY(${doc[DocData].showLabels ? doc._keywordHeight as number : 0}px)`, + width: `${totalWidth}px`, + fontSize: '50px' + }} + > + {numberRange(amButtons).map(i => ( + <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}> + <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}> + {this.getButtonIcon(doc, iconMap[i])} + </button> + </Tooltip> + ))} + </div> + ); + }; + + /** + * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt + * @param childPairIndex + * @param buttonID + * @param doc + */ + toggleButton = undoable((doc: Doc, icon: string) => { + + + + // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID); + + // doc.cardSort_activeIcons = new List<string>() + + + // const list = StrListCast(doc.cardSort_activeIcons); + // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); + + BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true + + + + // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID]) + }, 'toggle card tag'); + + + getButtonIcon = (doc: Doc, icon: any): JSX.Element => { + + // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon) + const isActive = doc[icon] + + // console.log(StrListCast(doc.cardSort_activeIcons)) + const color = isActive ? '#4476f7' : '#323232'; + + return <FontAwesomeIcon icon={icon} style={{ color , height: '30px', width: '30px'}} />; + }; + + render (){ + return ( + <> + {this.renderButtons(this._props.doc)} + + </> + ) + + } + + + +} + + diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 1c90fae9e..d940a96b9 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -454,6 +454,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options)); + renderedPixelDimensions = async () => { + const { nativeWidth: width, nativeHeight: height } = await Networking.PostToServer('/inspectImage', { source: this.paths[0] }); + return { width, height }; + }; + savedAnnotations = () => this._savedAnnotations; render() { TraceMobx(); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 7a89b143b..8db68ddfe 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -655,9 +655,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); else { if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); - PDFBox.pdfpromise.get(href)?.then((pdf: any) => { - PDFBox.pdfcache.set(href, (this._pdf = pdf)); - }); + PDFBox.pdfpromise.get(href)?.then( + action((pdf: any) => { + PDFBox.pdfcache.set(href, (this._pdf = pdf)); + }) + ); } } return pdfView ?? this.renderTitleBox; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 8835ea5e7..da947face 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -45,6 +45,7 @@ import { LinkInfo } from './LinkDocPreview'; import { OpenWhere } from './OpenWhere'; import './WebBox.scss'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { CreateImage } = require('./WebBoxRenderer'); @observer diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9f2a9b8e1..5b435e44a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -87,9 +87,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection static _globalHighlightsCache: string = ''; static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']); - static _highlightStyleSheet: any = addStyleSheet(); - static _bulletStyleSheet: any = addStyleSheet(); - static _userStyleSheet: any = addStyleSheet(); + static _highlightStyleSheet = addStyleSheet(); + static _bulletStyleSheet = addStyleSheet(); + static _userStyleSheet = addStyleSheet(); static _hadSelection: boolean = false; private _selectionHTML: string | undefined; private _sidebarRef = React.createRef<SidebarAnnos>(); @@ -384,7 +384,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } } } else { - const jsonstring = Cast(dataDoc[this.fieldKey], RichTextField)?.Data!; + const jsonstring = Cast(dataDoc[this.fieldKey], RichTextField)?.Data; if (jsonstring) { const json = JSON.parse(jsonstring); json.selection = state.toJSON().selection; @@ -1925,11 +1925,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB setHeight={this.setSidebarHeight} /> ) : ( - <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}> + <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.(), false), true)}> <ComponentTag // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} - ref={this._sidebarTagRef as any} + ref={this._sidebarTagRef} setContentView={emptyFunction} NativeWidth={returnZero} NativeHeight={returnZero} diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 2f6824466..96f025ca0 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -133,7 +133,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { _layout_autoHeight: true, }); - this.addToCollection?.(newCol); + this.addToCollection?.(newCol); //this._props.addDocument(newCol) }; pointerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 6d8793f82..eaa3eaebf 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -7,10 +7,13 @@ $highlightedText: #82e0ff; .summary-box { position: fixed; - bottom: 10px; - right: 10px; + top: 115px; + left: 75px; width: 250px; + height: 200px; min-height: 200px; + min-width: 180px; + border-radius: 16px; padding: 16px; padding-bottom: 0; @@ -21,6 +24,18 @@ $highlightedText: #82e0ff; background-color: #ffffff; box-shadow: 0 2px 5px #7474748d; color: $textgrey; + resize: both; /* Allows resizing */ + overflow: auto; + + .resize-handle { + width: 10px; + height: 10px; + background: #ccc; + position: absolute; + right: 0; + bottom: 0; + cursor: se-resize; + } .summary-heading { display: flex; @@ -51,15 +66,18 @@ $highlightedText: #82e0ff; .content-wrapper { padding-top: 10px; min-height: 50px; - max-height: 150px; + // max-height: 150px; overflow-y: auto; + height: 100% } .btns-wrapper-gpt { - height: 50px; + height: 100%; display: flex; justify-content: center; align-items: center; + flex-direction: column; + transform: translateY(30px); @@ -78,16 +96,20 @@ $highlightedText: #82e0ff; } - } - button { - font-size: 9px; - padding: 10px; - color: #ffffff; - background-color: $button; - border-radius: 5px; + } + // button { + // font-size: 9px; + // padding: 10px; + // color: #ffffff; + // width: 100%; + // background-color: $button; + // border-radius: 5px; + + // } + .text-btn { &:hover { background-color: $button; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index cb5aad32d..a41c33a4d 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -4,7 +4,7 @@ import { Button, IconButton, Type } from 'browndash-components'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { CgClose } from 'react-icons/cg'; +import { CgClose, CgPathBack, CgArrowLeftO, CgCornerUpLeft } from 'react-icons/cg'; import ReactLoading from 'react-loading'; import { TypeAnimation } from 'react-type-animation'; import { ClientUtils } from '../../../../ClientUtils'; @@ -19,6 +19,9 @@ import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; +import { DocumentView } from '../../nodes/DocumentView'; +import { DocCast } from '../../../../fields/Types'; +import { RTFCast } from '../../../../fields/Types'; export enum GPTPopupMode { SUMMARY, @@ -26,9 +29,16 @@ export enum GPTPopupMode { IMAGE, FLASHCARD, DATA, + CARD, SORT, + QUIZ } + + + + + interface GPTPopupProps {} @observer @@ -149,32 +159,138 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { this.cardsDoneLoading = done; } + @observable sortRespText: string = '' + + @action setSortRespText(resp: string) { + this.sortRespText = resp + } + + + @observable chatSortPrompt: string = "" + + sortPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => { + this.chatSortPrompt = e.target.value; + }); + + + @observable quizAnswer: string = "" + + quizAnswerChanged = action((e: React.ChangeEvent<HTMLInputElement>) => { + this.quizAnswer = e.target.value; + }); + + generateQuiz = async () => { + this.setLoading(true); + this.setSortDone(false); + + + const selected = DocumentView.SelectedDocs().lastElement(); + + const questionText = 'Question: ' + StrCast(selected['gptInputText']); + + if (StrCast(selected['gptRubric']) === '') { + const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected) + } + + const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric'])) + // const rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); + const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; + + try { + const res = await gptAPICall(queryText, GPTCallType.QUIZ); + if (!res) { + console.error('GPT call failed'); + return; + } + console.log(res) + this.setQuizResp(res) + + this.setLoading(false); + this.setSortDone(true); + + // this._outputValue = res; + } catch (err) { + console.error('GPT call failed'); + } + + + } + + generateRubric = async (inputText: string, doc:Doc) => { + try { + const res = await gptAPICall(inputText, GPTCallType.RUBRIC); + console.log(res + "rubbbb") + // if (!res) { + // console.error('GPT call failed'); + // return; + // } + doc['gptRubric']= res; + return res + } catch (err) { + console.error('GPT call failed'); + } + + } + + + + @observable private regenerateCallback: (() => Promise<void>) | null = null; + + @action public setRegenerateCallback(callback: () => Promise<void>) { + this.regenerateCallback = callback; + } + + + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; public createFilteredDoc: (axes?: any) => boolean = () => false; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; + @observable quizRespText: string = '' + + @action setQuizResp (resp: string) { + this.quizRespText = resp + + } + + /** * Sorts cards in the CollectionCardDeckView */ generateSort = async () => { + console.log(this.chatSortPrompt + "USER PROMPT") this.setLoading(true); this.setSortDone(false); + if (this.regenerateCallback) { + await this.regenerateCallback(); + } + try { - const res = await gptAPICall(this.sortDesc, GPTCallType.SORT); + const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); // Trigger the callback with the result if (this.onSortComplete) { this.onSortComplete(res || 'Something went wrong :('); + + // Extract explanation surrounded by ------ at the top or both at the top and bottom + const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || []; + const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found'; + + // Set the extracted explanation to sortRespText + this.setSortRespText(explanation); + console.log(res); } } catch (err) { console.error(err); } - + this.setLoading(false); this.setSortDone(true); }; - + + /** * Generates a Dalle image and uploads it to the server. */ @@ -314,59 +430,158 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { } }; + cardMenu = () => ( + <div className="btns-wrapper-gpt"> + <Button + tooltip="Have ChatGPT sort your cards for you!" + text="Sort Cards!" + onClick={() => this.setMode(GPTPopupMode.SORT)} + color={StrCast(Doc.UserDoc().userVariantColor)} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + marginBottom: '10px' + }} + /> + <Button + tooltip="Test your knowledge with ChatGPT!" + text="Quiz Cards!" + onClick={() => this.setMode(GPTPopupMode.QUIZ)} + color={StrCast(Doc.UserDoc().userVariantColor)} + type={Type.TERT} + style={{ + width: '100%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + height: '40%', + + }} + /> + </div> + ) + + cardActual = (opt: GPTPopupMode) => { + const isSort = opt === GPTPopupMode.SORT + // if (opt === GPTPopupMode.SORT) { + return ( + !this.sortDone ? ( + <> + <div className="btns-wrapper-gpt"> + <input + className="searchBox-input" + defaultValue="" + autoComplete="off" + onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged} + onKeyDown={e => { + if (e.key === 'Enter') { + isSort ? this.generateSort() : this.generateQuiz(); + } + e.stopPropagation(); + }} + type="text" + placeholder={`${isSort ? 'How do you want to sort your cards?' : 'What is the selected card?'}`} + id="search-input" + style={{ width: '100%' }} + /> + {/* </div> + <div className="btns-wrapper-gpt"> */} + <Button + tooltip={`${isSort ? 'HaveChatGPT sort ypur cards for you!' : 'See how close you get to the right answer!'}`} + text={`${isSort ? 'Sort!' : 'Check!'}`} + onClick={isSort ? this.generateSort : this.generateQuiz} + color={StrCast(Doc.UserDoc().userVariantColor)} + type={Type.TERT} + style={{ + width: '100%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + height: '40%', + + }} + /> + </div> + </> + ) : ( + <div> + <div className="content-wrapper"> + <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p> + <IconButton + tooltip="Generate Again" + onClick={() => this.setSortDone(false)} + icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} + color={StrCast(Doc.UserDoc().userVariantColor)} + /> + </div> + </div> + ) + ); + // } else if (opt === GPTPopupMode.QUIZ) { + // return ( + // <> + // <div className="btns-wrapper-gpt"> + // <input + // className="searchBox-input" + // defaultValue="" + // autoComplete="off" + // onChange={this.quizAnswerChanged} + // onKeyDown={e => { + // if (e.key === 'Enter') { + // this.generateQuiz(); + // } + // e.stopPropagation(); + // }} + // type="text" + // placeholder="What is the selected card?" + // id="search-input" + // style={{ width: '100%' }} + // /> + // {/* </div> + // <div className="btns-wrapper-gpt"> */} + // <Button + // tooltip="See how close you got to the right answer!" + // text="Check!" + // onClick={() => this.generateQuiz()} + // color={StrCast(Doc.UserDoc().userVariantColor)} + // type={Type.TERT} + // style={{ + // width: '90%', + // textAlign: 'center', + // color: '#ffffff', + // fontSize: '16px', + // }} + // /> + // </div> + // </> + // ); + // } + }; + sortBox = () => ( - <> - <div> - {this.heading('SORTING')} - {this.loading ? ( + <div> + {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} + <> + {!this.cardsDoneLoading || this.loading ? ( <div className="content-wrapper"> <div className="loading-spinner"> <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> - <span>Loading...</span> + {this.loading ? <span>Loading...</span> : <span>Reading Cards...</span>} </div> </div> ) : ( - <> - {!this.cardsDoneLoading ? ( - <div className="content-wrapper"> - <div className="loading-spinner"> - <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> - <span>Reading Cards...</span> - </div> - </div> - ) : ( - !this.sortDone && ( - <div className="btns-wrapper-gpt"> - <Button - tooltip="Have ChatGPT sort your cards for you!" - text="Sort!" - onClick={this.generateSort} - color={StrCast(Doc.UserDoc().userVariantColor)} - type={Type.TERT} - style={{ - width: '90%', // Almost as wide as the container - textAlign: 'center', - color: '#ffffff', // White text - fontSize: '16px', // Adjust font size as needed - }} - /> - </div> - ) - )} - - {this.sortDone && ( - <div> - <div className="content-wrapper"> - <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : 'Sorting done! Feel free to move things around / regenerate :) !'}</p> - <IconButton tooltip="Generate Again" onClick={() => this.setSortDone(false)} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> - </div> - </div> - )} - </> + (this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(this.mode)) // Call the functions to render JSX )} - </div> - </> + </> + </div> ); + + + imageBox = () => ( <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> {this.heading('GENERATED IMAGE')} @@ -512,15 +727,63 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { heading = (headingText: string) => ( <div className="summary-heading"> <label className="summary-text">{headingText}</label> - {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />} + {this.loading ? ( + <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> + ) : ( + <> + {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && ( + <IconButton + color={StrCast(SettingsManager.userVariantColor)} + tooltip="back" + icon={<CgCornerUpLeft size="16px" />} + onClick={() => this.mode = GPTPopupMode.CARD} + style = {{right: '-20%'}} + /> + )} + <IconButton + color={StrCast(SettingsManager.userVariantColor)} + tooltip="close" + icon={<CgClose size="16px" />} + onClick={() => this.setVisible(false)} + /> + + </> + )} </div> ); + render() { + let content; + + switch (this.mode) { + case GPTPopupMode.SUMMARY: + content = this.summaryBox(); + break; + case GPTPopupMode.DATA: + content = this.dataAnalysisBox(); + break; + case GPTPopupMode.IMAGE: + content = this.imageBox(); + break; + case GPTPopupMode.SORT: + case GPTPopupMode.CARD: + case GPTPopupMode.QUIZ: + content = this.sortBox(); + break; + default: + content = null; + } + return ( - <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}> - {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : null} + <div + className="summary-box" + style={{ display: this.visible ? 'flex' : 'none' }} + > + {content} + <div className="resize-handle" /> </div> ); } + } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6c1617c38..709e9a23c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -24,13 +24,11 @@ import { FieldViewProps } from '../nodes/FieldView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { LinkInfo } from '../nodes/LinkDocPreview'; import { PDFBox } from '../nodes/PDFBox'; -import { ComparisonBox } from '../nodes/ComparisonBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProp'; import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; -import { Docs } from '../../documents/Documents'; import './PDFViewer.scss'; // pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; @@ -58,7 +56,7 @@ interface IViewerProps extends FieldViewProps { */ @observer export class PDFViewer extends ObservableReactComponent<IViewerProps> { - static _annotationStyle: any = addStyleSheet(); + static _annotationStyle = addStyleSheet(); constructor(props: IViewerProps) { super(props); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4abb23404..ac2efbe5c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -174,53 +174,6 @@ export function updateCachedAcls(doc: Doc) { return undefined; } -export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore -export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore -export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ''); } // prettier-ignore -export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore -export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore -export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore -export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore -export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore -export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore -export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore -export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore -export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth); } // prettier-ignore - -export function SetActiveInkWidth(width: string): void { - !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); -} -export function SetActiveBezierApprox(bezier: string): void { - ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier); -} -export function SetActiveInkColor(value: string) { - ActiveInkPen() && (ActiveInkPen().activeInkColor = value); -} -export function SetActiveIsInkMask(value: boolean) { - ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value); -} -export function SetActiveInkHideTextLabels(value: boolean) { - ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value); -} -export function SetActiveFillColor(value: string) { - ActiveInkPen() && (ActiveInkPen().activeFillColor = value); -} -export function SetActiveArrowStart(value: string) { - ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); -} -export function SetActiveArrowEnd(value: string) { - ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); -} -export function SetActiveArrowScale(value: number) { - ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); -} -export function SetActiveDash(dash: string): void { - !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); -} -export function SetEraserWidth(width: number): void { - ActiveInkPen() && (ActiveInkPen().eraserWidth = width); -} - @scriptingGlobal @Deserializable('Doc', updateCachedAcls, ['id']) export class Doc extends RefField { |