diff options
Diffstat (limited to 'src')
26 files changed, 299 insertions, 360 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1ce25165c..21d3c978b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -384,6 +384,9 @@ export class DocumentOptions { presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false); presentation_zoomText?: BOOLt = new BoolInfo('whether text anchors should shown in a larger box when following links to make them stand out', false); + data_annotations?: List<Doc>; + _data_usePath?: STRt = new StrInfo("description of field key to display in image box ('alternate','alternate:hover', 'data:hover'). defaults to primary", false); + data_alternates?: List<Doc>; data?: FieldType; data_useCors?: BOOLt = new BoolInfo('whether CORS protocol should be used for web page'); _face_showImages?: BOOLt = new BoolInfo('whether to show images in uniqe face Doc'); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index efc8e79a6..3a248400b 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -502,7 +502,6 @@ export class SharingManager extends React.Component<object> { } }; - // eslint-disable-next-line react/sort-comp public close = action(() => { this.isOpen = false; this.selectedUsers = null; // resets the list of users and selected users (in the react-select component) @@ -517,7 +516,6 @@ export class SharingManager extends React.Component<object> { this.layoutDocAcls = false; }); - // eslint-disable-next-line react/no-unused-class-component-methods public open = (target?: DocumentView, targetDoc?: Doc) => { this.populateUsers(); runInAction(() => { @@ -534,7 +532,6 @@ export class SharingManager extends React.Component<object> { * @param group * @param emailId */ - // eslint-disable-next-line react/no-unused-class-component-methods shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => { const user = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { @@ -559,7 +556,6 @@ export class SharingManager extends React.Component<object> { /** * Called from the properties sidebar to change permissions of a user. */ - // eslint-disable-next-line react/no-unused-class-component-methods shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; if (shareWith !== 'Guest') { @@ -583,7 +579,6 @@ export class SharingManager extends React.Component<object> { * @param group * @param emailId */ - // eslint-disable-next-line react/no-unused-class-component-methods removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; @@ -607,7 +602,6 @@ export class SharingManager extends React.Component<object> { * Removes a group's permissions from documents that have been shared with it. * @param group */ - // eslint-disable-next-line react/no-unused-class-component-methods removeGroup = (group: Doc) => { if (group.docsShared) { DocListCast(group.docsShared).forEach(doc => { diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index 65bd44bf9..0399fe1d5 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -1,6 +1,7 @@ /* eslint-disable no-use-before-define */ /* eslint-disable no-param-reassign */ import { Point } from '../../pen-gestures/ndollar'; +import { numberRange } from '../../Utils'; export enum SVGType { Rect = 'rect', @@ -623,24 +624,20 @@ export function GenerateControlPoints(coordinates: Point[], alpha = 0.1) { return [...firstEnd, ...points, ...lastEnd]; } -function convertToAbsolute(pathData: string): string { +function convertRelativePathCmdsToAbsolute(pathData: string): string { const commands = pathData.match(/[a-zA-Z][^a-zA-Z]*/g); - if (!commands) return pathData; - let currentX = 0; let currentY = 0; let startX = 0; let startY = 0; - - const absoluteCommands = commands.map(command => { - const type = command[0]; + const absoluteCommands = commands?.map(command => { const values = command .slice(1) .trim() .split(/[\s,]+/) .map(v => +v); - switch (type) { + switch (command[0]) { case 'M': currentX = values[0]; currentY = values[1]; @@ -654,13 +651,19 @@ function convertToAbsolute(pathData: string): string { startY = currentY; return `M${currentX},${currentY}`; case 'L': - currentX = values[0]; - currentY = values[1]; - return `L${currentX},${currentY}`; - case 'l': - currentX += values[0]; - currentY += values[1]; - return `L${currentX},${currentY}`; + currentX = values[values.length - 2]; + currentY = values[values.length - 1]; + return `L${values.join(',')}`; + case 'l': { + let str = ''; + for (let i = 0; i < values.length; i += 2) { + str += (i === 0 ? 'L':',') + (values[i] + currentX) + + ',' + (values[i + 1] + currentY); // prettier-ignore + currentX += values[i]; + currentY += values[i + 1]; + } + return str; + } case 'H': currentX = values[0]; return `H${currentX}`; @@ -674,8 +677,8 @@ function convertToAbsolute(pathData: string): string { currentY += values[0]; return `V${currentY}`; case 'C': - currentX = values[4]; - currentY = values[5]; + currentX = values[values.length - 2]; + currentY = values[values.length - 1]; return `C${values.join(',')}`; case 'c': { let str = ''; @@ -698,8 +701,8 @@ function convertToAbsolute(pathData: string): string { case 's': return `S${values.map((v, i) => (i % 2 === 0 ? (currentX += v) : (currentY += v))).join(',')}`; case 'Q': - currentX = values[2]; - currentY = values[3]; + currentX = values[values.length - 2]; + currentY = values[values.length - 1]; return `Q${values.join(',')}`; case 'q': { let str = ''; @@ -737,7 +740,7 @@ function convertToAbsolute(pathData: string): string { } }); - return absoluteCommands.join(' '); + return absoluteCommands?.join(' ') ?? pathData; } export function SVGToBezier(name: SVGType, attributes: Record<string, string>, last: { X: number; Y: number }): Point[] { @@ -805,138 +808,73 @@ export function SVGToBezier(name: SVGType, attributes: Record<string, string>, l ]; } case 'path': { + const cmds = new Map<string, number>([ + ['A', 7], + ['C', 6], + ['Q', 4], + ['L', 2], + ['V', 1], + ['H', 1], + ['Z', 0], + ['M', 2], + ]); + const cmdReg = (letter: string) => `${letter}?${numberRange(cmds.get(letter)??0).map(() => '[, ]?(-?\\d*\\.?\\d*)').join('')}`; // prettier-ignore + const pathdata = convertRelativePathCmdsToAbsolute( + attributes.d + .replace(/([0-9])-/g, '$1,-') // numbers are smooshed together - put a ',' between number-number => number,-number + .replace(/([.][0-9]+)(?=\.)/g, '$1,') // numbers are smooshed together - put a ',' between .number.number => .number,.number + .trim() + ); + const move = pathdata.match(cmdReg('M')); + const start = move?.slice(1).map(v => +v) ?? [last.X, last.Y]; const coordList: Point[] = []; - let fixedattrs = attributes.d.trim().replace(/([0-9])-/g, '$1,-'); - for (let i = 0; i < 100; i++) { - const test = fixedattrs.replace(/([0-9]?\.[0-9]+)(\.[0-9]+)/g, '$1,$2'); - if (test === fixedattrs) break; - fixedattrs = test; - } - const attrdata = convertToAbsolute(fixedattrs); - const move = attrdata.match(/M(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/); - const [startX, startY] = move?.slice(1) ?? [last.X + '', last.Y + '']; - const startPt = { X: +startX, Y: +startY }; - let first = true; - let lastCmd = ''; - for (let attr = attrdata.slice(move?.[0].length ?? 0).trim(); attr; ) { - lastCmd = 'AQCLVHZ'.includes(attr[0]) ? attr[0] : lastCmd; - switch (lastCmd) { - case 'Q': { - const match = attr.match(/Q?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/); - if (match) { - const prev = first ? startPt : coordList.lastElement(); - const Q = [+match[1], +match[2], +match[3], +match[4]]; - - coordList.push(prev); - coordList.push({ X: prev.X + (2 / 3) * (Q[0] - prev.X), Y: prev.Y + (2 / 3) * (Q[1] - prev.Y) }); - coordList.push({ X: Q[2] + (2 / 3) * (Q[0] - Q[2]), Y: Q[3] + (2 / 3) * (Q[1] - Q[3]) }); - coordList.push({ X: Q[2], Y: Q[3] }); - attr = attr.slice(match[0].length).trim(); - } else { - attr = attr.slice(1).trim(); - alert('error' + attr); - } - break; - } - case 'C': { - const match = attr.match(/C?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/); - if (match) { - coordList.push(first ? startPt : coordList.lastElement()); - coordList.push({ X: +match[1], Y: +match[2] }); - coordList.push({ X: +match[3], Y: +match[4] }); - coordList.push({ X: +match[5], Y: +match[6] }); - attr = attr.slice(match[0].length).trim(); - } else { - attr = attr.slice(1).trim(); - alert('error' + attr); - } - break; - } - case 'A': { - const match = attr.match(/A?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/); - if (match) { - console.log('SKIPPING arc - not implemented'); - // coordList.push(first ? startPt : coordList.lastElement()); - // coordList.push({ X: +match[1], Y: +match[2] }); - // coordList.push({ X: +match[3], Y: +match[4] }); - // coordList.push({ X: +match[5], Y: +match[6] }); - attr = attr.slice(match[0].length).trim(); - } else { - attr = attr.slice(1).trim(); - alert('error' + attr); - } - break; - } - case 'L': { - const match = attr.match(/L?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/); - if (match) { - coordList.push(first ? startPt : coordList.lastElement()); - coordList.push(coordList.lastElement()); - coordList.push({ X: +match[1], Y: +match[2] }); - coordList.push({ X: +match[1], Y: +match[2] }); - attr = attr.slice(match[0].length).trim(); - } else { - attr = attr.slice(1).trim(); - alert('error' + attr); - } + for (let prev = coordList.lastElement() ?? { X: start[0], Y: start[1] }, + pathcmd = pathdata.slice(move?.[0].length ?? 0).trim(), + m = move, + lastCmd = ''; + pathcmd; + pathcmd = pathcmd.slice(m?.[0].length ?? 1).trim(), + prev = coordList.lastElement() + ) { + lastCmd = Array.from(cmds.keys()).includes(pathcmd[0]) ? pathcmd[0] : lastCmd; // command character is first, otherwise we're continuing coordinates for the last command + m = pathcmd.match(new RegExp(cmdReg(lastCmd)))!; // matches command + number parameters specific to command + switch (m ? lastCmd : 'error') { + case 'Q': // convert quadratic to Bezier + ((Q) => coordList.push( + prev, + { X: prev.X + (2 / 3) * (Q[0] - prev.X), Y: prev.Y + (2 / 3) * (Q[1] - prev.Y) }, + { X: Q[2] + (2 / 3) * (Q[0] - Q[2]), Y: Q[3] + (2 / 3) * (Q[1] - Q[3]) }, + { X: Q[2], Y: Q[3] } + ))([+m[1], +m[2], +m[3], +m[4]]); + break; case 'C': // bezier curve + coordList.push(prev, { X: +m[1], Y: +m[2] }, { X: +m[3], Y: +m[4] }, { X: +m[5], Y: +m[6] }); + break; case 'L': // convert line to bezier + coordList.push(prev, prev, { X: +m[1], Y: +m[2] }, { X: +m[1], Y: +m[2] }); + break; case 'H': // convert horiz line to bezier + coordList.push(prev, prev, { X: +m[1], Y: prev.Y }, { X: +m[1], Y: prev.Y }); + break; case 'V': // convert vert line to bezier + coordList.push(prev, prev, { X: prev.X, Y: +m[1] }, { X: prev.X, Y: +m[1] }); + break; case 'A': // convert arc to bezier + console.log('SKIPPING arc - conversion to bezier not implemented'); + break; case 'Z': break; - } - case 'H': { - const match = attr.match(/H?[, ]?(-?\d*\.?\d*)/); - if (match) { - coordList.push(first ? startPt : coordList.lastElement()); - coordList.push(coordList.lastElement()); - coordList.push({ X: +match[1], Y: coordList.lastElement().Y }); - coordList.push({ X: +match[1], Y: coordList.lastElement().Y }); - attr = attr.slice(match[0].length).trim(); - } else { - attr = attr.slice(1).trim(); - alert('error' + attr); - } - break; - } - case 'V': { - const match = attr.match(/V?[, ]?(-?\d*\.?\d*)/); - if (match) { - coordList.push(first ? startPt : coordList.lastElement()); - coordList.push(coordList.lastElement()); - coordList.push({ X: coordList.lastElement().X, Y: +match[1] }); - coordList.push({ X: coordList.lastElement().X, Y: +match[1] }); - attr = attr.slice(match[0].length).trim(); - } else { - attr = attr.slice(1).trim(); - alert('error' + attr); - } - break; - } - case 'Z': { - coordList.push(first ? startPt : coordList.lastElement()); - coordList.push(first ? startPt : coordList.lastElement()); - coordList.push(startPt); - coordList.push(startPt); - attr = attr.slice(1).trim(); - break; - } default: - attr = attr.slice(1).trim(); + // eslint-disable-next-line no-debugger debugger; - } - first = false; - } + } // prettier-ignore + } // prettier-ignore return coordList; } case 'polygon': { - const coords: RegExpMatchArray[] = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g)); - let list: Point[] = []; + const coords = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g)); + const list: Point[] = []; coords.forEach(coord => { list.push({ X: +coord[1], Y: +coord[2] }); list.push({ X: +coord[1], Y: +coord[2] }); list.push({ X: +coord[1], Y: +coord[2] }); list.push({ X: +coord[1], Y: +coord[2] }); }); - const firstPts = list.splice(0, 2); - list = list.concat(firstPts); - return list; + return list.concat(list.splice(0, 2)); // repeat start point to close } default: return []; diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss index 48fa86276..c2f6ae62d 100644 --- a/src/client/views/AntimodeMenu.scss +++ b/src/client/views/AntimodeMenu.scss @@ -6,7 +6,6 @@ height: global.$antimodemenu-height; width: fit-content; border-radius: global.$standard-border-radius; - overflow: hidden; // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); // border-radius: 0px 6px 6px 6px; display: flex; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 0be847281..5698da785 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -93,7 +93,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { savedKeys.forEach(key => { lightDoc[key] = this._savedState[key]; }); - this._savedState = {}; + lightDoc !== doc && (this._savedState = {}); if (doc) { lightDoc !== doc && @@ -136,9 +136,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { return this.SetLightboxDoc( doc, undefined, - [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort( - (a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) - ), + [...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), layoutTemplate ); }; diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 2170e0c34..e01e92101 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -1,5 +1,5 @@ @use 'global/globalCssVariables.module.scss' as global; -// bcz: fix @import 'nodeModuleOverrides'; +@import 'nodeModuleOverrides'; html { overscroll-behavior-x: none; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 12dd80ec5..fddc0e40c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -75,10 +75,8 @@ import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; import ImageEditorBox from './nodes/imageEditor/ImageEditor'; import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; -import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; import { TopBar } from './topbar/TopBar'; -import { OverlayView } from './OverlayView'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 8e043baee..11adf7435 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -152,9 +152,16 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return this.selectedDoc?.isGroup; } @computed get isStack() { - return [CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Card, CollectionViewType.Carousel].includes( - this.selectedDoc?.type_collection as CollectionViewType - ); + return [ + CollectionViewType.Masonry, + CollectionViewType.Multicolumn, + CollectionViewType.Multirow, + CollectionViewType.Stacking, + CollectionViewType.NoteTaking, + CollectionViewType.Card, + CollectionViewType.Carousel, + CollectionViewType.Grid, + ].includes(this.selectedDoc?.type_collection as CollectionViewType); } rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20)); diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index b70e21918..93d6fb684 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -398,8 +398,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> { e.stopPropagation(); }} type="text" - placeholder="Input tags for document..." - aria-label="tagsView-input" + placeholder="Enter #tags or @metadata" className="tagsView-input" style={{ width: '100%', borderRadius: '5px' }} /> diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 539b49c86..04e3b2663 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -48,8 +48,7 @@ export class CollectionDockingView extends CollectionSubView() { // eslint-disable-next-line no-use-before-define @observable public static Instance: CollectionDockingView | undefined = undefined; - private _reactionDisposer?: IReactionDisposer; - private _lightboxReactionDisposer?: IReactionDisposer; + private _disposers: { [key: string]: IReactionDisposer } = {}; private _containerRef = React.createRef<HTMLDivElement>(); private _flush: UndoManager.Batch | undefined; private _unmounting = false; @@ -66,6 +65,7 @@ export class CollectionDockingView extends CollectionSubView() { constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); + console.log('CREATING DOCKING VIEW'); if (this._props.renderDepth < 0) CollectionDockingView.Instance = this; // Why is this here? (window as unknown as { React: unknown }).React = React; @@ -282,6 +282,7 @@ export class CollectionDockingView extends CollectionSubView() { } setupGoldenLayout = async () => { if (this._unmounting) return; + console.log('SETUP LAYOUT'); // const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document))); const config = StrCast(this.Document.dockingConfig); if (config) { @@ -339,29 +340,36 @@ export class CollectionDockingView extends CollectionSubView() { componentDidMount: () => void = async () => { this._props.setContentViewBox?.(this); this._unmounting = false; + console.log('MOUNTING'); SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property if (this._containerRef.current) { - this._lightboxReactionDisposer = reaction( + this._disposers.lightbox = reaction( () => DocumentView.LightboxDoc(), doc => setTimeout(() => !doc && this.onResize()) ); new ResizeObserver(this.onResize).observe(this._containerRef.current); - this._reactionDisposer = reaction( + this._disposers.docking = reaction( () => StrCast(this.Document.dockingConfig), config => { if (!this._goldenLayout || this._ignoreStateChange !== config) { + console.log('CONFIG CHANGED'); // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one. this.setupGoldenLayout(); } this._ignoreStateChange = ''; } ); - reaction( + this._disposers.panel = reaction( () => this._props.PanelWidth(), - width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows + width => { + if (!this._goldenLayout && width > 20) { + console.log('PWIDTH = ' + width); + setTimeout(() => this.setupGoldenLayout()); + } + }, // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows { fireImmediately: true } ); - reaction( + this._disposers.color = reaction( () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor], () => { clearStyleSheetRules(CollectionDockingView._highlightStyleSheet); @@ -375,7 +383,9 @@ export class CollectionDockingView extends CollectionSubView() { }; componentWillUnmount: () => void = () => { + console.log('UNMOUNTING'); this._unmounting = true; + Object.values(this._disposers).forEach(d => d()); try { this._goldenLayout.unbind('stackCreated', this.stackCreated); this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); @@ -385,9 +395,6 @@ export class CollectionDockingView extends CollectionSubView() { setTimeout(() => this._goldenLayout?.destroy()); window.removeEventListener('resize', this.onResize); window.removeEventListener('mouseup', this.onPointerUp); - - this._reactionDisposer?.(); - this._lightboxReactionDisposer?.(); }; // ViewBoxInterface overrides @@ -412,6 +419,7 @@ export class CollectionDockingView extends CollectionSubView() { .map(f => f as Doc); const changesMade = this.Document.dockingConfig !== json; if (changesMade) { + console.log('WRITING CONFIG'); if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { this.layoutDoc.dockingConfig = json; this.layoutDoc.data = new List<Doc>(docs); @@ -426,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() { @action onPointerUp = (): void => { - window.removeEventListener('pointerup', this.onPointerUp); + window.removeEventListener('mouseup', this.onPointerUp); DragManager.CompleteWindowDrag = undefined; setTimeout(this.endUndoBatch, 100); }; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index f06e0b551..96125f2c2 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -111,7 +111,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // columnWidth handles the margin on the left and right side of the documents @computed get columnWidth() { const availableWidth = this._props.PanelWidth() - 2 * this.xMargin; - const cwid = availableWidth / NumCast(this.Document._layout_columnCount, 1); + const cwid = availableWidth / (NumCast(this.Document._layout_columnCount) || this._props.PanelWidth() / NumCast(this.Document._layout_columnWidth, this._props.PanelWidth() / 4)); return Math.min(availableWidth, this.isStackingView ? Number.MAX_VALUE : cwid - this.gridGap); } @@ -241,7 +241,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this._disposers.width = reaction( () => [this._props.PanelWidth() - 2 * this.xMargin, NumCast(this.Document._layout_columnWidth, 250)], ([pw, cw]) => { - if (!this.isStackingView) { + if (!this.isStackingView && Math.round(pw / cw)) { this.layoutDoc._layout_columnCount = Math.round(pw / cw); } } @@ -607,7 +607,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); return ( <div key={(heading?.heading ?? '') + 'head'}> - {this._props.isContentActive() && !this.isStackingView ? this.columnDragger : null} + {this._props.isContentActive() && !this.isStackingView && !this.chromeHidden ? this.columnDragger : null} <div style={{ top: this.yMargin }}> <CollectionMasonryViewFieldRow showHandle={first} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b3d908da4..43addfc29 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -344,7 +344,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection focusOnPoint = (options: FocusViewOptions) => { const { pointFocus, zoomTime, didMove } = options; if (!this.Document.isGroup && pointFocus && !didMove) { - const dfltScale = this.isAnnotationOverlay ? 1 : 0.5; + const dfltScale = this.isAnnotationOverlay ? 1 : 0.25; if (this.layoutDoc[this.scaleFieldKey] !== dfltScale) { this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(pointFocus.X, pointFocus.Y), dfltScale, zoomTime); options.didMove = true; diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss index 845b07c51..4edaf9745 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.scss +++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss @@ -21,11 +21,11 @@ width: 100%; } - .react-grid-item>.react-resizable-handle { + .react-grid-item > .react-resizable-handle { z-index: 4; // doesn't work on prezi otherwise } - .react-grid-item>.react-resizable-handle::after { + .react-grid-item > .react-resizable-handle::after { // grey so it can be seen on the audiobox border-right: 2px solid slategrey; border-bottom: 2px solid slategrey; @@ -41,7 +41,6 @@ position: absolute; height: 3; left: 5; - top: 30; transform-origin: left; transform: rotate(90deg); outline: none; @@ -103,7 +102,7 @@ span::before, span::after { - content: ""; + content: ''; width: 50%; position: relative; display: inline-block; @@ -128,10 +127,8 @@ } } } - } - /* Chrome, Safari, Edge, Opera */ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { @@ -140,6 +137,6 @@ input::-webkit-inner-spin-button { } /* Firefox */ -input[type=number] { +input[type='number'] { -moz-appearance: textfield; -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 6dffb80f1..80bf4bd12 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -9,7 +9,7 @@ import { emptyFunction } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; -import { undoable, undoBatch } from '../../../util/UndoManager'; +import { undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { DocumentView } from '../../nodes/DocumentView'; @@ -22,9 +22,9 @@ export class CollectionGridView extends CollectionSubView() { private _containerRef: React.RefObject<HTMLDivElement> = React.createRef(); private _changeListenerDisposer: Opt<Lambda>; // listens for changes in this.childLayoutPairs private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked + private _dropLocation: object = {}; // sets the drop location for external drops @observable private _rowHeight: Opt<number> = undefined; // temporary store of row height to make change undoable @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll - private dropLocation: object = {}; // sets the drop location for external drops constructor(props: SubCollectionViewProps) { super(props); @@ -48,14 +48,20 @@ export class CollectionGridView extends CollectionSubView() { } @computed get colWidthPlusGap() { - return (this._props.PanelWidth() - this.margin) / this.numCols; + return (this._props.PanelWidth() - 2 * this.xMargin - this.gridGap) / this.numCols; } @computed get rowHeightPlusGap() { - return this.rowHeight + this.margin; + return this.rowHeight + this.gridGap; } - @computed get margin() { - return NumCast(this.Document.margin, 10); + @computed get xMargin() { + return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); + } + @computed get yMargin() { + return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); + } + @computed get gridGap() { + return NumCast(this.Document._gridGap, 10); } // sets the margin between grid nodes @computed get flexGrid() { @@ -77,10 +83,10 @@ export class CollectionGridView extends CollectionSubView() { pairs.forEach((pair, i) => { const existing = oldLayouts.find(l => l.i === pair.layout[Id]); if (existing) newLayouts.push(existing); - else if (Object.keys(this.dropLocation).length) { + else if (Object.keys(this._dropLocation).length) { // external drop - this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid)); - this.dropLocation = {}; + this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this._dropLocation as { x: number; y: number }, !this.flexGrid)); + this._dropLocation = {}; } else { // internal drop this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid)); @@ -115,30 +121,29 @@ export class CollectionGridView extends CollectionSubView() { * @returns the default location of the grid node (i.e. when the grid is static) * @param index */ - unflexedPosition(index: number): Omit<Layout, 'i'> { - return { - x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW, - y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH, - w: this.defaultW, - h: this.defaultH, - static: true, - }; - } + unflexedPosition = (index: number): Omit<Layout, 'i'> => ({ + x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW, + y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH, + w: this.defaultW, + h: this.defaultH, + static: true, + }); /** * Maps the x- and y- coordinates of the event to a grid cell. */ - screenToCell(sx: number, sy: number) { - const pt = this.ScreenToLocalBoxXf().transformPoint(sx, sy); - const x = Math.floor(pt[0] / this.colWidthPlusGap); - const y = Math.floor((pt[1] + this._scroll) / this.rowHeight); + screenToCell = (sx: number, sy: number) => { + const [ptx, pty] = this.ScreenToLocalBoxXf().transformPoint(sx, sy); + const x = Math.floor((ptx + this.xMargin) / this.colWidthPlusGap); + const y = Math.floor((pty + this.yMargin + this._scroll) / this.rowHeight); return { x, y }; - } + }; /** * Creates a layout object for a grid item */ - makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static }); + makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => + ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static }); // prettier-ignore /** * Adds a layout to the list of layouts. @@ -152,9 +157,9 @@ export class CollectionGridView extends CollectionSubView() { /** * @returns the transform that will correctly place the document decorations box. */ - private lookupIndividualTransform = (layout: Layout) => { + lookupIndividualTransform = (layout: Layout) => { const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i)); - const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll }; + const pos = { x: xypos.x * this.colWidthPlusGap + this.gridGap + this.xMargin, y: xypos.y * this.rowHeightPlusGap + this.gridGap - this._scroll + this.yMargin }; return this.ScreenToLocalBoxXf().translate(-pos.x, -pos.y); }; @@ -169,9 +174,9 @@ export class CollectionGridView extends CollectionSubView() { /** * Stores the layout list on the Document as JSON */ - setLayoutList(layouts: Layout[]) { + setLayoutList = (layouts: Layout[]) => { this.Document.gridLayoutString = JSON.stringify(layouts); - } + }; isContentActive = () => this._props.isSelected() || this._props.isContentActive(); isChildContentActive = () => (this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined); @@ -183,29 +188,27 @@ export class CollectionGridView extends CollectionSubView() { * @param height * @returns the `ContentFittingDocumentView` of the node */ - getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { - return ( - <DocumentView - {...this._props} - Document={layout} - TemplateDataDocument={layout.resolvedDataDoc as Doc} - NativeWidth={returnZero} - NativeHeight={returnZero} - fitWidth={this._props.childLayoutFitWidth} - containerViewPath={this.childContainerViewPath} - renderDepth={this._props.renderDepth + 1} - isContentActive={this.isChildContentActive} - PanelWidth={width} - PanelHeight={height} - ScreenToLocalTransform={dxf} - setContentViewBox={emptyFunction} - whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - onClickScript={this.onChildClickHandler} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} - showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} - /> - ); - } + getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => ( + <DocumentView + {...this._props} + Document={layout} + TemplateDataDocument={layout.resolvedDataDoc as Doc} + NativeWidth={returnZero} + NativeHeight={returnZero} + fitWidth={this._props.childLayoutFitWidth} + containerViewPath={this.childContainerViewPath} + renderDepth={this._props.renderDepth + 1} + isContentActive={this.isChildContentActive} + PanelWidth={width} + PanelHeight={height} + ScreenToLocalTransform={dxf} + setContentViewBox={emptyFunction} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + onClickScript={this.onChildClickHandler} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} + showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} + /> + ); /** * Saves the layouts received from the Grid to the Document. @@ -238,15 +241,14 @@ export class CollectionGridView extends CollectionSubView() { * @returns a list of `ContentFittingDocumentView`s inside wrapper divs. * The key of the wrapper div must be the same as the `i` value of the corresponding layout. */ - @computed - private get contents(): JSX.Element[] { + @computed get contents(): JSX.Element[] { const collector: JSX.Element[] = []; if (this.renderedLayoutList.length === this.childLayoutPairs.length) { this.renderedLayoutList.forEach(l => { const child = this.childLayoutPairs.find(c => c.layout[Id] === l.i); const dxf = () => this.lookupIndividualTransform(l); - const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.margin; - const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.margin; + const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.gridGap; + const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.gridGap; child && collector.push( <div key={child.layout[Id]} className={'document-wrapper' + (this.flexGrid && this._props.isSelected() ? '' : ' static')}> @@ -295,7 +297,7 @@ export class CollectionGridView extends CollectionSubView() { * Handles external drop of images/PDFs etc from outside Dash. */ onExternalDrop = async (e: React.DragEvent): Promise<void> => { - this.dropLocation = this.screenToCell(e.clientX, e.clientY); + this._dropLocation = this.screenToCell(e.clientX, e.clientY); super.onExternalDrop(e, {}); }; @@ -316,12 +318,13 @@ export class CollectionGridView extends CollectionSubView() { this, e, returnFalse, - action(() => { - undoable(() => { + undoable( + action(() => { this.Document.gridRowHeight = this._rowHeight; - }, 'changing row height')(); - this._rowHeight = undefined; - }), + this._rowHeight = undefined; + }), + 'changing row height' + ), emptyFunction, false, false @@ -391,14 +394,14 @@ export class CollectionGridView extends CollectionSubView() { <div className="collectionGridView-gridContainer" ref={this._containerRef} - style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, 'white') }} + style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, 'white'), padding: `${this.yMargin} ${this.xMargin}` }} onWheel={e => e.stopPropagation()} onScroll={action(e => { if (!this._props.isSelected()) e.currentTarget.scrollTop = this._scroll; else this._scroll = e.currentTarget.scrollTop; })}> <Grid - width={this._props.PanelWidth()} + width={this._props.PanelWidth() - 2 * this.xMargin} nodeList={this.contents.length ? this.contents : null} layout={this.contents.length ? this.renderedLayoutList : undefined} childrenDraggable={!!this._props.isSelected()} @@ -408,15 +411,15 @@ export class CollectionGridView extends CollectionSubView() { transformScale={this.ScreenToLocalBoxXf().Scale} compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left preventCollision={BoolCast(this.Document.gridPreventCollision)} // determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them - margin={this.margin} + margin={this.gridGap} /> <input className="rowHeightSlider" type="range" - style={{ width: this._props.PanelHeight() - 30 }} + style={{ width: this._props.PanelHeight() - 2 * this.yMargin }} min={1} value={this.rowHeight} - max={this._props.PanelHeight() - 30} + max={this._props.PanelHeight() - 2 * this.yMargin} onPointerDown={this.onSliderDown} onChange={this.onSliderChange} /> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 76d6ed80b..ce1e9280a 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -180,7 +180,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF const timecode = Math.round(time); Object.keys(vals).forEach(val => { const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice(); - findexed[timecode] = vals[val] || 0; + findexed[timecode] = vals[val] as unknown as number; d[`${val}_indexed`] = new List<number>(findexed); }); } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 294af4d96..dd5fd0d0c 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -291,6 +291,7 @@ justify-items: center; background-color: rgb(223, 223, 223); transform-origin: top left; + background: transparent; .documentView-editorView-resizer { height: 5px; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5f5dd1210..595abc7f8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -279,16 +279,17 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined }; onBrowseClick = (e: React.MouseEvent) => { - const browseTransitionTime = 500; + //const browseTransitionTime = 500; DocumentView.DeselectAll(); DocumentView.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { - const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime }; + // const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime }; if (!focused && this._docView) { - this._docView - .docViewPath() - .reverse() - .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options)); - Doc.linkFollowHighlight(this.Document, false); + DocumentView.showDocument(this.Document, { zoomScale: 0.3, willZoomCentered: true }); + // this._docView + // .docViewPath() + // .reverse() + // .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options)); + // Doc.linkFollowHighlight(this.Document, false); } }); e.stopPropagation(); @@ -797,6 +798,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document <div className="documentView-editorView" style={{ + background: SnappingManager.userVariantColor, width: `${100 / this.uiBtnScaling}%`, // transform: `scale(${this.uiBtnScaling})`, }} diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 671621bbe..3d6942e6f 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -40,6 +40,7 @@ max-height: 100%; pointer-events: inherit; background: transparent; + z-index: 0; // z-index: -10000; // bcz: not sure why this was here. it broke dropping images on the image box alternate bullseye icon. img { @@ -103,6 +104,10 @@ margin: 0 auto; display: flex; height: 100%; + img { + object-fit: contain; + height: 100%; + } .imageBox-fadeBlocker, .imageBox-fadeBlocker-hover { @@ -195,8 +200,9 @@ flex-direction: row; gap: 5px; width: 100%; + padding: 0 10; .imageBox-aiView-regenerate-createBtn { - max-width: 10%; + max-width: 20%; .button-container { width: 100% !important; justify-content: left !important; @@ -207,7 +213,7 @@ .imageBox-aiView-firefly { overflow: hidden; text-overflow: ellipsis; - max-width: 10%; + max-width: 15%; width: 100%; } .imageBox-aiView-regenerate-send { @@ -218,7 +224,7 @@ text-align: center; align-items: center; display: flex; - max-width: 25%; + max-width: 90%; width: 100%; .imageBox-aiView-similarity { max-width: 65; @@ -236,5 +242,6 @@ text-overflow: ellipsis; max-width: 65%; width: 100%; + color: black; } } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 279317f49..017ef7191 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -436,6 +436,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined); + @computed get usingAlternate() { + const usePath = StrCast(this.Document[this.fieldKey + '_usePath']); + return 'alternate' === usePath || ('alternate:hover' === usePath && this._isHovering) || (':hover' === usePath && !this._isHovering); + } + @computed get nativeSize() { TraceMobx(); if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 }; @@ -471,10 +476,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}> <em>alternate, </em> </span> - and show <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}> <em> alternate on hover</em> </span> + and show + <span style={{ color: usePath === ':hover' ? 'black' : undefined }}> + <em> primary on hover</em> + </span> </div> }> <div @@ -482,7 +490,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ref={this._overlayIconRef} onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { - this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; + this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : usePath === 'alternate:hover' ? ':hover' : undefined; }) } style={{ @@ -527,7 +535,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { .filter(url => url) .map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; - return paths.length ? paths : [defaultUrl.href]; + return paths.length ? paths.reverse() : [defaultUrl.href]; } @computed get content() { @@ -552,7 +560,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { transformOrigin = 'right top'; transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`; } - const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`]; return ( <div @@ -571,7 +578,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ref={action((r: HTMLImageElement | null) => (this.imageRef = r))} key="paths" src={srcpath} - style={{ transform, transformOrigin, objectFit: 'fill', height: '100%' }} + style={{ transform, transformOrigin }} onError={action(e => { this._error = e.toString(); })} @@ -579,7 +586,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { width={nativeWidth} /> {fadepath === srcpath ? null : ( - <div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}> + <div className={`imageBox-fadeBlocker${this.usingAlternate ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}> <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> </div> )} @@ -619,37 +626,24 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return ( <div className="imageBox-aiView"> <div className="imageBox-aiView-regenerate"> - <span className="imageBox-aiView-firefly">Firefly:</span> + <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}> + Firefly: + </span> <input + style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} className="imageBox-aiView-input" aria-label="Edit instructions input" type="text" - value={this._regenInput} + value={this._regenInput || StrCast(this.Document.title)} onChange={action(e => this._canInteract && (this._regenInput = e.target.value))} placeholder={this._regenInput || StrCast(this.Document.title)} /> - <div className="imageBox-aiView-strength"> - <span className="imageBox-aiView-similarity">Similarity</span> - <Slider - className="imageBox-aiView-slider" - sx={{ - '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, - '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor }, - '& .MuiSlider-thumb': { color: SettingsManager.userVariantColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, - }} - min={0} - max={100} - step={1} - size="small" - value={this._fireflyRefStrength} - onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))} - valueLabelDisplay="auto" - /> - </div> <div className="imageBox-aiView-regenerate-createBtn"> <Button text="Create" - type={Type.SEC} + type={Type.TERT} + color={SnappingManager.userColor} + background={SnappingManager.userBackgroundColor} // style={{ alignSelf: 'flex-end' }} icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} iconPlacement="right" @@ -680,61 +674,24 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { /> </div> </div> - <div className="imageBox-aiView-options"> - <span className="imageBox-aiView-subtitle"> More: </span> - <Button - type={Type.TERT} - text="Get Text" - icon={<FontAwesomeIcon icon="font" />} - color={SettingsManager.userBackgroundColor} - iconPlacement="right" - onClick={() => { - Networking.PostToServer('/queryFireflyImageText', { - file: (file => { - const ext = extname(file); - return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href), - }).then(text => alert(text)); - }} - /> - <Button - type={Type.TERT} - text="Generative Fill" - icon={<FontAwesomeIcon icon="fill" />} - color={SettingsManager.userBackgroundColor} - iconPlacement="right" - onClick={action(() => { - ImageEditorData.Open = true; - ImageEditorData.Source = (field && this.choosePath(field.url)) || ''; - ImageEditorData.AddDoc = this._props.addDocument; - ImageEditorData.RootDoc = this.Document; - })} - /> - <Button - type={Type.TERT} - text="Expand" - icon={<FontAwesomeIcon icon="expand" />} - color={SettingsManager.userBackgroundColor} - iconPlacement="right" - onClick={() => { - Networking.PostToServer('/expandImage', { - prompt: 'sunny skies', - file: (file => { - const ext = extname(file); - return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href), - }).then(res => { - const info = res as Upload.ImageInformation; - const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title }); - DocUtils.assignImageInfo(info, img); - const genratedDocs = this.Document.generatedDocs - ? DocCast(this.Document.generatedDocs) - : Docs.Create.MasonryDocument([], { _width: 400, _height: 400, x: NumCast(this.Document.x) + NumCast(this.Document.width), y: NumCast(this.Document.y) }); - Doc.AddDocToList(genratedDocs, undefined, img); - this.Document[DocData].generatedDocs = genratedDocs; - if (!DocumentView.getFirstDocumentView(genratedDocs)) this._props.addDocTab(genratedDocs, OpenWhere.addRight); - }); + <div className="imageBox-aiView-strength"> + <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}> + Similarity + </span> + <Slider + className="imageBox-aiView-slider" + sx={{ + '& .MuiSlider-track': { color: SettingsManager.userColor }, + '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor }, + '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, }} + min={0} + max={100} + step={1} + size="small" + value={this._fireflyRefStrength} + onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))} + valueLabelDisplay="auto" /> </div> </div> @@ -782,11 +739,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return { width, height }; }; savedAnnotations = () => this._savedAnnotations; - render() { TraceMobx(); const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad; + const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); + const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document; return ( <div className="imageBox" @@ -811,6 +769,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <CollectionFreeFormView ref={this._ffref} {...this._props} + Document={doc} setContentViewBox={emptyFunction} NativeWidth={returnZero} NativeHeight={returnZero} diff --git a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts b/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts index ef4bbbc47..754d230c8 100644 --- a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts +++ b/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts @@ -13,63 +13,63 @@ const standardOptions = ['title', 'backgroundColor']; * Description of document options and data field for each type. */ const documentTypesInfo: { [key in supportedDocTypes]: { options: string[]; dataDescription: string } } = { - [supportedDocumentTypes.flashcard]: { + [supportedDocTypes.flashcard]: { options: [...standardOptions, 'fontColor', 'text_align'], dataDescription: 'an array of two strings. the first string contains a question, and the second string contains an answer', }, - [supportedDocumentTypes.text]: { + [supportedDocTypes.text]: { options: [...standardOptions, 'fontColor', 'text_align'], dataDescription: 'The text content of the document.', }, - [supportedDocumentTypes.html]: { + [supportedDocTypes.html]: { options: [], dataDescription: 'The HTML-formatted text content of the document.', }, - [supportedDocumentTypes.equation]: { + [supportedDocTypes.equation]: { options: [...standardOptions, 'fontColor'], dataDescription: 'The equation content as a string.', }, - [supportedDocumentTypes.functionplot]: { + [supportedDocTypes.functionplot]: { options: [...standardOptions, 'function_definition'], dataDescription: 'The function definition(s) for plotting. Provide as a string or array of function definitions.', }, - [supportedDocumentTypes.dataviz]: { + [supportedDocTypes.dataviz]: { options: [...standardOptions, 'chartType'], dataDescription: 'A string of comma-separated values representing the CSV data.', }, - [supportedDocumentTypes.notetaking]: { + [supportedDocTypes.notetaking]: { options: standardOptions, dataDescription: 'The initial content or structure for note-taking.', }, - [supportedDocumentTypes.rtf]: { + [supportedDocTypes.rtf]: { options: standardOptions, dataDescription: 'The rich text content in RTF format.', }, - [supportedDocumentTypes.image]: { + [supportedDocTypes.image]: { options: standardOptions, dataDescription: 'The image content as an image file URL.', }, - [supportedDocumentTypes.pdf]: { + [supportedDocTypes.pdf]: { options: standardOptions, dataDescription: 'the pdf content as a PDF file url.', }, - [supportedDocumentTypes.audio]: { + [supportedDocTypes.audio]: { options: standardOptions, dataDescription: 'The audio content as a file url.', }, - [supportedDocumentTypes.video]: { + [supportedDocTypes.video]: { options: standardOptions, dataDescription: 'The video content as a file url.', }, - [supportedDocumentTypes.message]: { + [supportedDocTypes.message]: { options: standardOptions, dataDescription: 'The message content of the document.', }, - [supportedDocumentTypes.diagram]: { + [supportedDocTypes.diagram]: { options: ['title', 'backgroundColor'], dataDescription: 'diagram content as a text string in Mermaid format.', }, - [supportedDocumentTypes.script]: { + [supportedDocTypes.script]: { options: ['title', 'backgroundColor'], dataDescription: 'The compilable JavaScript code. Use this for creating scripts.', }, diff --git a/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts b/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts index e92d87dfd..37907fd4f 100644 --- a/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts +++ b/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts @@ -5,6 +5,7 @@ import { ParametersType, ToolInfo } from '../types/tool_types'; import { Observation } from '../types/types'; import { BaseTool } from './BaseTool'; import { Upload } from '../../../../../server/SharedMediaTypes'; +import { List } from '../../../../../fields/List'; const imageCreationToolParams = [ { @@ -41,7 +42,7 @@ export class ImageCreationTool extends BaseTool<ImageCreationToolParamsType> { image_prompt, })) as { result: Upload.FileInformation & Upload.InspectionResults; url: string }; console.log('Image generation result:', result); - this._createImage(result, { text: RTFCast(image_prompt) }); + this._createImage(result, { text: RTFCast(image_prompt), ai: 'dall-e-3', tags: new List<string>(['@ai']) }); return url ? [ { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index da2131940..c2a2caecf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1283,13 +1283,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and layout_autoHeight is on () => ({ border: this._props.PanelHeight(), sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), - ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => { + ({ border, sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => { const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); if ( (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && // layoutAutoHeight && newHeight && - newHeight !== this.layoutDoc.height && + (newHeight !== this.layoutDoc.height || border < NumCast(this.layoutDoc.height)) && !this._props.dontRegisterView ) { this._props.setHeight?.(newHeight); @@ -1669,7 +1669,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB for (let target: HTMLElement | Element | null = clickTarget as HTMLElement; target instanceof HTMLElement && !target.dataset?.targethrefs; target = target.parentElement); while (clickTarget instanceof HTMLElement && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement; const dataset = clickTarget instanceof HTMLElement ? clickTarget?.dataset : undefined; - FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true'); + + if (dataset?.targethrefs) + window + .open( + dataset?.targethrefs + ?.trim() + .split(' ') + .filter(h => h) + .lastElement(), + '_blank' + ) + ?.focus(); + else FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true'); } }; @action diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 28371594e..9aa8fe649 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import './AnchorMenu.scss'; import { GPTPopup } from './GPTPopup/GPTPopup'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -241,6 +242,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { color={SettingsManager.userColor} /> )} + {this._selectedText && RichTextMenu.Instance?.createLinkButton()} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( <IconButton tooltip="Click to Record Annotation" // diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 79f5121ed..4dc45e6a0 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -30,6 +30,7 @@ import { Upload } from '../../../../server/SharedMediaTypes'; import { OpenWhere } from '../../nodes/OpenWhere'; import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; import { ImageField } from '../../../../fields/URLField'; +import { List } from '../../../../fields/List'; export enum GPTPopupMode { SUMMARY, // summary of seleted document text @@ -352,6 +353,8 @@ export class GPTPopup extends ObservableReactComponent<object> { y: NumCast(textAnchor.y), _height: 200, _width: 200, + ai: 'dall-e', + tags: new List<string>(['@ai']), data_nativeWidth: 1024, data_nativeHeight: 1024, }); diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index 0a30b14b8..c672bc718 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -1,6 +1,7 @@ import { imageUrlToBase64 } from '../../../ClientUtils'; import { Doc, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; +import { List } from '../../../fields/List'; import { DocCast, ImageCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { Upload } from '../../../server/SharedMediaTypes'; @@ -53,7 +54,10 @@ export class DrawingFillHandler { undefined, Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', + tags: new List<string>(['@ai']), title: newPrompt, + _data_usePath: 'alternate:hover', + data_alternates: new List<Doc>([drawing]), ai_firefly_prompt: newPrompt, _width: 500, data_nativeWidth: info.nativeWidth, diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index ca308015d..1cceabed3 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -28,6 +28,7 @@ import { FireflyDimensionsMap, FireflyImageData, FireflyImageDimensions } from ' import './SmartDrawHandler.scss'; import { Upload } from '../../../server/SharedMediaTypes'; import { PointData } from '../../../pen-gestures/GestureTypes'; +import { List } from '../../../fields/List'; export interface DrawingOptions { text?: string; @@ -293,15 +294,17 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { return undefined; } const newseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)?.[1]; - const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { + return Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { title: input, nativeWidth: dims.width, nativeHeight: dims.height, + tags: new List<string>(['@ai']), + _width: Math.min(400, dims.width), + _height: (Math.min(400, dims.width) * dims.height) / dims.width, ai: 'firefly', ai_firefly_seed: +(newseed ?? 0), ai_firefly_prompt: input, }); - return imgDoc; }) .catch(e => { alert('create image failed: ' + e.toString()); @@ -568,6 +571,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { color={SettingsManager.userColor} /> <input + style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} aria-label="Smart Draw Input" className="smartdraw-input" type="text" |