aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2025-02-08 14:19:41 -0500
committerbobzel <zzzman@gmail.com>2025-02-08 14:19:41 -0500
commitbb624694da02a56ce10e3bfa4c75957592e3a86d (patch)
tree79e33303fd2921960169704d8810f88fc4cb555a /src
parentd72977ad8b67f2575cad8aea988fcfa7c04f794a (diff)
parent392143a84250df3742bc2f52d358fec25877e4de (diff)
merged
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts49
-rw-r--r--src/client/apis/gpt/PresCustomization.ts115
-rw-r--r--src/client/documents/DocUtils.ts4
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/util/CurrentUserUtils.ts87
-rw-r--r--src/client/util/DocumentManager.ts29
-rw-r--r--src/client/views/MainView.tsx25
-rw-r--r--src/client/views/PropertiesView.tsx70
-rw-r--r--src/client/views/StyleProvider.tsx9
-rw-r--r--src/client/views/TagsView.scss4
-rw-r--r--src/client/views/TagsView.tsx4
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx14
-rw-r--r--src/client/views/collections/CollectionCardDeckView.scss5
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx290
-rw-r--r--src/client/views/collections/CollectionSubView.tsx49
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx3
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx8
-rw-r--r--src/client/views/global/globalScripts.ts75
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx4
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx44
-rw-r--r--src/client/views/nodes/FocusViewOptions.ts11
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx9
-rw-r--r--src/client/views/nodes/IconTagBox.scss2
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.tsx12
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx9
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditor.tsx69
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditorButtons.tsx2
-rw-r--r--src/client/views/nodes/trails/CubicBezierEditor.tsx154
-rw-r--r--src/client/views/nodes/trails/PresBox.scss117
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx1024
-rw-r--r--src/client/views/nodes/trails/SpringUtils.ts9
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx227
-rw-r--r--src/fields/ScriptField.ts9
36 files changed, 1252 insertions, 1302 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index cf3a28a8e..1894bb4df 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -16,10 +16,10 @@ enum GPTCallType {
PRONUNCIATION = 'pronunciation',
DRAW = 'draw',
COLOR = 'color',
- RUBRIC = 'rubric',
- TYPE = 'type',
- SUBSET = 'subset',
- INFO = 'info',
+ RUBRIC = 'rubric', // needs to be filled in below
+ TYPE = 'type', // needs to be filled in below
+ SUBSET = 'subset', // needs to be filled in below
+ INFO = 'info', // needs to be filled in below
TEMPLATE = 'template',
VIZSUM = 'vizsum',
VIZSUM2 = 'vizsum2',
@@ -42,7 +42,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
model: 'gpt-4o',
maxTokens: 2048,
temp: 0.7,
- prompt: 'Create a stack of flashcards out of this text with each question and answer labeled as question and answer. Create a title that represents the question in just a few words and label it "title". For some questions, ask "what is this image of" but tailored to stacks theme and the image and write a keyword that represents the image and label it "keyword". Otherwise, write none. Do not label each flashcard and do not include asterisks.',
+ prompt: 'Create a stack of at least 10 flashcards out of this text with each question and answer labeled as question and answer. Each flashcard should have a title that represents the question in just a few words and label it "title". For some questions, ask "what is this image of" but tailored to stacks theme and the image and write a keyword that represents the image and label it "keyword". Otherwise, write none. 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." },
@@ -121,6 +121,39 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
temp: 0.5,
prompt: 'You will be coloring drawings. You will be given what the drawing is, then a list of descriptions for parts of the drawing. Based on each description, respond with the stroke and fill color that it should be. Follow the rules: 1. Avoid using black for stroke color 2. Make the stroke color 1-3 shades darker than the fill color 3. Use the same colors when possible. Format as {#abcdef #abcdef}, making sure theres a color for each description, and do not include any additional text.',
},
+ type: {
+ model: 'gpt-4-turbo',
+ maxTokens: 1024,
+ temp: 0,
+ prompt: `I'm going to provide you with a question.
+ Based on the question, is the user asking you to
+ 1. Assigns docs with tags(like star / heart etc)/labels,
+ 2. Filter docs,
+ 3. Provide information about a specific doc
+ 4. Provide a specific doc based on a question/information
+ 5. Provide general information
+ 6. Put cards in a specific order.
+ Answer with only the number for 2-6. For number one, provide the number (1) and the appropriate tag`,
+ },
+ subset: {
+ model: 'gpt-4-turbo',
+ maxTokens: 1024,
+ temp: 0,
+ 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 `======`. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. 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 in the 2nd person (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",
+ },
+
+ info: {
+ model: 'gpt-4-turbo',
+ maxTokens: 1024,
+ temp: 0,
+ prompt: "Answer the user's question with a short (<100 word) response. If a particular document is selected I will provide that information (which may help with your response)",
+ },
+ rubric: {
+ model: 'gpt-4-turbo',
+ maxTokens: 1024,
+ temp: 0,
+ prompt: "BRIEFLY (<25 words) provide a definition for the following term. It will be used as a rubric to evaluate the user's understanding of the topic",
+ },
};
let lastCall = '';
let lastResp = '';
@@ -131,8 +164,12 @@ let lastResp = '';
* @returns AI Output
*/
const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: string, dontCache?: boolean) => {
- const inputText = [GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ, GPTCallType.STACK].includes(callType) ? inputTextIn + '.' : inputTextIn;
+ const inputText = inputTextIn + ([GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ, GPTCallType.STACK].includes(callType) ? '.' : '');
const opts = callTypeMap[callType];
+ if (!opts) {
+ console.log('The query type:' + callType + ' requires a configuration.');
+ return 'Error connecting with API.';
+ }
if (lastCall === inputText && dontCache !== true) return lastResp;
try {
lastCall = inputText;
diff --git a/src/client/apis/gpt/PresCustomization.ts b/src/client/apis/gpt/PresCustomization.ts
index 2262886a2..c465f098f 100644
--- a/src/client/apis/gpt/PresCustomization.ts
+++ b/src/client/apis/gpt/PresCustomization.ts
@@ -1,3 +1,5 @@
+import { PresEffect, PresEffectDirection } from '../../views/nodes/trails/PresEnums';
+import { AnimationSettingsProperties, easeItems } from '../../views/nodes/trails/SpringUtils';
import { openai } from './setup';
export enum CustomizationType {
@@ -10,15 +12,17 @@ interface PromptInfo {
}
const prompts: { [key: string]: PromptInfo } = {
trails: {
- description:
- 'We are customizing the properties and transition of a slide in a presentation. You are given the current properties of the slide in a json with the fields [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection], as well as the prompt for how the user wants to change it. Return a json with the required fields: [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection] by applying the changes in the prompt to the current state of the slide.',
+ description: `We are customizing the properties and transition of a slide in a presentation.
+ You are given the current properties of the slide in a json with the fields [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection],
+ as well as the prompt for how the user wants to change it.
+ Return a json with the required fields: [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection] by applying the changes in the prompt to the current state of the slide.`,
features: [],
},
};
// Allows you to register properties that are customizable
export const addCustomizationProperty = (type: CustomizationType, name: string, description: string, values?: string[]) => {
- values ? prompts[type].features.push({ name, description, values }) : prompts[type].features.push({ name, description });
+ prompts[type].features.push({ name, description, ...(values ? { values } : {}) });
};
// All the registered fields, make sure to update during registration, this
@@ -41,35 +45,34 @@ export const gptSlideProperties = [
// Registers slide properties
const setupPresSlideCustomization = () => {
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'title', 'is the title/name of the slide.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_easeFunc', 'is the easing function for the movement to the slide.', ['Ease', 'Ease In', 'Ease Out', 'Ease Out', 'Ease In Out', 'Linear']);
-
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']);
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effectDirection', 'is what direction the effect is applied.', ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']);
- addCustomizationProperty(
- CustomizationType.PRES_TRAIL_SLIDE,
- 'presentation_effectTiming',
- "is a json object of the format: {type: string, stiffness: number, damping: number, mass: number}. Type is always “custom”. Controls the spring-based timing of the presentation effect animation. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect."
- );
-
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'config_zoom', 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.');
-
- // boolean values
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_playAudio', 'is a boolean value indicating if we should play audio when we go to the slide.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_zoomText', 'is a boolean value indicating if we should zoom into text selections when we go to the slide.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hideBefore', 'is a boolean value indicating if we should hide the slide before going to it.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hide', 'is a boolean value indicating if we should hide the slide during the presentation.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hideAfter', 'is a boolean value indicating if we should hide the slide after going to it.');
- addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_openInLightbox', 'is a boolean value indicating if we should open the slide in an overlay or lightbox view during the presentation.');
-};
+ const add = (name: string, val:string, opts?:string[]) => addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, name, val, opts);
+ const addBool = (name: string, val:string) => add(name, 'is a boolean value indicating if we should ' + val);
+ add('title', 'is the title/name of the slide.');
+ add('config_zoom', 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.');
+ add('presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.');
+ add('presentation_easeFunc', 'is the easing function for the movement to the slide.', easeItems.filter(val => val.text !== 'Custom').map(val => val.text))
+ add('presentation_effect', 'is an effect applied to the slide when we transition to it.', Object.keys(PresEffect));
+ add('presentation_effectDirection','is what direction the effect is applied.', Object.keys(PresEffectDirection).filter(key => key !== PresEffectDirection.None));
+ add('presentation_effectTiming', `is a json object of the format: {type: string, ${AnimationSettingsProperties.stiffness}: number, ${AnimationSettingsProperties.damping}: number, ${AnimationSettingsProperties.mass}: number}.
+ Type is always “custom”. Controls the spring-based timing of the presentation effect animation.
+ Stiffness, damping, and mass control the physics-based properties of spring animations.
+ This is used to create a more natural looking timing, bouncy effects, etc.
+ Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect.`);
+
+
+ addBool('presentation_playAudio', 'play audio when we go to the slide.');
+ addBool('presentation_zoomText', 'zoom into text selections when we go to the slide.');
+ addBool('presentation_hideBefore', 'hide the slide before going to it.');
+ addBool('presentation_hide', 'hide the slide during the presentation.');
+ addBool('presentation_hideAfter', 'hide the slide after going to it.');
+ addBool('presentation_openInLightbox', 'open the slide in an overlay or lightbox view during the presentation.');
+}; // prettier-ignore
setupPresSlideCustomization();
-export const getSlideTransitionSuggestions = async (inputText: string) => {
+export const getSlideTransitionSuggestions = (inputText: string) => {
/**
- * Prompt: Generate an entrance animations from slower and gentler
- * to bouncier and more high energy
+ * Prompt: Generate entrance animations from slower and gentler to bouncier and more high energy
*
* Format:
* {
@@ -81,13 +84,19 @@ export const getSlideTransitionSuggestions = async (inputText: string) => {
* }
*/
- const prompt =
- "I want to generate four distinct types of slide effect animations. Return a json of the form {effect: string, direction: string, stiffness: number, damping: number, mass: number}[] with four elements. Effect is the type of animation; its only possible values are ['Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']. Direction is the direction that the animation starts from; its only possible values are ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural-looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to animate the effect.";
+ const prompt = `I want to generate four distinct types of slide effect animations.
+ Return a json of the form { ${AnimationSettingsProperties.effect}: string, ${AnimationSettingsProperties.direction}: string, ${AnimationSettingsProperties.stiffness}: number, ${AnimationSettingsProperties.damping}: number, ${AnimationSettingsProperties.mass}: number}[] with four elements.
+ ${AnimationSettingsProperties.effect} is the type of animation; its only possible values are [${Object.keys(PresEffect).filter(key => key !== PresEffect.None).join(',')}].
+ ${AnimationSettingsProperties.direction} is the direction that the animation starts from;
+ its only possible values are [${Object.values(PresEffectDirection).filter(key => key !== PresEffectDirection.None).join(',')}].
+ ${AnimationSettingsProperties.stiffness}, ${AnimationSettingsProperties.damping}, and ${AnimationSettingsProperties.mass} control the physics-based properties of spring animations.
+ This is used to create a more natural-looking timing, bouncy effects, etc.
+ Use spring physics to adjust these parameters to animate the effect.`; // prettier-ignore
const customInput = inputText ?? 'Make them as contrasting as possible with different effects and timings ranging from gentle to energetic.';
- try {
- const response = await openai.chat.completions.create({
+ return openai.chat.completions
+ .create({
model: 'gpt-4',
messages: [
{ role: 'system', content: prompt },
@@ -95,39 +104,33 @@ export const getSlideTransitionSuggestions = async (inputText: string) => {
],
temperature: 0,
max_tokens: 1000,
+ })
+ .then(response => response.choices[0].message?.content ?? '')
+ .catch(err => {
+ console.log(err);
+ return 'Error connecting with API.';
});
- return response.choices[0].message?.content;
- } catch (err) {
- console.log(err);
- return 'Error connecting with API.';
- }
};
-export const gptTrailSlideCustomization = async (inputText: string, properties: any | any[]) => {
- let prompt = prompts.trails.description;
-
- prompts.trails.features.forEach(feature => {
- prompt += feature.name + ' ' + feature.description;
- if (feature.values) {
- prompt += `Its only possible values are [${feature.values.join(', ')}].`;
- }
- });
+export const gptTrailSlideCustomization = (inputText: string, properties: string) => {
+ const preamble = prompts.trails.description + prompts.trails.features.map(feature => feature.name + ' ' + feature.description + (feature.values ? `Its only possible values are [${feature.values.join(', ')}]` : '')).join('. ');
- prompt += 'Set unchanged values to null and make sure you include new properties if they are specified in the prompt even if they do not exist in current properties. Please only return the json with the keys described and their values.';
+ const prompt = `Set unchanged values to null and make sure you include new properties if they are specified in the prompt even if they do not exist in current properties.
+ Please only return the json with the keys described and their values.`;
- try {
- const response = await openai.chat.completions.create({
+ return openai.chat.completions
+ .create({
model: 'gpt-4',
messages: [
- { role: 'system', content: prompt },
- { role: 'user', content: `Prompt: ${inputText}, Current properties: ${JSON.stringify(properties)}` },
+ { role: 'system', content: preamble + prompt },
+ { role: 'user', content: `Prompt: ${inputText}, Current properties: ${properties}` },
],
temperature: 0,
max_tokens: 1000,
+ })
+ .then(response => response.choices[0].message?.content ?? '')
+ .catch(err => {
+ console.log(err);
+ return 'Error connecting with API.';
});
- return response.choices[0].message?.content;
- } catch (err) {
- console.log(err);
- return 'Error connecting with API.';
- }
};
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index d11a3e235..23032b62e 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -72,9 +72,9 @@ export namespace DocUtils {
}
const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings
if (vals.length) {
- return vals.some(v => typeof v === 'string' && v.includes(value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return vals.some(v => typeof v === 'string' && v === (value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
- return Field.toString(fieldVal as FieldType).includes(value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return Field.toString(fieldVal as FieldType) === (value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
/**
* @param docs
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7f1387ff8..6c2f10652 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -922,7 +922,7 @@ export namespace Docs {
const I = Doc.GetProto(ink);
// I.layout_hideOpenButton = true; // don't show open full screen button when selected
I.color = color;
- I.fillColor = fillColor;
+ I.fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined;
I.stroke = new InkField(points);
I.stroke_width = strokeWidth;
I.stroke_bezier = strokeBezier;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b41fd09dc..0fd09b479 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -40,6 +40,7 @@ import { ColorScheme } from "./SettingsManager";
import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
import { DocumentView } from "../views/nodes/DocumentView";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
export interface Button {
// DocumentOptions fields a button can set
@@ -67,6 +68,21 @@ export interface Button {
subMenu?: Button[];
}
+// Not really necessary, but for now, all tags should start with a capital first letter
+export type TagName<T extends string> =
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ T extends `${infer First}${infer Rest}`
+ ? First extends Uppercase<First>
+ ? First extends Lowercase<First>
+ ? never // If it's the same when uppercased and lowercased, it's not a letter.
+ : T // Otherwise, it's a valid capitalized string.
+ : never
+ : never;
+export function ToTagName(key: string):"Tag"{
+ return ((str => str[0].toUpperCase() + str.slice(1))(key.startsWith('#') ? key.substring(1) : key)) as "Tag";
+}
+
+
export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
@@ -688,16 +704,12 @@ pie title Minerals in my tap water
{ title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"hcenter", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
- static cardTools(): Button[] {
+ static sortTools(): Button[] {
return [
{ 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: "Tags", icon:"bolt", toolTip:"Sort by document's tags", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"tag", 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: "Show Tags", icon:"id-card", toolTip:"Toggle tag annotation panel", width: 45, btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-tags",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
-
{ title: "Sort", icon: "sort" , toolTip: "Manage sort order / lock status", 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_);}'} },
@@ -705,27 +717,33 @@ pie title Minerals in my tap water
]},
]
}
+
+ static filterBtnDesc<T extends string>(tag:TagName<T>|"Tag", icon:IconProp):Button {
+ return { title: tag, isSystem: false, icon: icon.toString(), toolTip:`Click to toggle visibility of ${tag} tagged Docs`, btnType: ButtonType.ToggleButton, expertMode: false, toolType:`#${tag.toLowerCase()}`, funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}
+ }
- static tagGroupTools(): Button[] {
- const defaultTagButtonDescs = [
- { title: "Star", isSystem: false,icon: "star", toolTip:"Click to toggle visibility of Star tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#star", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
- { title: "Like", isSystem: false,icon: "heart", toolTip:"Click to toggle visibility of Like tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#like", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
- { title: "Todo", isSystem: false,icon: "bolt", toolTip:"Click to toggle visibility of Todo tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#todo", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
- { title: "Idea", isSystem: false,icon: "cloud", toolTip:"Click to toggle visibility of Idea tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#idea", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
+ static filterTools(): Button[] {
+ // If there's no active dashboard, then a default set of tags are added, otherwise, the user controls which tags are kept
+ const tagButtonDescs = Doc.UserDoc().activeDashboard ? [] : [
+ this.filterBtnDesc("Star", "star"),
+ this.filterBtnDesc("Like", "heart"),
+ this.filterBtnDesc("Todo", "bolt"),
+ this.filterBtnDesc("Idea", "cloud"),
+ this.filterBtnDesc("Chat", "robot")
];
- // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved
return [
- { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}},
- ...(Doc.UserDoc().activeDashboard ? [] : defaultTagButtonDescs)
+ { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}},
+ ...tagButtonDescs
]
- }
+ }
static viewTools(): Button[] {
return [
- { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Show Tags", icon: "id-card", toolTip: "Toggle tags", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"toggle-tags",funcs: { }, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Fit All", icon: "object-group", toolTip:"Fit Docs to View (double tap to persist)",
+ btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: { hidden: `!SelectedDocType("${CollectionViewType.Freeform}", this.expertMode)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
@@ -814,25 +832,24 @@ pie title Minerals in my tap water
CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear,
CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Schema, CollectionViewType.Stacking,
CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]),
- title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, _readOnly_); }'}},
- { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
- { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
- { title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
- { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
- { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
- { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
- { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
-
- { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}},
+ { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
+ { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
+ { title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
+ { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Chat", icon:"lightbulb", toolTip: "Toggle Chat Assistant",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat", funcs: {}, width: 30, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+ { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.filterTools(), ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
+ { title: "Sort", icon: "Sort", toolTip: "Sort Documents", subMenu: CurrentUserUtils.sortTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
{ title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
{ title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
{ 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: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:DocumentType.COL, 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: "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
@@ -870,7 +887,7 @@ pie title Minerals in my tap water
childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick,
linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
- const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
+ const items = (menuBtn?:Doc) => !menuBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtn) );
const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title),
(opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 286112bf9..acb35f7eb 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -8,7 +8,7 @@ import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
import { CollectionViewType } from '../documents/DocumentTypes';
import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { FocusEffectDelay, FocusViewOptions } from '../views/nodes/FocusViewOptions';
import { OpenWhere } from '../views/nodes/OpenWhere';
import { PresBox } from '../views/nodes/trails';
@@ -352,21 +352,18 @@ export class DocumentManager {
// if there's an options.effect, it will be handled from linkFollowHighlight. We delay the start of
// the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen
// bcz: should this delay be an options parameter?
- setTimeout(
- () => {
- Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
- if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
- // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
- contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
- DocumentManager._overlayViews.add(contextView);
- }
- Doc.AddUnHighlightWatcher(() => {
- docView.Document[Animation] = undefined;
- DocumentManager.removeOverlayViews();
- });
- },
- (options.zoomTime ?? 0) * 0.5
- );
+ setTimeout(() => {
+ Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
+ if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
+ // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
+ contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
+ DocumentManager._overlayViews.add(contextView);
+ }
+ Doc.AddUnHighlightWatcher(() => {
+ docView.Document[Animation] = undefined;
+ DocumentManager.removeOverlayViews();
+ });
+ }, FocusEffectDelay(options));
if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode));
if (options.playAudio) DocumentManager.playAudioAnno(docView.Document);
if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7abca5197..d748b70ae 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -20,7 +20,7 @@ import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
import { CaptureManager } from '../util/CaptureManager';
-import { Button, CurrentUserUtils } from '../util/CurrentUserUtils';
+import { CurrentUserUtils, ToTagName } from '../util/CurrentUserUtils';
import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
import { dropActionType } from '../util/DropActionTypes';
@@ -63,7 +63,6 @@ import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
import { DocButtonState } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
-import { ButtonType } from './nodes/FontIconBox/FontIconBox';
import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
@@ -874,25 +873,9 @@ export class MainView extends ObservableReactComponent<object> {
* @param hotKey tite of the new hotkey
*/
addHotKey = (hotKey: string) => {
- const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
- const filter = DocCast(buttons.Filter);
- const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey;
-
- const newKey: Button = {
- title,
- icon: 'question',
- toolTip: `Click to toggle the ${title}'s group's visibility`,
- btnType: ButtonType.ToggleButton,
- expertMode: false,
- toolType: '#' + title,
- funcs: {},
- scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
- };
-
- const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
- newBtn.isSystem = newBtn[DocData].isSystem = undefined;
-
- Doc.AddToFilterHotKeys(newBtn);
+ const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter);
+ const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
+ Doc.AddToFilterHotKeys(menuDoc);
};
@computed get mainInnerContent() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 42aa6782f..bed96f600 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -119,6 +119,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
// Pres Trails booleans:
@observable openPresTransitions: boolean = true;
@observable openPresProgressivize: boolean = false;
+ @observable openPresMedia: boolean = false;
@observable openPresVisibilityAndDuration: boolean = false;
@observable openAddSlide: boolean = false;
@observable openSlideOptions: boolean = false;
@@ -1936,74 +1937,74 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
</div>
</div>
{!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
+ <div className="propertiesView-section">
<div
- className="propertiesView-presentationTrails-title"
+ className="propertiesView-sectionTitle"
onPointerDown={action(() => {
- this.openPresTransitions = !this.openPresTransitions;
+ this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
})}
style={{
color: SnappingManager.userColor,
- backgroundColor: this.openPresTransitions ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ backgroundColor: SnappingManager.userVariantColor,
}}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Transitions
+ Visibility
<div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
+ <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+ {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
</div>
)}
{!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
+ <div className="propertiesView-section">
<div
- className="propertiesView-presentationTrails-title"
+ className="propertiesView-sectionTitle"
onPointerDown={action(() => {
- this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
+ this.openPresProgressivize = !this.openPresProgressivize;
})}
style={{
color: SnappingManager.userColor,
- backgroundColor: this.openPresVisibilityAndDuration ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ backgroundColor: SnappingManager.userVariantColor,
}}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Visibility
+ Progressivize
<div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
+ <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
+ {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
</div>
)}
{!selectedItem ? null : (
- <div className="propertiesView-presentationTrails">
+ <div className="propertiesView-section">
<div
- className="propertiesView-presentationTrails-title"
+ className="propertiesView-sectionTitle"
onPointerDown={action(() => {
- this.openPresProgressivize = !this.openPresProgressivize;
+ this.openPresMedia = !this.openPresMedia;
})}
style={{
color: SnappingManager.userColor,
- backgroundColor: this.openPresProgressivize ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ backgroundColor: SnappingManager.userVariantColor,
}}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Progressivize
+ Media
<div className="propertiesView-presentationTrails-title-icon">
- <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
+ <FontAwesomeIcon icon={this.openPresMedia ? 'caret-down' : 'caret-right'} size="lg" />
</div>
</div>
- {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+ {this.openPresMedia ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaDropdown}</div> : null}
</div>
)}
{!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
- <div className="propertiesView-presentationTrails">
+ <div className="propertiesView-section">
<div
- className="propertiesView-presentationTrails-title"
+ className="propertiesView-sectionTitle"
onPointerDown={action(() => {
this.openSlideOptions = !this.openSlideOptions;
})}
style={{
color: SnappingManager.userColor,
- backgroundColor: this.openSlideOptions ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ backgroundColor: SnappingManager.userVariantColor,
}}>
- &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
+ {type === DocumentType.AUDIO ? 'file-audio' : 'file-video'}
<div className="propertiesView-presentationTrails-title-icon">
<FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
</div>
@@ -2011,6 +2012,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
{this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
</div>
)}
+ {!selectedItem ? null : (
+ <div className="propertiesView-section">
+ <div
+ className="propertiesView-sectionTitle"
+ onPointerDown={action(() => {
+ this.openPresTransitions = !this.openPresTransitions;
+ })}
+ style={{
+ color: SnappingManager.userColor,
+ backgroundColor: SnappingManager.userVariantColor,
+ }}>
+ Transitions
+ <div className="propertiesView-presentationTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
+ </div>
+ </div>
+ {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+ </div>
+ )}
</div>
);
}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index e825a27d3..bebc9a341 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,7 +1,7 @@
+import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from '@dash/components';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from '@dash/components';
import { action, untracked } from 'mobx';
import { extname } from 'path';
import * as React from 'react';
@@ -10,6 +10,7 @@ import { FaFilter } from 'react-icons/fa';
import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
+import { InkInkTool } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
import { AudioAnnoState } from '../../server/SharedMediaTypes';
@@ -21,11 +22,9 @@ import { TreeSort } from './collections/TreeSort';
import { Colors } from './global/globalEnums';
import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
-import { styleProviderQuiz } from './StyleProviderQuiz';
import { StyleProp } from './StyleProp';
import './StyleProvider.scss';
-import { TagsView } from './TagsView';
-import { InkInkTool } from '../../fields/InkField';
+import { styleProviderQuiz } from './StyleProviderQuiz';
function toggleLockedPosition(doc: Doc) {
UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -394,7 +393,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
</Tooltip>
);
};
- const tags = () => docView?.() ? <TagsView Views={[docView?.()]}/> : null;
return (
<>
@@ -402,7 +400,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
{lock()}
{filter()}
{audio()}
- {tags()}
</>
);
}
diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss
index 303a08e1e..b21d303fb 100644
--- a/src/client/views/TagsView.scss
+++ b/src/client/views/TagsView.scss
@@ -12,7 +12,7 @@
.tagsView-list {
display: flex;
flex-wrap: wrap;
- height: inherit;
+ height: 1;
.iconButton-container {
min-height: unset !important;
}
@@ -58,7 +58,7 @@
}
.tagsView-editing-box {
- margin-top: 8px;
+ margin-top: 20px;
}
.tagsView-input-box {
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 73f404596..b70e21918 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -9,7 +9,7 @@ import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
-import { DocCast, NumCast, StrCast } from '../../fields/Types';
+import { DocCast, StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
import { SnappingManager } from '../util/SnappingManager';
@@ -146,7 +146,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
}
}
}
- doc[DocData].tags = new List<string>((doc[DocData].tags as List<string>).filter(label => label !== tag));
+ doc[DocData].tags = new List<string>(StrListCast(doc[DocData].tags).filter(label => label !== tag));
};
private _ref: React.RefObject<HTMLDivElement>;
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index d9ff21035..15683ebf2 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -16,6 +16,7 @@ import { RegionHelpers } from './Region';
import './Timeline.scss';
import { TimelineOverview } from './TimelineOverview';
import { Track } from './Track';
+import { Id } from '../../../fields/FieldSymbols';
/**
* Timeline class controls most of timeline functions besides individual region and track mechanism. Main functions are
@@ -56,7 +57,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
private DEFAULT_CONTAINER_HEIGHT: number = 330;
private MIN_CONTAINER_HEIGHT: number = 205;
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
@@ -89,7 +90,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
*/
@computed
private get children(): Doc[] {
- const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as any);
+ const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as unknown as DocumentType);
if (annotatedDoc) {
return DocListCast(this._props.Document[Doc.LayoutFieldKey(this._props.Document) + '_annotations']);
}
@@ -272,9 +273,9 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
* for displaying time to standard min:sec
*/
@action
- toReadTime = (time: number): string => {
- time = time / 1000;
- const inSeconds = Math.round(time * 100) / 100;
+ toReadTime = (timeIn: number): string => {
+ const timeSecs = timeIn / 1000;
+ const inSeconds = Math.round(timeSecs * 100) / 100;
const min = Math.floor(inSeconds / 60);
const sec = Math.round((inSeconds % 60) * 100) / 100;
@@ -552,6 +553,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
<div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
{[...this.children, this._props.Document].map(doc => (
<Track
+ key={doc[Id]}
ref={ref => this.mapOfTracks.push(ref)}
timeline={this}
animatedDoc={doc}
@@ -570,7 +572,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
<div className="currentTime">Current: {this.getCurrentTime()}</div>
<div key="timeline_title" className="title-container" ref={this._titleContainer}>
{[...this.children, this._props.Document].map(doc => (
- <div style={{ height: `${this._titleHeight}px` }} className="datapane" onPointerOver={() => Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>
+ <div key={doc[Id]} style={{ height: `${this._titleHeight}px` }} className="datapane" onPointerOver={() => Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>
<p>{StrCast(doc.title)}</p>
</div>
))}
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 5283601bf..79c53db08 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -28,11 +28,14 @@
.collectionCardView-cardwrapper {
display: grid;
- grid-template-columns: repeat(10, 1fr);
transform-origin: left 50%;
align-items: center;
z-index: 0; // so that setting z-index of active card doesn't make it land on top of things outside of the card-wrapper
}
+.collectionCardView-cardSizeDragger {
+ position: absolute;
+ top: 0;
+}
.no-card-span {
position: relative;
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 836a5a2c3..43464e50c 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,15 +1,16 @@
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
-import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero } from '../../../ClientUtils';
+import * as CSS from 'csstype';
+import { ClientUtils, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Animation, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
import { URLField } from '../../../fields/URLField';
import { gptImageLabel } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -18,24 +19,17 @@ import { DragManager } from '../../util/DragManager';
import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
-import { undoable } from '../../util/UndoManager';
+import { undoable, UndoManager } from '../../util/UndoManager';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
import { TagItem } from '../TagsView';
import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FocusViewOptions';
-import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
+import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
import './CollectionCardDeckView.scss';
-import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
-
-enum cardSortings {
- Time = 'time',
- Type = 'type',
- Color = 'color',
- Chat = 'chat',
- Tag = 'tag',
- None = '',
-}
+import { CollectionSubView, docSortings, SubCollectionViewProps } from './CollectionSubView';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { SettingsManager } from '../../util/SettingsManager';
/**
* New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily
@@ -52,17 +46,16 @@ export class CollectionCardView extends CollectionSubView() {
private _oldWheel: HTMLElement | null = null;
private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center)
private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!;
+ private _draggerRef = React.createRef<HTMLDivElement>();
@observable _forceChildXf = 0;
@observable _hoveredNodeIndex = -1;
@observable _docRefs = new ObservableMap<Doc, DocumentView>();
- @observable _maxRowCount = 10;
- @observable _docDraggedIndex: number = -1;
+ @observable _cursor: CSS.Property.Cursor = 'ew-resize';
constructor(props: SubCollectionViewProps) {
super(props);
makeObservable(this);
- this.setRegenerateCallback();
}
protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
this._dropDisposer?.();
@@ -74,36 +67,32 @@ export class CollectionCardView extends CollectionSubView() {
// prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
};
- /**
- * Callback to ensure gpt's text versions of the child docs are updated
- */
- setRegenerateCallback = () => GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
+ @computed get cardWidth() {
+ return NumCast(this.layoutDoc._cardWidth, 50);
+ }
+ @computed get _maxRowCount() {
+ return Math.ceil(this.cardDeckWidth / this.cardWidth);
+ }
/**
* update's gpt's doc-text list and initializes callbacks
*/
- @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, questionType: string, tag?: string) => this.processGptOutput(sortResult, questionType, tag);
- GPTPopup.Instance.onQuizRandom = () => this.quizMode();
- };
+ childPairStringListAndUpdateSortDesc = () =>
+ this.childPairStringList().then(sortDesc => {
+ GPTPopup.Instance.setSortDesc(sortDesc.join());
+ GPTPopup.Instance.onSortComplete = this.processGptOutput;
+ GPTPopup.Instance.onQuizRandom = this.quizMode;
+ });
componentDidMount() {
- this._props.setContentViewBox?.(this);
- this._disposers.sort = reaction(
- () => GPTPopup.Instance.visible,
- isVis => {
- if (isVis) {
- this.openChatPopup();
- } else {
- this.Document.card_sort = this.cardSort === cardSortings.Chat ? '' : this.Document.card_sort;
- }
- }
+ this._disposers.chatVis = reaction(
+ () => GPTPopup.Instance.Visible,
+ vis => !vis && this.onGptHide()
);
+ GPTPopup.Instance.setRegenerateCallback(this.Document, this.childPairStringListAndUpdateSortDesc);
+ this._props.setContentViewBox?.(this);
// if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles
- // when inquired from the dom (below in childScreenToLocal). When the doc is actually renders, we need to act like the
+ // when inquired from the dom (below in childScreenToLocal). When the doc is actually rendered, we need to act like the
// dash data just changed and trigger a React involidation with the correct data (read from the dom).
this._disposers.child = reaction(
() => [this.Document.x, this.Document.y],
@@ -121,19 +110,21 @@ export class CollectionCardView extends CollectionSubView() {
);
}
+ onGptHide = () => Doc.setDocFilter(this.Document, 'tags', '#chat', 'remove');
componentWillUnmount() {
+ GPTPopup.Instance.setSortDesc('');
+ GPTPopup.Instance.onSortComplete = undefined;
+ GPTPopup.Instance.onQuizRandom = undefined;
+ GPTPopup.Instance.setRegenerateCallback(undefined, null);
Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
this._dropDisposer?.();
}
- @computed get cardSort() {
- return StrCast(this.Document.card_sort) as cardSortings;
- }
/**
* Number of rows of cards to be rendered
*/
@computed get numRows() {
- return Math.ceil(this.sortedDocs.length / this._maxRowCount);
+ return Math.ceil(this.childDocs.length / this._maxRowCount);
}
/**
* Circle arc size, in radians, to layout cards
@@ -167,12 +158,19 @@ export class CollectionCardView extends CollectionSubView() {
return this._props.NativeDimScaling?.() || 1;
}
+ @computed get xMargin() {
+ return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth()));
+ }
+
+ @computed get cardDeckWidth() {
+ return this._props.PanelWidth() - 2 * this.xMargin;
+ }
+
/**
* When in quiz mode, randomly selects a document
*/
quizMode = () => {
- const randomIndex = Math.floor(Math.random() * this.childDocs.length);
- this.layoutDoc._card_curDoc = this.childDocs[randomIndex];
+ this.layoutDoc._card_curDoc = this.childDocs[Math.floor(Math.random() * this.childDocs.length)];
};
setHoveredNodeIndex = action((index: number) => {
@@ -193,7 +191,7 @@ export class CollectionCardView extends CollectionSubView() {
* @returns the card's new index
*/
findCardDropIndex = (mouseX: number, mouseY: number) => {
- const cardCount = this.sortedDocs.length;
+ const cardCount = this.childDocs.length;
let index = 0;
const cardWidth = cardCount < this._maxRowCount ? this._props.PanelWidth() / cardCount : this._props.PanelWidth() / this._maxRowCount;
@@ -227,8 +225,8 @@ export class CollectionCardView extends CollectionSubView() {
*/
@action
onPointerMove = (x: number, y: number) => {
- if (DragManager.docsBeingDragged.some(doc => this.sortedDocs.includes(doc)) || SnappingManager.CanEmbed) {
- this._docDraggedIndex = this.findCardDropIndex(x, y);
+ if (DragManager.docsBeingDragged.some(doc => this.childDocs.includes(doc)) || SnappingManager.CanEmbed) {
+ this.docDraggedIndex = this.findCardDropIndex(x, y);
}
};
@@ -241,14 +239,14 @@ export class CollectionCardView extends CollectionSubView() {
onInternalDrop = undoable(
action((e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
- const dragIndex = this._docDraggedIndex;
+ const dragIndex = this.docDraggedIndex;
const draggedDoc = DragManager.docsBeingDragged[0];
if (dragIndex > -1 && draggedDoc) {
- this._docDraggedIndex = -1;
- const sorted = this.sortedDocs;
+ this.docDraggedIndex = -1;
+ const sorted = this.childDocs;
const originalIndex = sorted.findIndex(doc => doc === draggedDoc);
- this.Document.card_sort = '';
+ this.Document[this._props.fieldKey + '_sort'] = '';
originalIndex !== -1 && sorted.splice(originalIndex, 1);
sorted.splice(dragIndex, 0, draggedDoc);
if (de.complete.docDragData.removeDocument?.(draggedDoc)) {
@@ -277,49 +275,6 @@ export class CollectionCardView extends CollectionSubView() {
.map(({ i }) => i)
.join('.');
- /**
- * 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
- * @param docs
- * @param sortType
- * @param isDesc
- * @returns
- */
- sort = (docsIn: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
- const docs = docsIn.slice(); // need make new object list since sort() modifies the incoming list which confuses mobx caching
- sortType &&
- docs.sort((docA, docB) => {
- const [typeA, typeB] = (() => {
- switch (sortType) {
- default:
- case cardSortings.Type: return [StrCast(docA.type), StrCast(docB.type)];
- case cardSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)];
- case cardSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
- case cardSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()];
- }
- })(); //prettier-ignore
- return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1);
- });
- if (dragIndex !== -1) {
- const draggedDoc = DragManager.docsBeingDragged[0];
- const originalIndex = docs.findIndex(doc => doc === draggedDoc);
-
- originalIndex !== -1 && docs.splice(originalIndex, 1);
- draggedDoc && docs.splice(dragIndex, 0, draggedDoc);
- }
-
- return docs;
- };
-
- @computed get sortedDocs() {
- return this.sort(
- this.childCards.map(card => card.layout),
- this.cardSort,
- BoolCast(this.Document.card_sort_isDesc),
- this._docDraggedIndex
- );
- }
-
isChildContentActive = computedFn(
(doc: Doc) => () =>
this._props.isContentActive?.() === false
@@ -485,74 +440,51 @@ export class CollectionCardView extends CollectionSubView() {
* Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to
* usable code
* @param gptOutput
+ * @param questionType
+ * @param tag
*/
- @action
- processGptOutput = undoable((gptOutput: string, questionType: string, tag?: string) => {
- // Split the string into individual list items
- const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
-
- if (questionType === '2' || questionType === '4') {
- this.childDocs.forEach(d => {
- d.chatFilter = false;
- });
- }
-
- if (questionType === '6') {
- this.Document.card_sort = 'chat';
- }
+ processGptOutput = (gptOutput: string, questionType: string, tag?: string) =>
+ undoable(() => {
+ // Split the string into individual list items
+ const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
+
+ if (questionType === '2' || questionType === '4') {
+ this.childDocs.forEach(d => {
+ TagItem.removeTagFromDoc(d, '#chat');
+ });
+ }
- listItems.forEach((item, index) => {
- const normalizedItem = item.trim();
- // find the corresponding Doc in the textToDoc map
- const doc = this._textToDoc.get(normalizedItem);
-
- if (doc) {
- switch (questionType) {
- case '6':
- doc.chatIndex = index;
- break;
- case '1': {
- const allHotKeys = Doc.MyFilterHotKeys;
-
- let myTag = '';
-
- if (tag) {
- for (let i = 0; i < allHotKeys.length; i++) {
- // bcz: CHECK THIS CODE OUT -- SOMETHING CHANGED
- const keyTag = StrCast(allHotKeys[i].toolType);
- if (tag.includes(keyTag)) {
- myTag = keyTag;
- break;
- }
- }
+ if (questionType === '6') {
+ this.Document[this._props.fieldKey + '_sort'] = docSortings.Chat;
+ }
- if (myTag != '') {
- doc[myTag] = true;
+ listItems.forEach((item, index) => {
+ const normalizedItem = item.trim();
+ // find the corresponding Doc in the textToDoc map
+ const doc = this._textToDoc.get(normalizedItem);
+ if (doc) {
+ switch (questionType) {
+ case '6':
+ doc.chatIndex = index;
+ break;
+ case '1':
+ if (tag) {
+ const hashTag = tag.startsWith('#') ? tag : '#' + tag[0].toLowerCase() + tag.slice(1);
+ const filterTag = Doc.MyFilterHotKeys.map(key => StrCast(key.toolType)).find(key => key.includes(tag)) ?? hashTag;
+ TagItem.addTagToDoc(doc, filterTag);
}
- }
- break;
+ break;
+ case '2':
+ case '4':
+ TagItem.addTagToDoc(doc, '#chat');
+ Doc.setDocFilter(this.Document, 'tags', '#chat', 'check');
+ break;
}
- case '2':
- case '4':
- doc.chatFilter = true;
- Doc.setDocFilter(DocCast(this.Document.embedContainer), 'chatFilter', true, 'match');
- break;
+ } else {
+ console.warn(`No matching document found for item: ${normalizedItem}`);
}
- } 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.CARD);
- GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
- await this.childPairStringListAndUpdateSortDesc();
- };
+ });
+ }, '')();
childScreenToLocal = computedFn((doc: Doc, index: number, isSelected: boolean) => () => {
// need to explicitly trigger an invalidation since we're reading everything from the Dom
@@ -591,6 +523,26 @@ export class CollectionCardView extends CollectionSubView() {
}
});
+ cardSizerDown = (e: React.PointerEvent) => {
+ runInAction(() => {
+ this._cursor = 'grabbing';
+ });
+ const batch = UndoManager.StartBatch('card view size');
+ setupMoveUpEvents(
+ this,
+ e,
+ (emove: PointerEvent, down: number[], delta: number[]) => {
+ this.layoutDoc._cardWidth = Math.max(10, delta[0] < 0 ? Math.floor(this.cardWidth + delta[0]) : Math.ceil(this.cardWidth + delta[0]));
+ return false;
+ },
+ action(() => {
+ this._cursor = 'ew-resize';
+ batch.end();
+ }),
+ emptyFunction
+ );
+ };
+
/**
* turns off the _dropped flag at the end of a drag/drop, or releases the focused Doc if a different Doc is clicked
*/
@@ -626,9 +578,8 @@ export class CollectionCardView extends CollectionSubView() {
* Actually renders all the cards
*/
@computed get renderCards() {
- trace();
// Map sorted documents to their rendered components
- return this.sortedDocs.map((doc, index) => {
+ return this.childDocs.map((doc, index) => {
const cardsInRow = this.cardsInRowThatIncludesCardIndex(index);
const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this.curDoc());
@@ -638,7 +589,7 @@ export class CollectionCardView extends CollectionSubView() {
const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale * this.nativeScaling) / (aspect * this.childPanelWidth()),
(this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10)))); // prettier-ignore
- const hscale = Math.min(this.sortedDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
+ const hscale = Math.min(this.childDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
return (
<div
key={doc[Id]}
@@ -674,17 +625,13 @@ export class CollectionCardView extends CollectionSubView() {
curDoc = () => DocCast(this.layoutDoc._card_curDoc);
render() {
- trace();
const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale;
return (
<div
className="collectionCardView-outer"
ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
- onPointerDown={action(e => {
- if (e.button === 2 || e.ctrlKey) return;
- this.releaseCurDoc();
- })}
- onPointerLeave={action(() => (this._docDraggedIndex = -1))}
+ onPointerDown={e => e.button !== 2 && !e.ctrlKey && this.releaseCurDoc()}
+ onPointerLeave={action(() => (this.docDraggedIndex = -1))}
onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))}
onDrop={this.onExternalDrop.bind(this)}
style={{
@@ -701,6 +648,7 @@ export class CollectionCardView extends CollectionSubView() {
<div
className="collectionCardView-cardwrapper"
style={{
+ gridTemplateColumns: `repeat(${this._maxRowCount}, 1fr)`,
gridAutoRows: `${100 / this.numRows}%`,
height: `${this.cardSpacing}%`,
}}>
@@ -717,6 +665,14 @@ export class CollectionCardView extends CollectionSubView() {
{this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
</div>
</div>
+
+ <div
+ className="collectionCardView-cardSizeDragger"
+ onPointerDown={this.cardSizerDown}
+ ref={this._draggerRef}
+ style={{ display: this._props.isContentActive() ? undefined : 'none', cursor: this._cursor, color: SettingsManager.userColor, left: `${this.cardWidth + this.xMargin}px` }}>
+ <FontAwesomeIcon icon="arrows-alt-h" />
+ </div>
</div>
);
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0c059f729..5e99bec39 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
import { action, computed, makeObservable, observable } from 'mobx';
import * as React from 'react';
import * as rp from 'request-promise';
-import { ClientUtils, returnFalse } from '../../../ClientUtils';
+import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils';
import CursorField from '../../../fields/CursorField';
import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -29,6 +29,14 @@ import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
import { FlashcardPracticeUI } from './FlashcardPracticeUI';
import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
+export enum docSortings {
+ Time = 'time',
+ Type = 'type',
+ Color = 'color',
+ Chat = 'chat',
+ Tag = 'tag',
+ None = '',
+}
export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
@@ -150,6 +158,8 @@ export function CollectionSubView<X>() {
unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])];
childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
+
+ @observable docDraggedIndex = -1;
@computed.struct get childDocs() {
TraceMobx();
let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -166,8 +176,10 @@ export function CollectionSubView<X>() {
const templateRoot = this._props.TemplateDataDocument;
rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
}
-
- const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
+ const childDocs = this.childSortedDocs(
+ rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc),
+ this.docDraggedIndex
+ );
const childDocFilters = this.childDocFilters();
const childFiltersByRanges = this.childDocRangeFilters();
@@ -214,6 +226,35 @@ export function CollectionSubView<X>() {
return docsforFilter;
}
+ childSortedDocs = (docsIn: Doc[], dragIndex: number) => {
+ const sortType = StrCast(this.Document[this._props.fieldKey + '_sort']) as docSortings;
+ const isDesc = BoolCast(this.Document[this._props.fieldKey + '_sort_desc']);
+ const docs = docsIn.slice();
+ if (sortType) {
+ docs.sort((docA, docB) => {
+ const [typeA, typeB] = (() => {
+ switch (sortType) {
+ default:
+ case docSortings.Type: return [StrCast(docA.type), StrCast(docB.type)];
+ case docSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)];
+ case docSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
+ case docSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()];
+ case docSortings.Tag: return [StrListCast(docA.tags).join(""), StrListCast(docB.tags).join("")];
+ }
+ })(); //prettier-ignore
+ return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1);
+ });
+ }
+ if (dragIndex !== -1) {
+ const draggedDoc = DragManager.docsBeingDragged[0];
+ const originalIndex = docs.findIndex(doc => doc === draggedDoc);
+
+ originalIndex !== -1 && docs.splice(originalIndex, 1);
+ draggedDoc && docs.splice(dragIndex, 0, draggedDoc);
+ }
+ return docs;
+ };
+
@action
protected async setCursorPosition(position: [number, number]) {
let ind;
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index 77f1db9ad..c071c5fb8 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -58,7 +58,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore
@computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
- @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
+ @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale);
btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
@@ -179,7 +179,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
</div>
);
}
- tryFilterOut = (doc: Doc) => (this.practiceMode && BoolCast(doc?._flashcardType) && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct
+ tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct
render() {
return (
<div className="FlashcardPracticeUI">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 272c13546..bebdbd731 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -44,6 +44,7 @@ export interface PoolData {
transition?: string;
highlight?: boolean;
pointerEvents?: string;
+ showTags?: boolean;
}
export interface ViewDefResult {
@@ -425,6 +426,7 @@ function normalizeResults(
opacity: newPosRaw.opacity,
color: newPosRaw.color,
pair: ele[1].pair,
+ showTags: newPosRaw.showTags,
};
if (newPosRaw.transition) newPos.transition = newPosRaw.transition;
poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 796949378..3c31b584e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -232,7 +232,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
- return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true);
+ return DocumentView.SetViewTransition(docs, 'all', duration, timer, true);
}
changeKeyFrame = (back = false) => {
const currentFrame = Cast(this.Document._currentFrame, 'number', null);
@@ -1658,6 +1658,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
width: _width,
height: _height,
transition: StrCast(childDocLayout.dataTransition),
+ showTags: BoolCast(childDocLayout.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags),
pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
pair,
replica: '',
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 16f8b86f3..da203abfa 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -100,9 +100,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
return infos;
}
- isolatedSelection = (doc: Doc) => {
- return this.schemaView?.selectionOverlap(doc);
- };
+ isolatedSelection = (doc: Doc) => this.schemaView?.selectionOverlap(doc);
setCursorIndex = (mouseY: number) => this.schemaView?.setRelCursorIndex(mouseY);
selectedCol = () => this.schemaView._selectedCol;
getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey));
@@ -113,9 +111,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth);
computeRowIndex = () => this.schemaView?.rowIndex(this.Document);
highlightCells = (text: string) => this.schemaView?.highlightCells(text);
- selectReference = (doc: Doc, col: number) => {
- this.schemaView.selectReference(doc, col);
- };
+ selectReference = (doc: Doc, col: number) => this.schemaView.selectReference(doc, col);
eqHighlightFunc = (text: string) => {
const info = this.schemaView.findCellRefs(text);
const cells: HTMLDivElement[] = [];
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 1738802b7..b44292164 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -40,6 +40,7 @@ import { WebBox } from '../nodes/WebBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
import { OpenWhere } from '../nodes/OpenWhere';
+import { docSortings } from '../collections/CollectionSubView';
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function IsNoneSelected() {
@@ -48,10 +49,20 @@ ScriptingGlobals.add(function IsNoneSelected() {
// toggle: Set overlay status of selected document
// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function setView(view: string, getSelected: boolean) {
- if (getSelected) return DocumentView.SelectedDocs();
- const selected = DocumentView.SelectedDocs().lastElement();
- selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
+ScriptingGlobals.add(function setView(view: string, shiftKey: boolean, checkResult?: boolean) {
+ if (checkResult) return DocumentView.SelectedDocs();
+ const selected = DocumentView.Selected().lastElement();
+ if (selected) {
+ if (shiftKey) {
+ const newCol = Doc.MakeEmbedding(selected.Document);
+ newCol._type_collection = view;
+ selected._props.addDocTab?.(newCol, OpenWhere.addRight);
+ } else {
+ selected.Document._type_collection = view;
+ }
+ } else {
+ console.log('[FontIconBox.tsx] changeView failed');
+ }
return undefined;
});
@@ -141,7 +152,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function showFreeform(
- attr: 'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'toggle-tags' | 'tag',
+ attr: 'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'toggle-chat' | 'toggle-tags' | 'tag',
checkResult?: boolean,
persist?: boolean
) {
@@ -152,7 +163,7 @@ ScriptingGlobals.add(function showFreeform(
}
// prettier-ignore
- const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'toggle-tags' | 'tag',
+ const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down'| 'toggle-chat' | 'toggle-tags' | 'tag',
{
waitForRender?: boolean;
checkResult: (doc: Doc) => boolean;
@@ -187,43 +198,35 @@ ScriptingGlobals.add(function showFreeform(
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),
- setDoc: (doc: Doc, dv: DocumentView) => { Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards}, // prettier-ignore
- }],
['time', {
- checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "time",
- setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "time" ? doc.card_sort = '' : doc.card_sort = 'time'}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "time",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore
}],
['docType', {
- checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "type",
- setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "type" ? doc.card_sort = '' : doc.card_sort = 'type'}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "type",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore
}],
['color', {
- checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "color",
- setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "color" ? doc.card_sort = '' : doc.card_sort = 'color'}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "color",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutFieldKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore
}],
['tag', {
- checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "tag",
- setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "tag" ? doc.card_sort = '' : doc.card_sort = 'tag'}, // prettier-ignore
+ checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "tag",
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore
}],
['up', {
- checkResult: (doc: Doc) => BoolCast(!doc?.card_sort_isDesc),
- setDoc: (doc: Doc, dv: DocumentView) => {
- doc.card_sort_isDesc = false;
- },
+ checkResult: (doc: Doc) => BoolCast(!doc?.[Doc.LayoutFieldKey(doc)+"_sort_desc"]),
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = undefined; },
}],
['down', {
- checkResult: (doc: Doc) => BoolCast(doc?.card_sort_isDesc),
- setDoc: (doc: Doc, dv: DocumentView) => {
- doc.card_sort_isDesc = true;
- },
+ checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort_desc"]),
+ setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; },
}],
['toggle-chat', {
- checkResult: (doc: Doc) => GPTPopup.Instance.visible,
+ checkResult: (doc: Doc) => GPTPopup.Instance.Visible,
setDoc: (doc: Doc, dv: DocumentView) => {
- if (GPTPopup.Instance.visible){
- doc.card_sort = ''
+ if (GPTPopup.Instance.Visible){
+ doc[Doc.LayoutFieldKey(doc)+"_sort"] = '';
GPTPopup.Instance.setVisible(false);
} else {
@@ -242,20 +245,6 @@ ScriptingGlobals.add(function showFreeform(
doc.showChildTags = !doc.showChildTags;
},
}],
- ['pile', {
- checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform,
- setDoc: (doc: Doc, dv: DocumentView) => {
- const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), {
- title: doc.title + "_carousel",
- _width: 250,
- _height: 200,
- _layout_fitWidth: false,
- _layout_autoHeight: true,
- childFilters: new List<string>(StrListCast(doc.childFilters))
- });
- dv._props.addDocTab?.(newCol, OpenWhere.addRight);
- },
- }],
]);
if (checkResult) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3cbfb1796..beea6ab3c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -28,7 +28,7 @@ export enum GroupActive { // flags for whether a view is activate because of its
}
/// 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'];
+const freeFormPropsKeys = ['x', 'y', 'z', 'width', 'height', 'zIndex', 'autoDim', 'rotation', 'color', 'backgroundColor', 'opacity', 'highlight', 'transition'];
interface freeFormProps {
x: number;
y: number;
@@ -198,7 +198,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
- const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
+ const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, true);
const timecode = Math.round(time);
docs.forEach(doc => {
this.animFields.forEach(val => {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index e0c360132..53fbd11c5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import axios from 'axios';
-import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import ReactLoading from 'react-loading';
@@ -510,7 +510,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
* Calls GPT for each flashcard type.
*/
askGPT = async (callType: GPTCallType) => {
- const questionText = 'Question: ' + this.frontText;
+ const questionText = this.frontText;
const queryText = questionText + (callType == GPTCallType.QUIZ ? ' UserAnswer: ' + this._inputValue + '. ' + ' Rubric: ' + this.backText : '');
this.loading = true;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f1b48be4a..0193fd328 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -52,6 +52,7 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails/PresEnums';
import SpringAnimation from './trails/SlideEffect';
import { SpringType, springMappings } from './trails/SpringUtils';
+import { TagsView } from '../TagsView';
export interface DocumentViewProps extends FieldViewSharedProps {
hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
@@ -778,7 +779,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
transform: `scale(${this.uiBtnScaling})`,
bottom: this.maxWidgetSize,
}}>
- {this.widgetDecorations ?? null}
+ {this._props.DocumentView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
</div>
) : (
<>
@@ -801,10 +802,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
<div className="documentView-editorView-resizer" />
{this._componentView?.componentAIView?.() ?? null}
- {this.widgetDecorations ?? null}
+ {this._props.DocumentView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
</div>
</>
)}
+ {this.widgetDecorations ?? null}
</>
);
}
@@ -1041,13 +1043,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
>,
root: Doc
) {
- const dir = ((presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) || PresEffectDirection.Center) as PresEffectDirection;
+ const effectDirection = (presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) as PresEffectDirection;
const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null));
const effectProps = {
- left: dir === PresEffectDirection.Left,
- right: dir === PresEffectDirection.Right,
- top: dir === PresEffectDirection.Top,
- bottom: dir === PresEffectDirection.Bottom,
+ left: effectDirection === PresEffectDirection.Left,
+ right: effectDirection === PresEffectDirection.Right,
+ top: effectDirection === PresEffectDirection.Top,
+ bottom: effectDirection === PresEffectDirection.Bottom,
opposite: true,
delay: 0,
duration,
@@ -1061,7 +1063,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
const presEffect = StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect));
switch (presEffect) {
case PresEffect.Expand: case PresEffect.Flip: case PresEffect.Rotate: case PresEffect.Bounce:
- case PresEffect.Roll: return <SpringAnimation doc={root} startOpacity={0} dir={dir} presEffect={presEffect} springSettings={timingConfig}>{renderDoc}</SpringAnimation>
+ case PresEffect.Roll: return <SpringAnimation doc={root} startOpacity={0} dir={effectDirection || PresEffectDirection.Left} presEffect={presEffect} springSettings={timingConfig}>{renderDoc}</SpringAnimation>
// case PresEffect.Fade: return <SlideEffect doc={root} dir={dir} presEffect={PresEffect.Fade} tension={timingConfig.stiffness} friction={timingConfig.damping} mass={timingConfig.mass}>{renderDoc}</SlideEffect>
case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>
// keep as preset, doesn't really make sense with spring config
@@ -1388,8 +1390,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
this.Document[Animation] = presEffect;
this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore
};
- public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
- this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
+ public setViewTransition = (transProp: string, timeInMs: number, dataTrans = false) => {
+ this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, dataTrans);
};
public setCustomView = undoable((custom: boolean, layout: string): void => {
@@ -1588,21 +1590,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
);
}
- public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, afterTrans?: () => void, dataTrans = false) {
- docs.forEach(doc => {
- doc._viewTransition = `${transProp} ${timeInMs}ms`;
- dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
- });
+ public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, dataTrans = false) {
+ const setTrans = (transition?: string) =>
+ docs.forEach(doc => {
+ doc._viewTransition = transition;
+ dataTrans && (doc.dataTransition = transition);
+ });
+ setTrans(`${transProp} ${timeInMs}ms`);
timer && clearTimeout(timer);
- return setTimeout(
- () =>
- docs.forEach(doc => {
- doc._viewTransition = undefined;
- dataTrans && (doc.dataTransition = 'inherit');
- afterTrans?.();
- }),
- timeInMs + 10
- );
+ return setTimeout(setTrans, timeInMs + 10);
}
// shows a stacking view collection (by default, but the user can change) of all documents linked to the source
diff --git a/src/client/views/nodes/FocusViewOptions.ts b/src/client/views/nodes/FocusViewOptions.ts
index bb0d2b03c..1c462e98f 100644
--- a/src/client/views/nodes/FocusViewOptions.ts
+++ b/src/client/views/nodes/FocusViewOptions.ts
@@ -22,3 +22,14 @@ export interface FocusViewOptions {
pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode)
contextPath?: Doc[]; // path of inner documents that will also be focused
}
+
+/**
+ * if there's an options.effect, it will be handled from linkFollowHighlight. We delay the start of
+ * the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen
+ * bcz: should this delay be an options parameter?
+ * @param options
+ * @returns
+ */
+export function FocusEffectDelay(options: FocusViewOptions) {
+ return (options.zoomTime ?? 0) * 0.5;
+}
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 60b2a7519..f58862028 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -160,6 +160,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
buttonList,
jsx: undefined,
selectedVal: script(),
+ toolTip: 'Set text font',
getStyle: (val: string) => ({ fontFamily: val }),
};
};
@@ -174,6 +175,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
buttonList: buttonList.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value as CollectionViewType)),
getStyle: undefined,
selectedVal: StrCast(selected[0]._type_collection),
+ toolTip: 'change view type (press Shift to add as a new view)',
}
: {
jsx: selected.length ? (
@@ -205,11 +207,11 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
@computed get dropdownListButton() {
const script = ScriptCast(this.Document.script);
const selectedFunc = () => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
- const { buttonList, selectedVal, getStyle, jsx } = (() => {
+ const { buttonList, selectedVal, getStyle, jsx, toolTip } = (() => {
switch (this.Document.title) {
case 'Font': return this.handleFontDropdown(selectedFunc, this.buttonList);
case 'Perspective': return this.handleViewDropdown(script, this.buttonList);
- default: return { buttonList: this.buttonList, selectedVal: selectedFunc(), jsx: undefined, getStyle: undefined };
+ default: return { buttonList: this.buttonList, selectedVal: selectedFunc(), toolTip: undefined, jsx: undefined, getStyle: undefined };
} // prettier-ignore
})();
if (jsx) return jsx;
@@ -225,9 +227,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
return (
<Dropdown
selectedVal={selectedVal}
- setSelectedVal={undoable(value => script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)}
+ setSelectedVal={undoable((value, e) => script.script.run({ this: this.Document, value, shiftKey: e.shiftKey }), `dropdown select ${this.label}`)}
color={SnappingManager.userColor}
background={SnappingManager.userVariantColor}
+ toolTip={toolTip}
type={Type.TERT}
closeOnSelect={false}
dropdownType={DropdownType.SELECT}
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 90cc06092..c79d662f4 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -10,8 +10,6 @@
gap: 5px;
padding-left: 5px;
padding-right: 5px;
- padding-top: 2px;
- padding-bottom: 2px;
button {
pointer-events: auto;
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 87469b50e..cef202256 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -109,7 +109,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
return this._left > 0;
}
- constructor(props: any) {
+ constructor(props: AntimodeMenuProps) {
super(props);
makeObservable(this);
MapAnchorMenu.Instance = this;
@@ -117,10 +117,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}
componentWillUnmount() {
- this.destinationFeatures = [];
- this.destinationSelected = false;
- this.selectedDestinationFeature = undefined;
- this.currentRouteInfoMap = undefined;
+ runInAction(() => {
+ this.destinationFeatures = [];
+ this.destinationSelected = false;
+ this.selectedDestinationFeature = undefined;
+ this.currentRouteInfoMap = undefined;
+ });
this._disposer?.();
}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index f0313fba4..0684daeb6 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
+import { returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
@@ -169,12 +169,17 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
selectedCells={this.selectedCells}
selectedCol={returnZero}
fieldKey={this._fieldKey}
+ highlightCells={emptyFunction} // fix
+ refSelectModeInfo={{ enabled: false, currEditing: undefined }} // fix
+ selectReference={emptyFunction} //
+ eqHighlightFunc={() => []} // fix
+ isolatedSelection={() => [true, true]} // fix
+ rowSelected={returnTrue} //fix
rowHeight={returnZero}
isRowActive={this.isRowActive}
padding={0}
getFinfo={emptyFunction}
setColumnValues={returnFalse}
- setSelectedColumnValues={returnFalse}
allowCRs
oneLine={!this._expanded && !this._props.nodeSelected()}
finishEdit={this.finishEdit}
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 2ae6ee1dd..6b1d05031 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -551,8 +551,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
img.src = src;
if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
try {
- const res = await createNewImgDoc(img, false);
- return res;
+ return await createNewImgDoc(img, false);
} catch (err) {
console.log(err);
}
@@ -589,9 +588,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
// disable once edited has been clicked (doesn't make sense to change after first edit)
disabled={edited}
checked={isNewCollection}
- onChange={() => {
- setIsNewCollection(prev => !prev);
- }}
+ onChange={() => setIsNewCollection(prev => !prev)}
/>
}
label="Create New Collection"
@@ -610,49 +607,13 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
return (
<div className="sideControlsContainer" style={{ backgroundColor: bgColor }}>
<div className="sideControls">
- <div className="imageToolsContainer">
- {imageEditTools.map(tool => {
- return ImageToolButton(tool, tool.type === currTool.type, changeTool);
- })}
- </div>
+ <div className="imageToolsContainer">{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool.type, changeTool))}</div>
{currTool.type == ImageToolType.Cut && (
<div className="cutToolsContainer">
- <Button
- style={{ width: '100%' }}
- text="Keep in"
- type={Type.TERT}
- color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor}
- onClick={() => {
- setCutType(CutMode.IN);
- }}
- />
- <Button
- style={{ width: '100%' }}
- text="Keep out"
- type={Type.TERT}
- color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor}
- onClick={() => {
- setCutType(CutMode.OUT);
- }}
- />
- <Button
- style={{ width: '100%' }}
- text="Draw in"
- type={Type.TERT}
- color={cutType == CutMode.DRAW_IN ? SettingsManager.userColor : bgColor}
- onClick={() => {
- setCutType(CutMode.DRAW_IN);
- }}
- />
- <Button
- style={{ width: '100%' }}
- text="Erase"
- type={Type.TERT}
- color={cutType == CutMode.ERASE ? SettingsManager.userColor : bgColor}
- onClick={() => {
- setCutType(CutMode.ERASE);
- }}
- />
+ <Button style={{ width: '100%' }} text="Keep in" type={Type.TERT} color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.IN)} />
+ <Button style={{ width: '100%' }} text="Keep out" type={Type.TERT} color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.OUT)} />
+ <Button style={{ width: '100%' }} text="Draw in" type={Type.TERT} color={cutType == CutMode.DRAW_IN ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.DRAW_IN)} />
+ <Button style={{ width: '100%' }} text="Erase" type={Type.TERT} color={cutType == CutMode.ERASE ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.ERASE)} />
</div>
)}
<div className="sliderContainer" onPointerDown={e => e.stopPropagation()}>
@@ -669,9 +630,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
defaultValue={genFillTool.sliderDefault}
size="small"
valueLabelDisplay="auto"
- onChange={(e, val) => {
- setCursorData(prev => ({ ...prev, width: val as number }));
- }}
+ onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))}
/>
)}
{currTool.type === ImageToolType.Cut && (
@@ -687,9 +646,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
defaultValue={cutTool.sliderDefault}
size="small"
valueLabelDisplay="auto"
- onChange={(e, val) => {
- setCursorData(prev => ({ ...prev, width: val as number }));
- }}
+ onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))}
/>
)}
</div>
@@ -701,9 +658,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
e.stopPropagation();
handleUndo();
}}
- onPointerUp={e => {
- e.stopPropagation();
- }}
+ onPointerUp={e => e.stopPropagation()}
color={activeColor}
tooltip="Undo"
icon={<IoMdUndo />}
@@ -714,9 +669,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
e.stopPropagation();
handleRedo();
}}
- onPointerUp={e => {
- e.stopPropagation();
- }}
+ onPointerUp={e => e.stopPropagation()}
color={activeColor}
tooltip="Redo"
icon={<IoMdRedo />}
diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
index de2116253..985dc914f 100644
--- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
@@ -53,7 +53,7 @@ export function ApplyFuncButtons({ loading, onClick: getEdit, onReset, btnText }
export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTool: (type: ImageToolType) => void) {
return (
- <div className="imageEditorButtonContainer">
+ <div key={tool.name} className="imageEditorButtonContainer">
<Button
style={{ width: '100%' }}
text={tool.name}
diff --git a/src/client/views/nodes/trails/CubicBezierEditor.tsx b/src/client/views/nodes/trails/CubicBezierEditor.tsx
index e1ad1e6e5..627b77184 100644
--- a/src/client/views/nodes/trails/CubicBezierEditor.tsx
+++ b/src/client/views/nodes/trails/CubicBezierEditor.tsx
@@ -118,84 +118,82 @@ function CubicBezierEditor({ setFunc, currPoints }: Props) {
}, [c2Down, currPoints]);
return (
- <div
- onPointerMove={e => {
- e.stopPropagation;
- }}>
- <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg">
- {/* Outlines */}
- <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" />
- {/* Box Outline */}
- <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" />
- {/* Editor */}
- <path
- d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${currPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${
- currPoints.p2[0] * EDITOR_WIDTH + OFFSET
- } ${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`}
- stroke="#ffffff"
- fill="transparent"
- />
- {/* Bottom left */}
- <line
- onPointerDown={() => {
- setC1Down(true);
- }}
- onPointerUp={() => {
- setC1Down(false);
- }}
- x1={`${0 + OFFSET}`}
- y1={`${EDITOR_WIDTH + OFFSET}`}
- x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
- y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
- stroke="#00000000"
- strokeWidth="5"
- />
- <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
- <circle
- cx={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
- cy={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
- r="5"
- fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`}
- onPointerDown={e => {
- e.stopPropagation();
- setC1Down(true);
- }}
- onPointerUp={() => {
- setC1Down(false);
- }}
- />
- {/* Top right */}
- <line
- onPointerDown={e => {
- e.stopPropagation();
- setC2Down(true);
- }}
- onPointerUp={() => {
- setC2Down(false);
- }}
- x1={`${EDITOR_WIDTH + OFFSET}`}
- y1={`${0 + OFFSET}`}
- x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
- y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
- stroke="#00000000"
- strokeWidth="5"
- />
- <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
- <circle
- cx={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
- cy={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
- r="5"
- fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`}
- onPointerDown={e => {
- e.stopPropagation();
- setC2Down(true);
- }}
- onPointerUp={() => {
- setC2Down(false);
- }}
- />
- </svg>
- </div>
+ <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg">
+ {/* Outlines */}
+ <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" />
+ {/* Box Outline */}
+ <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" />
+ {/* Editor */}
+ <path
+ d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${currPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${
+ currPoints.p2[0] * EDITOR_WIDTH + OFFSET
+ } ${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`}
+ stroke="#ffffff"
+ fill="transparent"
+ />
+ {/* Bottom left */}
+ <line
+ onPointerDown={() => {
+ setC1Down(true);
+ }}
+ onPointerMove={e => {
+ e.stopPropagation;
+ }}
+ onPointerUp={() => {
+ setC1Down(false);
+ }}
+ x1={`${0 + OFFSET}`}
+ y1={`${EDITOR_WIDTH + OFFSET}`}
+ x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
+ y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
+ stroke="#00000000"
+ strokeWidth="5"
+ />
+ <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
+ <circle
+ cx={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
+ cy={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
+ r="5"
+ fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`}
+ onPointerDown={e => {
+ e.stopPropagation();
+ setC1Down(true);
+ }}
+ onPointerUp={() => {
+ setC1Down(false);
+ }}
+ />
+ {/* Top right */}
+ <line
+ onPointerDown={e => {
+ e.stopPropagation();
+ setC2Down(true);
+ }}
+ onPointerUp={() => {
+ setC2Down(false);
+ }}
+ x1={`${EDITOR_WIDTH + OFFSET}`}
+ y1={`${0 + OFFSET}`}
+ x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
+ y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
+ stroke="#00000000"
+ strokeWidth="5"
+ />
+ <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
+ <circle
+ cx={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
+ cy={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
+ r="5"
+ fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`}
+ onPointerDown={e => {
+ e.stopPropagation();
+ setC2Down(true);
+ }}
+ onPointerUp={() => {
+ setC2Down(false);
+ }}
+ />
+ </svg>
);
}
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 60d4e580d..e34e1b380 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -5,11 +5,25 @@
display: flex;
flex-direction: column;
gap: 1rem;
+ .presBox-gpt-chat-span {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ textarea {
+ width: 100%;
+ }
+}
+.presBox-subheading-slider {
+ max-width: calc(100% - 25px);
+ width: 100%;
+ padding: 15px;
+ padding-left: 0px;
}
.pres-chat {
display: flex;
- flex-direction: column;
+ flex-direction: row;
gap: 8px;
}
@@ -18,30 +32,38 @@
gap: 8px;
}
-.pres-chatbox-container {
- padding: 16px;
+.pres-chatbox-container,
+.pres-chatbox-container-ai {
+ width: 100%;
+ padding-left: 16px;
+ padding-right: 16px;
outline: 1px solid #999999;
- border-radius: 16px;
+ border-radius: 5px;
display: flex;
align-items: center;
justify-content: space-between;
+ overflow: auto;
+ max-height: 200px;
+ .pres-chatbox {
+ outline: none;
+ border: none;
+ resize: none;
+ font-family: Verdana, Geneva, sans-serif;
+ background-color: transparent;
+ overflow-y: hidden;
+ }
}
-.pres-chatbox {
- outline: none;
- border: none;
- resize: none;
- font-family: Verdana, Geneva, sans-serif;
- background-color: transparent;
- overflow-y: hidden;
+.pres-chatbox-container-ai {
+ padding-left: 8px;
+ padding-right: 8px;
+ margin-left: 8px;
}
-
// Effect Animations
.presBox-effects {
- display: grid;
- grid-template-columns: auto auto;
- gap: 8px;
+ display: flow;
+ margin: auto;
}
.presBox-effect-row {
@@ -55,7 +77,7 @@
overflow: hidden;
width: 80px;
height: 80px;
- display: flex;
+ display: inline-flex;
justify-content: center;
align-items: center;
border: 1px solid rgb(118, 118, 118);
@@ -74,12 +96,19 @@
.presBox-show-hide-dropdown {
cursor: pointer;
- padding: 8px 0;
display: flex;
align-items: center;
gap: 4px;
}
+.presBox-switches {
+ display: flex;
+ width: 100%;
+ > div {
+ width: 100%;
+ }
+}
+
.presBox-bezier-editor {
border: 1px solid rgb(221, 221, 221);
border-radius: 4px;
@@ -96,6 +125,18 @@
align-items: center;
}
+.presBox-previewContainer {
+ display: flex;
+ align-items: center;
+ width: fit-content;
+ margin: auto;
+ grid-template-columns: auto auto;
+ grid-gap: 10px;
+ .presBox-option-block {
+ padding: 0px;
+ }
+}
+
.presBox-cont {
cursor: auto;
position: absolute;
@@ -270,7 +311,7 @@
}
.presBox-toggles {
- display: flex;
+ display: flow;
overflow-x: auto;
}
@@ -280,6 +321,9 @@
font-family: Roboto;
z-index: 100;
transition: 0.7s;
+ .form-wrapper.left .formLabel {
+ width: 100px;
+ }
.ribbon-doubleButton {
display: flex;
@@ -352,6 +396,18 @@
font-size: 11;
font-weight: 400;
margin-top: 10px;
+ max-width: min(85px, 25%);
+ width: 100%;
+ }
+ .presBox-springSlider {
+ display: grid;
+ column-count: 2;
+ grid-template-columns: min(60px, 25%) calc(100% - min(60px, 25%) - min(5px, 10%));
+ grid-gap: min(5px, 10%);
+ > span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
@@ -459,7 +515,7 @@
justify-content: space-between;
width: 100%;
height: max-content;
- grid-template-columns: auto auto auto;
+ grid-template-columns: auto auto;
grid-template-rows: max-content;
font-weight: 100;
margin-top: 3px;
@@ -520,20 +576,17 @@
}
.presBox-input {
- border: none;
background-color: transparent;
- width: 40;
- // padding: 8px;
- // border-radius: 4px;
- // width: 30;
- // height: 100%;
- // background: none;
- // border: none;
- // text-align: right;
+ text-align: center;
+ width: 100%;
+ height: 15px;
+ font-size: 10;
}
-
- .presBox-input:focus {
- outline: none;
+ .presBox-inputNumber {
+ border: none;
+ background-color: transparent;
+ width: 100%;
+ max-width: 25px;
}
.ribbon-frameSelector {
@@ -737,7 +790,7 @@
font-weight: 200;
height: 20;
background-color: $white;
- display: flex;
+ display: inline-flex;
color: $black;
border-radius: 5px;
width: max-content;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f23b32a48..7c23ca8e1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,7 +1,8 @@
+import { Button, Dropdown, DropdownType, IconButton, Size, Type } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import Slider from '@mui/material/Slider';
-import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from '@dash/components';
+import _ from 'lodash';
import { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -40,7 +41,7 @@ import { CollectionFreeFormPannableContents } from '../../collections/collection
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import { FocusViewOptions } from '../FocusViewOptions';
+import { FocusEffectDelay, FocusViewOptions } from '../FocusViewOptions';
import { OpenWhere, OpenWhereMod } from '../OpenWhere';
import { ScriptingBox } from '../ScriptingBox';
import CubicBezierEditor, { EaseFuncToPoints, TIMING_DEFAULT_MAPPINGS } from './CubicBezierEditor';
@@ -48,7 +49,6 @@ import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
import SlideEffect from './SlideEffect';
import { AnimationSettings, SpringSettings, SpringType, easeItems, effectItems, effectTimings, movementItems, presEffectDefaultTimings, springMappings, springPreviewColors } from './SpringUtils';
-import _ from 'lodash';
@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -61,6 +61,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
static navigateToDocScript: ScriptField;
+ public static PanelName = 'PRESBOX'; // name of dockingview tab where presentations get added
+
constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
@@ -72,6 +74,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
private _disposers: { [name: string]: IReactionDisposer } = {};
public selectedArray = new ObservableSet<Doc>();
+ public slideToModify: Doc | null = null;
_batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves
_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.
_unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
@@ -97,14 +100,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _presKeyEvents: boolean = false;
@observable _forceKeyEvents: boolean = false;
+ @observable _showAIGallery = false;
+ @observable _showPreview = true;
+ @observable _easeDropdownVal = 'ease';
+
// GPT
- private _inputref: HTMLTextAreaElement | null = null;
- private _inputref2: HTMLTextAreaElement | null = null;
- @observable chatActive: boolean = false;
- @observable chatInput: string = '';
- public slideToModify: Doc | null = null;
- @observable isRecording: boolean = false;
- @observable isLoading: boolean = false;
+ @observable _chatActive: boolean = false;
+ @observable _animationChat: string = '';
+ @observable _chatInput: string = '';
+ @observable _isRecording: boolean = false;
+ @observable _isLoading: boolean = false;
@observable generatedAnimations: AnimationSettings[] = [
// default presets
@@ -138,54 +143,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
},
];
- @action
- setGeneratedAnimations = (settings: AnimationSettings[]) => {
- this.generatedAnimations = settings;
- };
-
- @observable animationChat: string = '';
-
- @action
- setChatInput = (input: string) => {
- this.chatInput = input;
- };
-
- @action
- setAnimationChat = (input: string) => {
- this.animationChat = input;
- };
-
- @action
- setIsLoading = (isLoading: boolean) => {
- this.isLoading = isLoading;
- };
-
- @action
- public setIsRecording = (isRecording: boolean) => {
- this.isRecording = isRecording;
- };
-
- @observable showBezierEditor = false;
- @action setBezierEditorVisibility = (visible: boolean) => {
- this.showBezierEditor = visible;
- };
- @observable showSpringEditor = true;
- @action setSpringEditorVisibility = (visible: boolean) => {
- this.showSpringEditor = visible;
- };
-
- // Easing function variables
-
- @observable easeDropdownVal = 'ease';
-
- @action setBezierControlPoints = (newPoints: { p1: number[]; p2: number[] }) => {
+ setGeneratedAnimations = action((input: AnimationSettings[]) => { this.generatedAnimations = input; }) // prettier-ignore
+ setChatInput = action((input: string) => { this._chatInput = input; }); // prettier-ignore
+ setAnimationChat = action((input: string) => { this._animationChat = input; }); // prettier-ignore
+ setIsLoading = action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
+ setIsRecording = action((input: boolean) => { this._isRecording = input; }); // prettier-ignore
+ setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
+ setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
- };
+ });
+
+ @computed get showEaseFunctions() {
+ return ![PresMovement.None, PresMovement.Jump, ''].includes(StrCast(this.activeItem?.presentation_movement) as PresMovement);
+ }
@computed
get currCPoints() {
- const strPoints = this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease';
- return EaseFuncToPoints(strPoints);
+ return EaseFuncToPoints(this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
}
@computed
@@ -221,25 +195,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._type_collection === CollectionViewType.Stacking) return true;
return false;
}
- @computed get panable() {
- if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._type_collection === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true;
- return false;
- }
@computed get selectedDocumentView() {
if (DocumentView.Selected().length) return DocumentView.Selected()[0];
if (this.selectedArray.size) return DocumentView.getDocumentView(this.Document);
return undefined;
}
- @computed get isPres() {
- return this.selectedDoc === this.Document;
- }
- @computed get selectedDoc() {
- return this.selectedDocumentView?.Document;
- }
- isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentation_targetDoc === layoutDoc;
- clearSelectedArray = () => this.selectedArray.clear();
- addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
- removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
componentWillUnmount() {
this._unmounting = true;
@@ -256,7 +216,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
() => this.pauseAutoPres()
);
this._disposers.keyboard = reaction(
- () => this.selectedDoc,
+ () => this.selectedDocumentView?.Document,
selected => {
document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
(this._presKeyEvents = selected === this.Document) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
@@ -293,13 +253,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
);
}
+ clearSelectedArray = () => this.selectedArray.clear();
+ addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
+ removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
+
@action
updateCurrentPresentation = (pres?: Doc) => {
Doc.ActivePresentation = pres ?? this.Document;
PresBox.Instance = this;
};
- _mediaTimer!: [NodeJS.Timeout, Doc];
// 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart);
@@ -318,7 +281,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
// Recording for GPT customization
-
recordDictation = () => {
this.setIsRecording(true);
this.setChatInput('');
@@ -336,64 +298,34 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
DictationManager.Controls.stop();
};
- setDictationContent = (value: string) => {
- console.log('Dictation value', value);
- this.setChatInput(value);
- };
+ setDictationContent = (value: string) => this.setChatInput(value);
- @action
- customizeAnimations = async () => {
+ customizeAnimations = action(() => {
this.setIsLoading(true);
- try {
- const res = await getSlideTransitionSuggestions(this.animationChat);
- if (typeof res === 'string') {
- const resObj = JSON.parse(res);
- console.log('Parsed GPT Result ', resObj);
- this.setGeneratedAnimations(resObj as AnimationSettings[]);
- }
- } catch (err) {
- console.error(err);
- }
- this.setIsLoading(false);
- };
+ getSlideTransitionSuggestions(this._animationChat)
+ .then(res => this.setGeneratedAnimations(JSON.parse(res) as AnimationSettings[]))
+ .catch(err => console.error(err))
+ .finally(this.setIsLoading);
+ });
- @action
- customizeWithGPT = async (input: string) => {
+ customizeWithGPT = action((input: string) => {
// const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
this.setIsRecording(false);
this.setIsLoading(true);
-
- const currSlideProperties: { [key: string]: FieldResult } = {};
- gptSlideProperties.forEach(key => {
- if (this.activeItem[key]) {
- currSlideProperties[key] = this.activeItem[key];
- }
- // default values
- else if (key === 'presentation_transition') {
- currSlideProperties[key] = 500;
- } else if (key === 'config_zoom') {
- currSlideProperties[key] = 1.0;
- }
- });
- console.log('current slide props ', currSlideProperties);
-
- try {
- const res = await gptTrailSlideCustomization(input, currSlideProperties);
- if (typeof res === 'string') {
- const resObj = JSON.parse(res);
- console.log('Parsed GPT Result ', resObj);
- for (const key in resObj) {
- if (resObj[key]) {
- console.log('typeof property', typeof resObj[key]);
- this.activeItem[key] = resObj[key];
- }
- }
- }
- } catch (err) {
- console.error(err);
- }
- this.setIsLoading(false);
- };
+ const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 };
+ const currSlideProperties = gptSlideProperties.reduce(
+ (prev, key) => { prev[key] = Field.toString(this.activeItem[key]) ?? prev[key]; return prev; },
+ slideDefaults); // prettier-ignore
+
+ gptTrailSlideCustomization(input, JSON.stringify(currSlideProperties))
+ .then(res =>
+ (Object.entries(JSON.parse(res)) as string[][]).forEach(([key, val]) => {
+ this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]);
+ })
+ )
+ .catch(e => console.error(e))
+ .finally(this.setIsLoading);
+ });
// TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
// TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
@@ -452,27 +384,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const progressiveReveal = (first: boolean) => {
const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
if (presIndexed !== undefined) {
- const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
- targetRenderedDoc._dataTransition = 'all 1s';
- targetRenderedDoc.opacity = 1;
- setTimeout(() => {
- targetRenderedDoc._dataTransition = 'inherit';
- }, 1000);
const listItems = this.progressivizedItems(this.activeItem);
- if (listItems && presIndexed < listItems.length) {
+ const listItemDoc = listItems?.[presIndexed];
+ if (listItems && listItemDoc) {
if (!first) {
- const listItemDoc = listItems[presIndexed];
- const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc);
+ const presBulletTiming = 500; // bcz: hardwired for now
Doc.linkFollowUnhighlight();
- Doc.HighlightDoc(listItemDoc);
+ Doc.linkFollowHighlight(listItemDoc);
listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
- listItemDoc.presentation_transition = 500;
- targetView?.setAnimEffect(listItemDoc, 500);
- if (targetView && this.activeItem.presBulletExpand) {
- targetView.setAnimateScaling(1.2, 400);
- Doc.AddUnHighlightWatcher(() => targetView?.setAnimateScaling(0, undefined));
- }
+ listItemDoc.presentation_transition = presBulletTiming;
listItemDoc.opacity = undefined;
+
+ const targetView = DocumentView.getFirstDocumentView(listItemDoc);
+ if (targetView) {
+ targetView.setAnimEffect(listItemDoc, presBulletTiming);
+ if (this.activeItem.presBulletExpand) {
+ targetView.setAnimateScaling(1.2, presBulletTiming * 0.8);
+ Doc.AddUnHighlightWatcher(() => targetView.setAnimateScaling(0, undefined));
+ }
+ }
this.activeItem.presentation_indexed = presIndexed + 1;
}
return true;
@@ -547,8 +477,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (!group) this.clearSelectedArray();
this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array
this.turnOffEdit();
- this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list
- this.doHideBeforeAfter(); // Handles hide after/before
+ this.navigateToActiveItem((options: FocusViewOptions) => {
+ setTimeout(this.doHideBeforeAfter, FocusEffectDelay(options)); // Handles hide after/before
+ finished?.();
+ }); // Handles movement to element only when presentationTrail is list
}
});
static pinDataTypes(target?: Doc): dataTypes {
@@ -784,11 +716,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
setTimeout(
() =>
- Array.from(transitioned).forEach(
- action(doc => {
- doc._dataTransition = undefined;
- })
- ),
+ Array.from(transitioned).forEach(doc => {
+ doc._dataTransition = undefined;
+ }),
transTime + 10
);
}
@@ -826,16 +756,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* a new tab. If presCollection is undefined it will open the document
* on the right.
*/
- navigateToActiveItem = (afterNav?: () => void) => {
+ navigateToActiveItem = (afterNav?: (options: FocusViewOptions) => void) => {
const { activeItem, targetDoc } = this;
- const finished = () => {
- afterNav?.();
+ const finished = (options: FocusViewOptions) => {
+ afterNav?.(options);
targetDoc[Animation] = undefined;
};
const selViewCache = Array.from(this.selectedArray);
const dragViewCache = Array.from(this._dragArray);
const eleViewCache = Array.from(this._eleArray);
- const resetSelection = action(() => {
+ const resetSelection = action((options: FocusViewOptions) => {
if (!this._props.isSelected()) {
const presDocView = DocumentView.getDocumentView(this.Document);
if (presDocView) DocumentView.SelectView(presDocView, false);
@@ -844,14 +774,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._dragArray.splice(0, this._dragArray.length, ...dragViewCache);
this._eleArray.splice(0, this._eleArray.length, ...eleViewCache);
}
- finished();
+ finished(options);
});
PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
};
- public static PanelName = 'PRESBOX';
-
- static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
+ static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: (options: FocusViewOptions) => void) {
if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
(DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
return;
@@ -886,9 +814,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
DocumentView.SetLightboxDoc(undefined);
}
- DocumentView.showDocument(targetDoc, options, finished);
+ DocumentView.showDocument(targetDoc, options, () => finished?.(options));
});
- } else finished?.();
+ } else finished?.(options);
}
/**
@@ -900,8 +828,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.childDocs.forEach((doc, index) => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = PresBox.targetRenderedDoc(curDoc);
- const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, curDoc);
- let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
+ const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc);
+ let opacity = index === this.itemIndex ? 1 : undefined;
if (curDoc.presentation_hide) {
if (index !== this.itemIndex) {
opacity = 1;
@@ -913,9 +841,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
opacity = 0;
} else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
opacity = 1;
- setTimeout(() => {
- tagDoc._dataTransition = undefined;
- }, 1000);
}
}
const hidingIndAft =
@@ -1687,20 +1612,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
</div>
{[DocumentType.AUDIO, DocumentType.VID].includes(targetType as DocumentType) ? null : (
- <>
- <div className="ribbon-doubleButton">
- <div className="presBox-subheading">Slide Duration</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+ <div className="ribbon-doubleButton">
+ <Tooltip title={<div>How long to view the slide before transitioning to the next slide</div>}>
+ <div className="presBox-subheading">DURATION</div>
+ </Tooltip>
+ <div className="presBox-subheading-slider">
+ {PresBox.inputter('0.1', '0.1', '10', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
+ <div className="slider-headers">
+ <div className="slider-text">Short</div>
+ <div className="slider-text">Long</div>
</div>
</div>
- {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
- <div className="slider-headers" style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
- <div className="slider-text">Short</div>
- <div className="slider-text">Medium</div>
- <div className="slider-text">Long</div>
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+ <input className="presBox-inputNumber" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateDurationTime(e.target.value))} />
+ <span>s</span>
</div>
- </>
+ </div>
)}
</div>
</div>
@@ -1708,28 +1635,62 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
return undefined;
}
- @computed get progressivizeDropdown() {
+
+ @computed get mediaDropdown() {
const { activeItem } = this;
if (activeItem && this.targetDoc) {
- const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
- const bulletEffect = (presEffect: PresEffect) => (
- <div
- className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
- onPointerDown={StopEvent}
- onClick={() => this.updateEffect(presEffect, true)}>
- {presEffect}
+ return (
+ <div className="presBox-option-block">
+ <div className="presBox-ribbon presbox-toggles">
+ <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}>
+ <div
+ className={`ribbon-toggle ${BoolCast(activeItem.presentation_playAudio) ? 'active' : ''}`}
+ style={{
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: BoolCast(activeItem.presentation_playAudio) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ }}
+ onClick={() => {
+ activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio);
+ }}>
+ Play Audio Annotation
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}>
+ <div
+ className={`ribbon-toggle ${BoolCast(activeItem.presentation_zoomText) ? 'active' : ''}`}
+ style={{
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: BoolCast(activeItem.presentation_zoomText) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ }}
+ onClick={() => {
+ activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
+ }}>
+ Zoom Text Selections
+ </div>
+ </Tooltip>
+ </div>
</div>
);
+ }
+ return null;
+ }
+ @computed get progressivizeDropdown() {
+ const { activeItem } = this;
+ if (activeItem && this.targetDoc) {
return (
<div className="presBox-option-block">
- <div className="presBox-ribbon">
- <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
- <div className="presBox-subheading">Progressivize Collection</div>
- <input
- className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
- type="checkbox"
- onChange={() => {
+ <div className="presBox-toggles presBox-ribbon">
+ <Tooltip title={<div className="dash-tooltip">whether progressivization is active for this slide</div>}>
+ <div
+ className={`ribbon-toggle ${Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? 'active' : ''}`}
+ style={{
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ }}
+ onClick={() => {
activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined;
const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
@@ -1742,62 +1703,51 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
+ }}>
+ Enable
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}>
+ <div
+ className={`ribbon-toggle ${!NumCast(activeItem.presentation_indexedStart) ? 'active' : ''}`}
+ style={{
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: !NumCast(activeItem.presentation_indexedStart) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
}}
- checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined}
- />
- </div>
- <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
- <div className="presBox-subheading">Progressivize First Bullet</div>
- <input
- className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
- type="checkbox"
- onChange={() => {
+ onClick={() => {
activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1;
- }}
- checked={!NumCast(activeItem.presentation_indexedStart)}
- />
- </div>
- <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
- <div className="presBox-subheading">Expand Current Bullet</div>
- <input
- className="presBox-checkbox"
- style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
- type="checkbox"
- onChange={() => {
- activeItem.presBulletExpand = !activeItem.presBulletExpand;
- }}
- checked={BoolCast(activeItem.presBulletExpand)}
- />
- </div>
-
- <div className="ribbon-box">
- Bullet Effect
+ }}>
+ All Bullets
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">Whether the active bullet expands when active.</div>}>
<div
- className="presBox-dropdown"
- onClick={action(e => {
- e.stopPropagation();
- this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
- })}
+ className={`ribbon-toggle ${BoolCast(activeItem.presBulletExpand) ? 'active' : ''}`}
style={{
+ border: `solid 1px ${SnappingManager.userColor}`,
color: SnappingManager.userColor,
- background: SnappingManager.userVariantColor,
- borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
- border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
+ background: BoolCast(activeItem.presBulletExpand) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ }}
+ onClick={() => {
+ activeItem.presBulletExpand = !activeItem.presBulletExpand;
}}>
- {effect?.toString()}
- <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
- <div
- className="presBox-dropdownOptions"
- style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
- onPointerDown={e => e.stopPropagation()}>
- {Object.values(PresEffect)
- .filter(v => isNaN(Number(v)))
- .map(pEffect => bulletEffect(pEffect))}
- </div>
+ Expand Active
</div>
- </div>
+ </Tooltip>
</div>
+ <Dropdown
+ color={SnappingManager.userColor}
+ formLabel="Effect"
+ toolTip="Animation effect to use when bullet activates"
+ formLabelPlacement="left"
+ closeOnSelect
+ items={Object.values(PresEffect).map(v => ({ text: v.toString(), val: v }))}
+ selectedVal={StrCast(activeItem.presBulletEffect, PresMovement.None)}
+ setSelectedVal={val => this.updateEffect(val as PresEffect, true)}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ />
</div>
);
}
@@ -1808,23 +1758,89 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return <div />;
}
+ /**
+ * This chatbox is for getting slide effect transition suggestions from gpt and visualizing them
+ */
+ @computed get aiEffects() {
+ return (
+ <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 || !this._showAIGallery ? 'none' : undefined }}>
+ {/* Custom */}
+ <div className="pres-chat">
+ <div className="pres-chatbox-container-ai">
+ <ReactTextareaAutosize
+ placeholder="Use AI to suggest effects. Leave blank for random results."
+ className="pres-chatbox"
+ ref={r => {
+ setTimeout(() => {
+ if (r && !r.textContent) {
+ r.style.height = '';
+ r.style.height = r.scrollHeight + 'px';
+ }
+ });
+ }}
+ value={this._animationChat}
+ onChange={e => {
+ e.currentTarget.style.height = '';
+ e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
+ this.setAnimationChat(e.target.value);
+ }}
+ onKeyDown={e => {
+ this.stopDictation();
+ e.stopPropagation();
+ }}
+ />
+ </div>
+ <Button
+ style={{ alignSelf: 'flex-end' }}
+ text="Send"
+ type={Type.TERT}
+ icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
+ iconPlacement="right"
+ color={SnappingManager.userVariantColor}
+ onClick={this.customizeAnimations}
+ />
+ </div>
+ <div style={{ alignItems: 'center' }}>
+ Click a box to use the effect.
+ {/* Preview Animations */}
+ <div className="presBox-effects">
+ {this.generatedAnimations.map((elem, i) => (
+ <div
+ key={i}
+ className="presBox-effect-container"
+ onClick={() => {
+ this.updateEffect(elem.effect, false);
+ this.updateEffectDirection(elem.direction);
+ this.updateEffectTiming(this.activeItem, {
+ type: SpringType.CUSTOM,
+ stiffness: elem.stiffness,
+ damping: elem.damping,
+ mass: elem.mass,
+ });
+ }}>
+ <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
+ <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
+ </SlideEffect>
+ </div>
+ ))}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
@computed get transitionDropdown() {
const { activeItem } = this;
// Retrieving spring timing properties
- const timing = StrCast(activeItem.presentation_effectTiming);
- let timingConfig: SpringSettings | undefined;
- if (timing) {
- timingConfig = JSON.parse(timing);
- }
-
- if (!timingConfig) {
- timingConfig = {
- type: SpringType.GENTLE,
- stiffness: 100,
- damping: 15,
- mass: 1,
- };
- }
+ const timing = StrCast(activeItem?.presentation_effectTiming);
+ const timingConfig: SpringSettings = timing
+ ? JSON.parse(timing)
+ : {
+ type: SpringType.GENTLE,
+ stiffness: 100,
+ damping: 15,
+ mass: 1,
+ };
if (activeItem && this.targetDoc) {
const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5;
@@ -1835,20 +1851,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return (
<>
{/* This chatbox is for customizing the properties of trails, like transition time, movement type (zoom, pan) using GPT */}
- <div className="presBox-gpt-chat">
- <span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+ <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
+ <span className="presBox-gpt-chat-span">
Customize Slide Properties{' '}
<div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/trails/#slide-customization')}>
<IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SnappingManager.userColor} />
</div>
</span>
<div className="pres-chat">
- <div className="pres-chatbox-container">
+ <div className="pres-chatbox-container-ai">
<ReactTextareaAutosize
- placeholder="Describe how you would like to modify the slide properties."
+ placeholder="Describe how to modify the slide properties."
className="pres-chatbox"
- value={this.chatInput}
+ ref={r => {
+ setTimeout(() => {
+ if (r && !r.textContent) {
+ r.style.height = '';
+ r.style.height = r.scrollHeight + 'px';
+ }
+ });
+ }}
+ value={this._chatInput}
onChange={e => {
+ e.currentTarget.style.height = '';
+ e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
this.setChatInput(e.target.value);
}}
onKeyDown={e => {
@@ -1858,11 +1884,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
/>
<IconButton
type={Type.TERT}
- color={this.isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
+ color={this._isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
tooltip="Record"
icon={<BiMicrophone size="16px" />}
onClick={() => {
- if (!this.isRecording) {
+ if (!this._isRecording) {
this.recordDictation();
} else {
this.stopDictation();
@@ -1874,16 +1900,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
style={{ alignSelf: 'flex-end' }}
text="Send"
type={Type.TERT}
- icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
+ icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
iconPlacement="right"
color={SnappingManager.userVariantColor}
onClick={() => {
this.stopDictation();
- this.customizeWithGPT(this.chatInput);
+ this.customizeWithGPT(this._chatInput);
}}
/>
</div>
</div>
+
{/* Movement */}
<div
className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
@@ -1895,116 +1922,68 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openEffectDropdown = false;
this._openBulletEffectDropdown = false;
})}>
- <div
- className="presBox-option-block"
- // style={{ padding: '16px' }}
- >
- Movement
+ <div className="presBox-option-block">
+ <div className="ribbon-doubleButton">
+ <Tooltip title={<div>How long the transition (view navigation and slide animation effect) lasts</div>}>
+ <div className="presBox-subheading">SPEED</div>
+ </Tooltip>
+ <div className="presBox-subheading-slider">
+ {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)}
+ <div className="slider-headers">
+ <div className="slider-text">Fast</div>
+ <div className="slider-text">Slow</div>
+ </div>
+ </div>
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+ <input className="presBox-inputNumber" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} />
+ <span>s</span>
+ </div>
+ </div>
<Dropdown
color={SnappingManager.userColor}
- formLabel="Movement"
+ formLabel="View"
+ formLabelPlacement="left"
closeOnSelect
items={movementItems}
selectedVal={this.movementName(activeItem)}
- setSelectedVal={val => {
- this.updateMovement(val as PresMovement);
- }}
+ setSelectedVal={val => this.updateMovement(val as PresMovement)}
dropdownType={DropdownType.SELECT}
type={Type.TERT}
/>
- <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
- <div className="presBox-subheading">Zoom (% screen filled)</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
- <input className="presBox-input" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
- </div>
- </div>
- {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
- <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
- <div className="presBox-subheading">Transition Time</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
- <input className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? undefined : 'none' }}>
+ <Tooltip title={<div>How much (%) of screen target should occupy</div>}>
+ <div className="presBox-subheading">ZOOM %</div>
+ </Tooltip>
+ <div className="presBox-subheading-slider">{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}</div>
+ <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+ <input className="presBox-inputNumber" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />
+ <span>%</span>
</div>
</div>
- {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)}
- <div className="slider-headers">
- <div className="slider-text">Fast</div>
- <div className="slider-text">Medium</div>
- <div className="slider-text">Slow</div>
- </div>
{/* Easing function */}
- <Dropdown
- color={SnappingManager.userColor}
- formLabel="Easing Function"
- closeOnSelect
- items={easeItems}
- selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
- setSelectedVal={val => {
- if (typeof val === 'string') {
- if (val !== 'custom') {
- this.setEaseFunc(this.activeItem, val);
- } else {
- this.setBezierEditorVisibility(true);
- this.setEaseFunc(this.activeItem, TIMING_DEFAULT_MAPPINGS.ease);
- }
- }
- }}
- dropdownType={DropdownType.SELECT}
- type={Type.TERT}
- />
- {/* Custom */}
- <div
- className="presBox-show-hide-dropdown"
- style={{ alignSelf: 'flex-start' }}
- onClick={e => {
- e.stopPropagation();
- this.setBezierEditorVisibility(!this.showBezierEditor);
- }}>
- {`${this.showBezierEditor ? 'Hide' : 'Show'} Timing Editor`}
- <FontAwesomeIcon icon={this.showBezierEditor ? 'chevron-up' : 'chevron-down'} />
- </div>
+ {!this.showEaseFunctions ? null : (
+ <Dropdown
+ color={SnappingManager.userColor}
+ formLabel="Timing"
+ formLabelPlacement="left"
+ closeOnSelect
+ items={easeItems}
+ selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
+ setSelectedVal={val => typeof val === 'string' && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ />
+ )}
</div>
</div>
{/* Cubic bezier editor */}
- {this.showBezierEditor && (
- <div className="presBox-option-block" style={{ paddingTop: 0 }}>
- <p className="presBox-submenu-label" style={{ alignSelf: 'flex-start' }}>
- Custom Timing Function
- </p>
+ {this.showEaseFunctions && StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') && (
+ <div className="presBox-option-block" style={{ paddingTop: 0, alignItems: 'center' }}>
<CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} />
</div>
)}
- {/* This chatbox is for getting slide effect transition suggestions from gpt and visualizing them */}
- <div className="presBox-gpt-chat">
- Effects
- <div className="pres-chat">
- <div className="pres-chatbox-container">
- <ReactTextareaAutosize
- placeholder="Customize prompt for effect suggestions. Leave blank for random results."
- className="pres-chatbox"
- value={this.animationChat}
- onChange={e => {
- this.setAnimationChat(e.target.value);
- }}
- onKeyDown={e => {
- this.stopDictation();
- e.stopPropagation();
- }}
- />
- </div>
- <Button
- style={{ alignSelf: 'flex-end' }}
- text="Send"
- type={Type.TERT}
- icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
- iconPlacement="right"
- color={SnappingManager.userVariantColor}
- onClick={this.customizeAnimations}
- />
- </div>
- </div>
-
<div
className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
onPointerDown={StopEvent}
@@ -2016,213 +1995,168 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this._openBulletEffectDropdown = false;
})}>
<div className="presBox-option-block">
- Click on a box to apply the effect.
- <div className="presBox-option-block presBox-option-center">
- {/* Preview Animations */}
- <div className="presBox-effects">
- {this.generatedAnimations.map((elem, i) => (
- <div
- key={i}
- className="presBox-effect-container"
- onClick={() => {
- this.updateEffect(elem.effect, false);
- this.updateEffectDirection(elem.direction);
- this.updateEffectTiming(this.activeItem, {
- type: SpringType.CUSTOM,
- stiffness: elem.stiffness,
- damping: elem.damping,
- mass: elem.mass,
- });
- }}>
- <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
- <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
- </SlideEffect>
- </div>
- ))}
+ {/* Effect dropdown */}
+ <div style={{ display: 'flex' }}>
+ <Dropdown
+ color={SnappingManager.userColor}
+ formLabel="Effect"
+ toolTip="Animation effect to apply when transitiong to slide"
+ formLabelPlacement="left"
+ closeOnSelect
+ items={effectItems}
+ selectedVal={effect?.toString()}
+ setSelectedVal={val => {
+ this.updateEffect(val as PresEffect, false);
+ // set default spring options for that effect
+ this.updateEffectTiming(activeItem, presEffectDefaultTimings[val as keyof typeof presEffectDefaultTimings]);
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ />
+
+ <div
+ className={`ribbon-toggle ${this._showAIGallery ? 'active' : ''}`}
+ style={{
+ border: `solid 1px ${SnappingManager.userColor}`,
+ color: SnappingManager.userColor,
+ background: this._showAIGallery ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+ }}
+ onClick={() => this.setShowAIGalleryVisibilty(!this._showAIGallery)}>
+ MORE
</div>
</div>
- {/* Effect dropdown */}
- <Dropdown
- color={SnappingManager.userColor}
- formLabel="Slide Effect"
- closeOnSelect
- items={effectItems}
- selectedVal={effect?.toString()}
- setSelectedVal={val => {
- this.updateEffect(val as PresEffect, false);
- // set default spring options for that effect
- this.updateEffectTiming(activeItem, presEffectDefaultTimings[val as keyof typeof presEffectDefaultTimings]);
- }}
- dropdownType={DropdownType.SELECT}
- type={Type.TERT}
- />
- {/* Effect direction */}
- {/* Only applies to certain effects */}
- {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && (
- <>
- <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
- <div className="presBox-subheading">Effect direction</div>
- <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
- {StrCast(this.activeItem.presentation_effectDirection)}
- </div>
- </div>
- <div className="presBox-icon-list">
- <IconButton
- type={Type.TERT}
- color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
- tooltip="Left"
- icon={<FaArrowRight size="16px" />}
- onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
- />
- <IconButton
- type={Type.TERT}
- color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
- tooltip="Right"
- icon={<FaArrowLeft size="16px" />}
- onClick={() => this.updateEffectDirection(PresEffectDirection.Right)}
- />
- {effect !== PresEffect.Roll && (
- <>
+ {this.aiEffects}
+ <div className="presBox-gpt-chat">
+ {/* Effect direction */}
+ {/* Only applies to certain effects */}
+ {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && (
+ <div className="ribbon-doubleButton">
+ <div className="presBox-subheading">DIRECTION</div>
+ <div style={{ width: '100%' }}>
+ <div className="presBox-icon-list" style={{ width: 'fit-content', margin: 'auto' }}>
<IconButton
type={Type.TERT}
- color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
- tooltip="Top"
- icon={<FaArrowDown size="16px" />}
- onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
+ color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+ tooltip="Left"
+ icon={<FaArrowRight size="16px" />}
+ onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
/>
<IconButton
type={Type.TERT}
- color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
- tooltip="Bottom"
- icon={<FaArrowUp size="16px" />}
- onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)}
- />
- </>
- )}
- </div>
- </>
- )}
- {/* Spring settings */}
- {/* No spring settings for jackinthebox (lightspeed) */}
- {effect !== PresEffect.Lightspeed && (
- <>
- <Dropdown
- color={SnappingManager.userColor}
- formLabel="Effect Timing"
- closeOnSelect
- items={effectTimings}
- selectedVal={timingConfig.type}
- setSelectedVal={val => {
- this.updateEffectTiming(activeItem, {
- type: val as SpringType,
- ...springMappings[val],
- });
- }}
- dropdownType={DropdownType.SELECT}
- type={Type.TERT}
- />
- <div
- className="presBox-show-hide-dropdown"
- onClick={e => {
- e.stopPropagation();
- this.setSpringEditorVisibility(!this.showSpringEditor);
- }}>
- {`${this.showSpringEditor ? 'Hide' : 'Show'} Spring Settings`}
- <FontAwesomeIcon icon={this.showSpringEditor ? 'chevron-up' : 'chevron-down'} />
- </div>
- {this.showSpringEditor && (
- <>
- <div>Tension</div>
- <div
- onPointerDown={e => {
- e.stopPropagation();
- }}>
- <Slider
- min={1}
- max={1000}
- step={5}
- size="small"
- value={timingConfig.stiffness}
- onChange={(e, val) => {
- if (!timingConfig) return;
- this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number });
- }}
- valueLabelDisplay="auto"
- />
- </div>
- <div>Damping</div>
- <div
- onPointerDown={e => {
- e.stopPropagation();
- }}>
- <Slider
- min={1}
- max={100}
- step={1}
- size="small"
- value={timingConfig.damping}
- onChange={(e, val) => {
- if (!timingConfig) return;
- this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number });
- }}
- valueLabelDisplay="auto"
- />
- </div>
- <div>Mass</div>
- <div
- onPointerDown={e => {
- e.stopPropagation();
- }}>
- <Slider
- min={1}
- max={10}
- step={1}
- size="small"
- value={timingConfig.mass}
- onChange={(e, val) => {
- if (!timingConfig) return;
- this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number });
- }}
- valueLabelDisplay="auto"
+ color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+ tooltip="Right"
+ icon={<FaArrowLeft size="16px" />}
+ onClick={() => this.updateEffectDirection(PresEffectDirection.Right)}
/>
+ {effect !== PresEffect.Roll && (
+ <>
+ <IconButton
+ type={Type.TERT}
+ color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+ tooltip="Top"
+ icon={<FaArrowDown size="16px" />}
+ onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
+ />
+ <IconButton
+ type={Type.TERT}
+ color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+ tooltip="Bottom"
+ icon={<FaArrowUp size="16px" />}
+ onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)}
+ />
+ </>
+ )}
</div>
- Preview Effect
- <div className="presBox-option-block presBox-option-center">
- <div className="presBox-effect-container">
- <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig} infinite>
- <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
- </SlideEffect>
- </div>
- </div>
- </>
- )}
- </>
- )}
+ </div>
+ </div>
+ )}
+ {![PresEffect.Lightspeed, PresEffect.Fade, PresEffect.None, ''].includes(effect) && (
+ <>
+ <Dropdown
+ color={SnappingManager.userColor}
+ formLabel="Springiness"
+ formLabelPlacement="left"
+ closeOnSelect
+ items={effectTimings}
+ selectedVal={timingConfig.type}
+ setSelectedVal={val => this.updateEffectTiming(activeItem, { type: val as SpringType, ...springMappings[val] })}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ />
+
+ <div style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
+ {/* No spring settings for jackinthebox (lightspeed) */}
+ {StrCast(activeItem.presentation_effectTiming).includes('custom') && effect !== PresEffect.None && (
+ <>
+ <div className="presBox-springSlider">
+ <span>Tension</span>
+ <div onPointerDown={e => e.stopPropagation()}>
+ {/* prettier-ignore */}
+ <Slider min={1} max={1000} step={5} size="small"
+ value={timingConfig.stiffness}
+ onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ </div>
+ <div className="presBox-springSlider">
+ <span>Damping</span>
+ <div onPointerDown={e => e.stopPropagation()}>
+ {/* prettier-ignore */}
+ <Slider min={1} max={100} step={1} size="small"
+ value={timingConfig.damping}
+ onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number })}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ </div>
+ <div className="presBox-springSlider">
+ <span>Mass</span>
+ <div onPointerDown={e => e.stopPropagation()}>
+ {/* prettier-ignore */}
+ <Slider min={1} max={10} step={1} size="small"
+ value={timingConfig.mass}
+ onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number })}
+ valueLabelDisplay="auto"
+ />
+ </div>
+ </div>
+ </>
+ )}
+ </div>
+ </>
+ )}
+ </div>
</div>
+ </div>
- {/* Toggles */}
- <div className="presBox-option-block">
- <Toggle
- formLabel="Play Audio Annotation"
- toggleType={ToggleType.SWITCH}
- toggleStatus={BoolCast(activeItem.presentation_playAudio)}
- onClick={() => {
- activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio);
- }}
- color={SnappingManager.userColor}
- />
- <Toggle
- formLabel="Zoom Text Selections"
- toggleType={ToggleType.SWITCH}
- toggleStatus={BoolCast(activeItem.presentation_zoomText)}
- onClick={() => {
- activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
- }}
+ {[PresEffect.None, PresEffect.Fade, ''].includes(effect) ? null : (
+ <div className="presBox-previewContainer">
+ <Button
+ type={Type.TERT}
+ tooltip="show preview of slide animation effect"
+ size={Size.SMALL}
color={SnappingManager.userColor}
+ background="transparent"
+ onClick={action(() => {
+ this._showPreview = false;
+ setTimeout(action(() => { this._showPreview = true; }) ); // prettier-ignore
+ })}
+ text="Preview Effect"
/>
- <Button text="Apply to all" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} />
+ <div className="presBox-option-block presBox-option-center">
+ <div className="presBox-effect-container">
+ {!this._showPreview ? null : (
+ <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}>
+ <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
+ </SlideEffect>
+ )}
+ </div>
+ </div>
</div>
- </div>
+ )}
+
+ <Button text="Apply to all slides" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} />
</>
);
}
@@ -2248,7 +2182,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
<input
className="presBox-input"
- style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
readOnly
value={NumCast(activeItem.config_clipStart).toFixed(2)}
@@ -2275,7 +2208,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input
className="presBox-input"
onKeyDown={e => e.stopPropagation()}
- style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
type="number"
readOnly
value={configClipEnd.toFixed(2)}
@@ -3083,7 +3015,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
} */}
</div>
{/* presbox chatbox */}
- {this.chatActive && <div className="presBox-chatbox" />}
+ {this._chatActive && <div className="presBox-chatbox" />}
</div>
);
}
diff --git a/src/client/views/nodes/trails/SpringUtils.ts b/src/client/views/nodes/trails/SpringUtils.ts
index 73e1e14f1..044afbeb1 100644
--- a/src/client/views/nodes/trails/SpringUtils.ts
+++ b/src/client/views/nodes/trails/SpringUtils.ts
@@ -22,7 +22,14 @@ export interface SpringSettings {
}
// Overall config
-
+// Keeps these settings in sync with the AnimationSettings interface (used by gpt);
+export enum AnimationSettingsProperties {
+ effect = 'effect',
+ direction = 'direction',
+ stiffness = 'stiffness',
+ damping = 'damping',
+ mass = 'mass',
+}
export interface AnimationSettings {
effect: PresEffect;
direction: PresEffectDirection;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 22ca9dcd7..f5a9f9e6a 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, Type } from '@dash/components';
-import { action, makeObservable, observable } from 'mobx';
+import { Button, IconButton, Toggle, ToggleType, Type } from '@dash/components';
+import { action, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CgClose, CgCornerUpLeft } from 'react-icons/cg';
@@ -37,10 +37,8 @@ export enum GPTQuizType {
MULTIPLE = 2,
}
-interface GPTPopupProps {}
-
@observer
-export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
+export class GPTPopup extends ObservableReactComponent<object> {
// eslint-disable-next-line no-use-before-define
static Instance: GPTPopup;
private messagesEndRef: React.RefObject<HTMLDivElement>;
@@ -48,115 +46,84 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
@observable private chatMode: boolean = false;
private correlatedColumns: string[] = [];
- @observable
- public visible: boolean = false;
- @action
- public setVisible = (vis: boolean) => {
- this.visible = vis;
+ @observable public Visible: boolean = false;
+ @action public setVisible = (vis: boolean) => {
+ this.Visible = vis;
};
- @observable
- public loading: boolean = false;
- @action
- public setLoading = (loading: boolean) => {
+ @observable public loading: boolean = false;
+ @action public setLoading = (loading: boolean) => {
this.loading = loading;
};
- @observable
- public text: string = '';
- @action
- public setText = (text: string) => {
+ @observable public text: string = '';
+ @action public setText = (text: string) => {
this.text = text;
};
- @observable
- public selectedText: string = '';
- @action
- public setSelectedText = (text: string) => {
+ @observable public selectedText: string = '';
+ @action public setSelectedText = (text: string) => {
this.selectedText = text;
};
- @observable
- public dataJson: string = '';
+ @observable public dataJson: string = '';
public dataChatPrompt: string | undefined = undefined;
- @action
- public setDataJson = (text: string) => {
+ @action public setDataJson = (text: string) => {
if (text === '') this.dataChatPrompt = '';
this.dataJson = text;
};
- @observable
- public imgDesc: string = '';
- @action
- public setImgDesc = (text: string) => {
+ @observable public imgDesc: string = '';
+ @action public setImgDesc = (text: string) => {
this.imgDesc = text;
};
- @observable
- public imgUrls: string[][] = [];
- @action
- public setImgUrls = (imgs: string[][]) => {
+ @observable public imgUrls: string[][] = [];
+ @action public setImgUrls = (imgs: string[][]) => {
this.imgUrls = imgs;
};
- @observable
- public mode: GPTPopupMode = GPTPopupMode.SUMMARY;
- @action
- public setMode = (mode: GPTPopupMode) => {
+ @observable public mode: GPTPopupMode = GPTPopupMode.SUMMARY;
+ @action public setMode = (mode: GPTPopupMode) => {
this.mode = mode;
};
- @observable
- public highlightRange: number[] = [];
+ @observable public highlightRange: number[] = [];
@action callSummaryApi = () => {};
- @observable
- private done: boolean = false;
- @action
- public setDone = (done: boolean) => {
+ @observable private done: boolean = false;
+ @action public setDone = (done: boolean) => {
this.done = done;
this.chatMode = false;
};
- @observable
- private sortDone: boolean = false; // this is so redundant but the og done variable was causing weird unknown problems and im just a girl
-
- @action
- public setSortDone = (done: boolean) => {
- this.sortDone = done;
- };
-
// change what can be a ref into a ref
- @observable
- private sidebarId: string = '';
- @action
- public setSidebarId = (id: string) => {
+ @observable private sidebarId: string = '';
+ @action public setSidebarId = (id: string) => {
this.sidebarId = id;
};
- @observable
- private imgTargetDoc: Doc | undefined;
- @action
- public setImgTargetDoc = (anchor: Doc) => {
+ @observable private imgTargetDoc: Doc | undefined;
+ @action public setImgTargetDoc = (anchor: Doc) => {
this.imgTargetDoc = anchor;
};
- @observable
- private textAnchor: Doc | undefined;
- @action
- public setTextAnchor = (anchor: Doc) => {
+ @observable private textAnchor: Doc | undefined;
+ @action public setTextAnchor = (anchor: Doc) => {
this.textAnchor = anchor;
};
- @observable
- public sortDesc: string = '';
-
+ @observable public sortDesc: string = '';
@action public setSortDesc = (t: string) => {
this.sortDesc = t;
};
- @observable onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void;
- @observable onQuizRandom?: () => void;
+ onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void;
+ onQuizRandom?: () => void;
@observable cardsDoneLoading = false;
+ @observable collectionDoc: Doc | undefined = undefined;
+ @action setCollectionDoc(doc: Doc | undefined) {
+ this.collectionDoc = doc;
+ }
+
@action setCardsDoneLoading(done: boolean) {
- console.log(done + 'HI HIHI');
this.cardsDoneLoading = done;
}
@@ -186,39 +153,27 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
*/
generateQuiz = async () => {
this.setLoading(true);
- this.setSortDone(false);
- const quizType = this.quizMode;
+ await this.regenerateCallback?.();
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));
+ if (!StrCast(selected.gptRubric)) {
+ await this.generateRubric(StrCast(selected.gptInputText), selected);
}
- const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']);
- 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.conversationArray.push(res);
+ const res = await gptAPICall('Question: ' + StrCast(selected.gptInputText) + ' UserAnswer: ' + this.quizAnswer + '. Rubric: ' + StrCast(selected.gptRubric), GPTCallType.QUIZ);
+ if (res) {
+ this.setQuizResp(res);
+ this.conversationArray.push(res);
- this.setLoading(false);
- this.setSortDone(true);
+ this.setLoading(false);
+ this.onQuizRandom?.();
+ } else {
+ console.error('GPT provided no response');
+ }
} catch (err) {
- console.error('GPT call failed');
- }
-
- if (this.onQuizRandom) {
- this.onQuizRandom();
+ console.error('GPT call failed', err);
}
};
@@ -231,10 +186,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
generateRubric = async (inputText: string, doc: Doc) => {
try {
const res = await gptAPICall(inputText, GPTCallType.RUBRIC);
- doc['gptRubric'] = res;
+ doc.gptRubric = res;
return res;
} catch (err) {
- console.error('GPT call failed');
+ console.error('GPT call failed', err);
}
};
@@ -244,7 +199,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
* Callback function that causes the card view to update the childpair string list
* @param callback
*/
- @action public setRegenerateCallback(callback: () => Promise<void>) {
+ @action public setRegenerateCallback(collectionDoc: Doc | undefined, callback: null | (() => Promise<void>)) {
+ this.setCollectionDoc(collectionDoc);
this.regenerateCallback = callback;
}
@@ -262,37 +218,21 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
* Generates a response to the user's question depending on the type of their question
*/
generateCard = async () => {
- console.log(this.chatSortPrompt + 'USER PROMPT');
this.setLoading(true);
- this.setSortDone(false);
- if (this.regenerateCallback) {
- await this.regenerateCallback();
- }
+ await this.regenerateCallback?.();
try {
- // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE);
- const questionNumber = questionType.split(' ')[0];
- console.log(questionType);
- let res = '';
-
- switch (questionNumber) {
- case '1':
- case '2':
- case '4':
- res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
- break;
- case '6':
- res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
- break;
- default:
- const selected = DocumentView.SelectedDocs().lastElement();
- const questionText = StrCast(selected!['gptInputText']);
-
- res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt);
- break;
- }
+ const questionNumber = questionType.split(' ')[0][0];
+ const res = await (() => {
+ switch (questionNumber) {
+ case '1':
+ case '2':
+ case '4': return gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
+ case '6': return gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
+ default: return gptAPICall(StrCast(DocumentView.SelectedDocs().lastElement()?.gptInputText), GPTCallType.INFO, this.chatSortPrompt);
+ }})(); // prettier-ignore
// Trigger the callback with the result
if (this.onSortComplete) {
@@ -308,7 +248,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
// Set the extracted explanation to sortRespText
this.setSortRespText(explanation);
- this.conversationArray.push(this.sortRespText);
+ runInAction(() => this.conversationArray.push(this.sortRespText));
this.scrollToBottom();
console.log(res);
@@ -318,7 +258,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
}
this.setLoading(false);
- this.setSortDone(true);
};
/**
@@ -448,7 +387,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
private getPreviewUrl = (source: string) => source.split('.').join('_m.');
- constructor(props: GPTPopupProps) {
+ constructor(props: object) {
super(props);
makeObservable(this);
GPTPopup.Instance = this;
@@ -498,9 +437,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
onClick={() => {
this.conversationArray = ['Define the selected card!'];
this.setMode(GPTPopupMode.QUIZ);
- if (this.onQuizRandom) {
- this.onQuizRandom();
- }
+ this.onQuizRandom?.();
}}
color={StrCast(Doc.UserDoc().userVariantColor)}
type={Type.TERT}
@@ -515,18 +452,25 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
</div>
);
- handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => {
+ @action
+ handleKeyPress = (e: React.KeyboardEvent, isSort: boolean) => {
if (e.key === 'Enter') {
e.stopPropagation();
if (isSort) {
this.conversationArray.push(this.chatSortPrompt);
- await this.generateCard();
- this.chatSortPrompt = '';
+ this.generateCard().then(
+ action(() => {
+ this.chatSortPrompt = '';
+ })
+ );
} else {
this.conversationArray.push(this.quizAnswer);
- await this.generateQuiz();
- this.quizAnswer = '';
+ this.generateQuiz().then(
+ action(() => {
+ this.quizAnswer = '';
+ })
+ );
}
this.scrollToBottom();
@@ -569,7 +513,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
};
sortBox = () => (
- <div style={{ height: '80%' }}>
+ <div className="gptPopup-sortBox" style={{ height: '80%' }}>
{this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')}
<>
{
@@ -742,6 +686,15 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
{(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: '50px', position: 'absolute' }} />
)}
+ <Toggle
+ tooltip="Clear Chat filter"
+ toggleType={ToggleType.BUTTON}
+ type={Type.PRIM}
+ toggleStatus={Doc.hasDocFilter(this.collectionDoc, 'tags', '#chat')}
+ text={Doc.hasDocFilter(this.collectionDoc, 'tags', '#chat') ? 'filtered' : ''}
+ color="red"
+ onClick={() => this.collectionDoc && Doc.setDocFilter(this.collectionDoc, 'tags', '#chat', 'remove')}
+ />
<IconButton
color={StrCast(SettingsManager.userVariantColor)}
tooltip="close"
@@ -778,7 +731,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
}
return (
- <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
+ <div className="summary-box" style={{ display: this.Visible ? 'flex' : 'none' }}>
{content}
<div className="resize-handle" />
</div>
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 0252961d3..b294ee8c6 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -263,14 +263,9 @@ export class ComputedField extends ScriptField {
doc[`${fieldKey}_indexed`] = flist;
}
const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey})`, {}, true, {});
- const setField = ScriptField.CompileScript(
- `{setIndexVal (this['${fieldKey}_indexed'], this.${interpolatorKey}, value); console.log(this["${fieldKey}_indexed"][this.${interpolatorKey}],this.data,this["${fieldKey}_indexed"]))}`,
- { value: 'any' },
- false,
- {}
- );
+ const setField = ScriptField.CompileScript(`{setIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, value);}`, { value: 'any' }, false, {});
doc[fieldKey] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
- return doc[fieldKey];
+ return Field.Copy(doc[fieldKey]);
}
}