aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/ContextMenu.scss12
-rw-r--r--src/client/views/DashboardView.scss8
-rw-r--r--src/client/views/DocumentDecorations.scss32
-rw-r--r--src/client/views/DocumentDecorations.tsx16
-rw-r--r--src/client/views/EditableView.scss9
-rw-r--r--src/client/views/FilterPanel.scss54
-rw-r--r--src/client/views/GestureOverlay.scss2
-rw-r--r--src/client/views/InkStroke.scss4
-rw-r--r--src/client/views/InkTangentHandles.tsx2
-rw-r--r--src/client/views/LightboxView.scss48
-rw-r--r--src/client/views/Main.scss6
-rw-r--r--src/client/views/MainView.scss40
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/MetadataEntryMenu.scss20
-rw-r--r--src/client/views/OverlayView.scss12
-rw-r--r--src/client/views/PreviewCursor.scss4
-rw-r--r--src/client/views/PropertiesSection.tsx1
-rw-r--r--src/client/views/PropertiesView.scss50
-rw-r--r--src/client/views/SidebarAnnos.scss12
-rw-r--r--src/client/views/StyleProvider.scss22
-rw-r--r--src/client/views/StyleProviderQuiz.scss8
-rw-r--r--src/client/views/ViewBoxInterface.ts3
-rw-r--r--src/client/views/animationtimeline/Timeline.scss4
-rw-r--r--src/client/views/animationtimeline/TimelineMenu.tsx4
-rw-r--r--src/client/views/animationtimeline/TimelineOverview.tsx5
-rw-r--r--src/client/views/collections/CollectionCardDeckView.scss4
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.scss12
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss16
-rw-r--r--src/client/views/collections/CollectionDockingView.scss132
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx4
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss30
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss16
-rw-r--r--src/client/views/collections/CollectionStackingView.scss34
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx8
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx2
-rw-r--r--src/client/views/collections/CollectionTimeView.scss13
-rw-r--r--src/client/views/collections/CollectionTreeView.scss6
-rw-r--r--src/client/views/collections/CollectionView.scss8
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.scss8
-rw-r--r--src/client/views/collections/KeyRestrictionRow.tsx72
-rw-r--r--src/client/views/collections/TabDocView.scss4
-rw-r--r--src/client/views/collections/TreeView.scss4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss32
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss14
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss8
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx121
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss6
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss8
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss8
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss4
-rw-r--r--src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx1
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx1
-rw-r--r--src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx3
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss10
-rw-r--r--src/client/views/global/globalCssVariables.module.scss2
-rw-r--r--src/client/views/linking/LinkMenuItem.scss4
-rw-r--r--src/client/views/linking/LinkPopup.scss6
-rw-r--r--src/client/views/linking/LinkPopup.tsx1
-rw-r--r--src/client/views/newlightbox/NewLightboxView.scss4
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.scss2
-rw-r--r--src/client/views/nodes/AudioBox.scss4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.scss4
-rw-r--r--src/client/views/nodes/ComparisonBox.scss18
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss10
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss26
-rw-r--r--src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss8
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss4
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss4
-rw-r--r--src/client/views/nodes/DocumentView.scss37
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBadge.scss2
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.scss20
-rw-r--r--src/client/views/nodes/IconTagBox.scss2
-rw-r--r--src/client/views/nodes/ImageBox.scss26
-rw-r--r--src/client/views/nodes/ImageBox.tsx61
-rw-r--r--src/client/views/nodes/KeyValueBox.scss10
-rw-r--r--src/client/views/nodes/KeyValuePair.scss2
-rw-r--r--src/client/views/nodes/LabelBox.scss12
-rw-r--r--src/client/views/nodes/LinkDocPreview.scss2
-rw-r--r--src/client/views/nodes/LoadingBox.scss4
-rw-r--r--src/client/views/nodes/MapBox/MapAnchorMenu.scss37
-rw-r--r--src/client/views/nodes/MapBox/MapBox.scss10
-rw-r--r--src/client/views/nodes/PDFBox.scss34
-rw-r--r--src/client/views/nodes/PDFBox.tsx33
-rw-r--r--src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss4
-rw-r--r--src/client/views/nodes/RecordingBox/ProgressBar.scss141
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.scss12
-rw-r--r--src/client/views/nodes/ScreenshotBox.scss8
-rw-r--r--src/client/views/nodes/ScriptingBox.scss6
-rw-r--r--src/client/views/nodes/VideoBox.scss10
-rw-r--r--src/client/views/nodes/VideoBox.tsx47
-rw-r--r--src/client/views/nodes/WebBox.scss44
-rw-r--r--src/client/views/nodes/audio/AudioWaveform.scss10
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss14
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss12
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss2
-rw-r--r--src/client/views/nodes/formattedText/EquationEditor.scss10
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss54
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx19
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss16
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss4
-rw-r--r--src/client/views/nodes/formattedText/TooltipTextMenu.scss8
-rw-r--r--src/client/views/nodes/imageEditor/ImageEditor.scss10
-rw-r--r--src/client/views/nodes/scrapbook/AIPresetGenerator.ts31
-rw-r--r--src/client/views/nodes/scrapbook/EmbeddedDocView.tsx52
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.scss66
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.tsx302
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookContent.tsx23
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPreset.tsx94
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts36
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookSlot.scss85
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookSlot.tsx28
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts25
-rw-r--r--src/client/views/nodes/trails/PresBox.scss104
-rw-r--r--src/client/views/nodes/trails/PresSlideBox.scss10
-rw-r--r--src/client/views/pdf/AnchorMenu.scss14
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss10
-rw-r--r--src/client/views/pdf/PDFViewer.scss10
-rw-r--r--src/client/views/search/FaceRecognitionHandler.tsx13
-rw-r--r--src/client/views/topbar/TopBar.scss2
125 files changed, 1490 insertions, 1222 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index d22c4d096..337c976cb 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -68,8 +68,8 @@
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 0;
filter: opacity(0);
}
@@ -187,10 +187,10 @@
display: flex;
.close-menu {
- margin-top: 0;
- margin-bottom: 0;
- margin-right: 0;
- padding: 0;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-right: 0px;
+ padding: 0px;
margin-left: auto;
z-index: 999999999;
width: 20px;
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss
index daa711bc4..d74441b9c 100644
--- a/src/client/views/DashboardView.scss
+++ b/src/client/views/DashboardView.scss
@@ -79,8 +79,8 @@ $dashboard-container-width: 250px;
position: absolute;
width: 100%;
height: 100%;
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
z-index: -1;
}
}
@@ -137,8 +137,8 @@ $dashboard-container-width: 250px;
position: absolute;
width: 100%;
height: 100%;
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
z-index: -1;
}
}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 55eb0b127..25a806abd 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -11,8 +11,8 @@ $resizeHandler: 8px;
// Rotation handler
.documentDecorations-rotation {
border-radius: 100%;
- height: 30;
- width: 30;
+ height: 30px;
+ width: 30px;
right: -40;
bottom: -20;
//top: calc(50% - 15px);
@@ -45,15 +45,15 @@ $resizeHandler: 8px;
width: 100%;
pointer-events: all;
border-radius: 50%;
- top: 30; // offset by height of documentButtonBar so that items can be clicked without overlap interference
+ top: 30px; // offset by height of documentButtonBar so that items can be clicked without overlap interference
color: black;
}
}
.documentDecorations-container {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
transform-origin: 50% calc(50% + 10px);
display: grid;
grid-template-rows: $headerHeight $resizeHandler 1fr $resizeHandler;
@@ -121,7 +121,7 @@ $resizeHandler: 8px;
}
> svg {
- margin: 0;
+ margin: 0px;
}
}
@@ -150,7 +150,7 @@ $resizeHandler: 8px;
}
> svg {
- margin: 0;
+ margin: 0px;
}
}
&:hover {
@@ -185,7 +185,7 @@ $resizeHandler: 8px;
}
> svg {
- margin: 0;
+ margin: 0px;
}
}
@@ -246,7 +246,7 @@ $resizeHandler: 8px;
display: inline;
position: relative;
top: -2.5;
- left: 35;
+ left: 35px;
zoom: 0.7;
}
@@ -360,8 +360,8 @@ $resizeHandler: 8px;
left: 7px;
top: 7px;
background: global.$medium-gray;
- height: 10;
- width: 10;
+ height: 10px;
+ width: 10px;
opacity: 0.5;
pointer-events: all;
cursor: nwse-resize;
@@ -371,8 +371,8 @@ $resizeHandler: 8px;
position: relative;
background: black;
color: rgb(145, 144, 144);
- height: 20;
- width: 20;
+ height: 20px;
+ width: 20px;
pointer-events: all;
margin: auto;
display: flex;
@@ -380,8 +380,8 @@ $resizeHandler: 8px;
border-radius: 100%;
cursor: default;
svg {
- width: 10;
- height: 10;
+ width: 10px;
+ height: 10px;
margin: auto;
}
}
@@ -389,7 +389,7 @@ $resizeHandler: 8px;
.documentDecorations-rotationPath {
position: absolute;
width: 100%;
- height: 0;
+ height: 0px;
transform: translate(0px, -25%);
padding-bottom: 100%;
border-radius: 100%;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 69c2467a3..7a9f6c514 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,7 +1,7 @@
+import { IconButton } from '@dash/components';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { IconButton } from '@dash/components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -27,15 +27,13 @@ import './DocumentDecorations.scss';
import { InkStrokeProperties } from './InkStrokeProperties';
import { InkingStroke } from './InkingStroke';
import { ObservableReactComponent } from './ObservableReactComponent';
+import { TagsView } from './TagsView';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { Colors } from './global/globalEnums';
import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
import { DocumentView } from './nodes/DocumentView';
-import { ImageBox } from './nodes/ImageBox';
import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { TagsView } from './TagsView';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -430,7 +428,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
onPointerDown = (e: React.PointerEvent): void => {
SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
DocumentView.Selected()
- .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox)
+ .filter(dv => e.shiftKey && dv.ComponentView?.isOutpaintable?.())
.forEach(dv => {
dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width);
dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height);
@@ -486,7 +484,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._interactionLock = true;
this._snapPt = thisPt;
- const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : [];
+ const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView?.isOutpaintable?.()) : [];
const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected();
// Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI)
@@ -750,7 +748,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const rotation = DocumentView.Selected().length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
// Radius constants
- const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
+ const useRounding = seldocview.ComponentView?.showBorderRounding?.();
const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null));
const docMax = Math.min(NumCast(seldocview.Document._width) / 2, NumCast(seldocview.Document._height) / 2);
const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
@@ -911,8 +909,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
transform: `rotate(${rotation}deg)`,
width: this.Bounds.r - this.Bounds.x + 'px',
height: this.Bounds.b - this.Bounds.y + 'px',
- left: this.Bounds.x,
- top: this.Bounds.y,
+ left: this.Bounds.r,
+ top: this.Bounds.b,
pointerEvents: 'none',
}}>
{this._isRotating ? null : (
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index fa4542ac4..333939d03 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -6,14 +6,14 @@
overflow-y: auto;
height: 100%;
width: 100%;
- min-width: 20;
+ min-width: 20px;
text-overflow: ellipsis;
- -ms-overflow-style: none;
- scrollbar-width: none;
+ -ms-overflow-style: none;
+ scrollbar-width: none;
}
.editableView-container-editing::-webkit-scrollbar {
- display: none;
+ display: none;
}
.editableView-container-editing-oneLine {
@@ -44,4 +44,3 @@
border: none;
outline: none;
}
-
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index 508b1ee1f..e32db000f 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -1,4 +1,3 @@
-
.filterBox-flyout {
display: block;
text-align: left;
@@ -29,7 +28,7 @@
// .filterBox-bottom {
// // position: fixed;
-// // bottom: 0;
+// // bottom: 0px;
// // width: 100%;
// }
@@ -88,7 +87,7 @@
// padding-bottom: 20px;
// border-bottom: 2px solid black;
// position: fixed;
-// top: 0;
+// top: 0px;
// width: 100%;
// }
@@ -154,8 +153,8 @@
flex-direction: column;
width: 100%;
position: relative;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
z-index: 1;
// background-color: #9f9f9f;
@@ -240,60 +239,52 @@
transition: all 0.3s ease-out;
display: flex;
flex-direction: row;
- padding: 5px;
-
+ padding: 5px;
- &:hover{
+ &:hover {
border-color: #e9e9e9;
- background-color: #6d6c6c
+ background-color: #6d6c6c;
}
- .hotKey-icon, .hotKey-close{
+ .hotKey-icon,
+ .hotKey-close {
background-color: transparent;
border-radius: 10%;
padding: 5px;
-
- &:hover{
+ &:hover {
background-color: #616060;
}
}
- .hotKey-close{
+ .hotKey-close {
right: 30px;
- position: fixed;
+ position: fixed;
padding-top: 10px;
-
-}
+ }
- .hotkey-title{
+ .hotkey-title {
top: 6px;
position: relative;
cursor: text;
-
}
- .hotkey-title-input{
+ .hotkey-title-input {
background-color: transparent;
border: none;
border-color: transparent;
outline: none;
cursor: text;
-
}
}
.hotKeyButtons {
position: relative;
width: 100%;
-
}
.hotKey-icon-button {
-
- background-color: transparent;
-
-
+ background-color: transparent;
}
.icon-panel {
@@ -305,24 +296,19 @@
border-radius: 10%;
background-color: #323232;
- .icon-panel-button{
+ .icon-panel-button {
background-color: #323232;
border-radius: 10%;
-
- &:hover{
- background-color:#7a7878
+ &:hover {
+ background-color: #7a7878;
}
}
-
-
-
}
-
// .sliderBox-outerDiv {
// width: 30%;// width: calc(100% - 14px); // 14px accounts for handles that are at the max value of the slider that would extend outside the box
-// height: 40; // height: 100%;
+// height: 40px; // height: 100%;
// border-radius: inherit;
// display: flex;
// flex-direction: column;
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index bfe2d5c64..0fa3fd973 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -3,7 +3,7 @@
height: 100%;
position: absolute;
touch-action: none;
- top: 0;
+ top: 0px;
.pointerBubbles {
width: 100%;
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index c672824bf..0595283fc 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -8,8 +8,8 @@
svg:not(:root) {
overflow: visible !important;
position: absolute;
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
}
}
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index 577acc4d1..0af46df5d 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -105,7 +105,6 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
return (
<>
{tangentHandles.map((pts, i) => (
- // eslint-disable-next-line react/no-array-index-key
<svg height="10" width="10" key={`hdl${i}`}>
<circle
cx={pts.X}
@@ -135,7 +134,6 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
/>
);
return (
- // eslint-disable-next-line react/no-array-index-key
<svg height="100" width="100" key={`line${i}`}>
{tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
{tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index 3e65843df..7a481d887 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -1,12 +1,12 @@
.lightboxView-navBtn {
margin: auto;
position: absolute;
- right: 19;
- top: 10;
+ right: 19px;
+ top: 10px;
background: transparent;
- border-radius: 8;
+ border-radius: 8px;
opacity: 0.7;
- width: 25;
+ width: 25px;
flex-direction: column;
display: flex;
&:hover {
@@ -16,12 +16,12 @@
.lightboxView-tabBtn {
margin: auto;
position: absolute;
- right: 54;
- top: 10;
+ right: 54px;
+ top: 10px;
background: transparent;
- border-radius: 8;
+ border-radius: 8px;
opacity: 0.7;
- width: 25;
+ width: 25px;
flex-direction: column;
display: flex;
&:hover {
@@ -31,12 +31,12 @@
.lightboxView-paletteBtn {
margin: auto;
position: absolute;
- right: 89;
- top: 10;
+ right: 89px;
+ top: 10px;
background: transparent;
- border-radius: 8;
+ border-radius: 8px;
opacity: 0.7;
- width: 25;
+ width: 25px;
flex-direction: column;
display: flex;
&:hover {
@@ -47,12 +47,12 @@
.lightboxView-penBtn {
margin: auto;
position: absolute;
- right: 124;
- top: 10;
+ right: 124px;
+ top: 10px;
background: transparent;
- border-radius: 8;
+ border-radius: 8px;
opacity: 0.7;
- width: 25;
+ width: 25px;
flex-direction: column;
display: flex;
&:hover {
@@ -62,12 +62,12 @@
.lightboxView-exploreBtn {
margin: auto;
position: absolute;
- right: 159;
- top: 10;
+ right: 159px;
+ top: 10px;
background: transparent;
- border-radius: 8;
+ border-radius: 8px;
opacity: 0.7;
- width: 25;
+ width: 25px;
flex-direction: column;
display: flex;
&:hover {
@@ -76,8 +76,8 @@
}
.lightboxView-frame {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
z-index: 1000;
@@ -91,9 +91,9 @@
margin: auto;
position: relative;
background: transparent;
- border-radius: 8;
+ border-radius: 8px;
opacity: 0.7;
- width: 35;
+ width: 35px;
&:hover {
opacity: 1;
}
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index bea1de435..df4160fc1 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -12,10 +12,10 @@ body {
overflow: hidden;
font-family: global.$sans-serif;
font-size: global.$body-text;
- margin: 0;
+ margin: 0px;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
// div {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index db949285b..d5e6b8998 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -10,7 +10,7 @@ body {
.dash-tooltip {
font-size: 11px;
padding: 2px;
- max-width: 150;
+ max-width: 150px;
line-height: 150%;
}
@@ -49,8 +49,8 @@ body {
.mainView-snapLines {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
pointer-events: none;
@@ -61,8 +61,8 @@ body {
height: 100%;
position: absolute;
pointer-events: all;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 1;
touch-action: none;
}
@@ -86,8 +86,8 @@ body {
.properties-container {
height: 100%;
position: absolute;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
}
.mainView-propertiesDragger-minified,
@@ -98,7 +98,7 @@ body {
width: 17px;
position: absolute;
top: 50%;
- border-radius: 0;
+ border-radius: 0px;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
border-right: unset;
@@ -141,7 +141,7 @@ body {
}
.propertiesView {
- left: 0;
+ left: 0px;
position: absolute;
z-index: 2;
// background-color: linen; //$light-gray;
@@ -165,7 +165,7 @@ body {
}
::-webkit-scrollbar {
- width: 0;
+ width: 0px;
}
}
@@ -173,19 +173,19 @@ body {
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
overflow: hidden;
}
.buttonContainer {
position: absolute;
- bottom: 0;
+ bottom: 0px;
.mainView-settings {
// position: absolute;
- // left: 0;
- // bottom: 0;
+ // left: 0px;
+ // bottom: 0px;
border-radius: 25%;
margin-left: -5px;
background: darkblue;
@@ -198,8 +198,8 @@ body {
.mainView-logout {
position: absolute;
- right: 0;
- bottom: 0;
+ right: 0px;
+ bottom: 0px;
font-size: 8px;
}
@@ -223,12 +223,12 @@ body {
}
.mainView-libraryFlyout-close {
- right: 6;
- top: 5;
+ right: 6px;
+ top: 5px;
position: absolute;
margin-right: 6px;
z-index: 10;
- margin-bottom: 10;
+ margin-bottom: 10px;
}
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 686f8ed88..13b14617c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1039,7 +1039,7 @@ export class MainView extends ObservableReactComponent<object> {
@computed get inkResources() {
return (
- <svg width={0} height={0}>
+ <svg width={0} height={0} style={{ display: 'block' }}>
<defs>
<filter id="inkSelectionHalo">
<feColorMatrix
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index 28de0b7a5..924476a30 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -3,7 +3,7 @@
width: 310px;
flex-direction: column;
- input[type=checkbox] {
+ input[type='checkbox'] {
margin-left: 5px;
}
}
@@ -11,7 +11,7 @@
.metadataEntry-autoSuggester {
width: 80%;
height: 100%;
- margin: 0;
+ margin: 0px;
display: inline-block;
}
@@ -20,13 +20,13 @@
}
.metadataEntry-keys {
- max-height: 80;
- overflow-y: auto;
+ max-height: 80px;
+ overflow-y: auto;
display: flex;
flex-direction: column;
}
.metadataEntry-inputArea {
- display:inline-block;
+ display: inline-block;
flex-direction: row;
}
@@ -53,8 +53,8 @@
}
.react-autosuggest__input--open {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
}
.react-autosuggest__suggestions-container {
@@ -78,8 +78,8 @@
}
.react-autosuggest__suggestions-list {
- margin: 0;
- padding: 0;
+ margin: 0px;
+ padding: 0px;
list-style-type: none;
}
@@ -90,4 +90,4 @@
.react-autosuggest__suggestion--highlighted {
background-color: #ddd;
-} \ No newline at end of file
+}
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 2e8621b5b..44203e38f 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -1,7 +1,7 @@
.overlayView {
position: absolute;
pointer-events: none;
- top: 0;
+ top: 0px;
width: 100vw;
height: 100vh;
z-index: 2002; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear
@@ -14,8 +14,8 @@
overflow: hidden;
display: flex;
flex-direction: column;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
pointer-events: all;
box-shadow: black 5px 5px 5px;
}
@@ -46,7 +46,7 @@
float: right;
height: 20px;
width: 20px;
- padding: 0;
+ padding: 0px;
background-color: inherit;
}
@@ -62,6 +62,6 @@
.overlayView-doc {
z-index: 9002; //so that it appears above chroma
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
index 82488c750..eef120c58 100644
--- a/src/client/views/PreviewCursor.scss
+++ b/src/client/views/PreviewCursor.scss
@@ -2,8 +2,8 @@
color: black;
position: absolute;
transform-origin: left top;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
pointer-events: none;
opacity: 1;
z-index: 1001;
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index 12a46c7a4..9ea9c3a3d 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 280de4893..de3012948 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -5,7 +5,7 @@
}
.propertiesView-presentationTrails-title-icon {
position: absolute;
- right: 4;
+ right: 4px;
}
.propertiesView-palette {
cursor: pointer;
@@ -24,7 +24,7 @@
}
.propertiesView {
height: 100%;
- width: 250;
+ width: 250px;
font-family: 'Roboto';
font-size: 12px;
cursor: auto;
@@ -61,7 +61,7 @@
}
.propertiesView-info {
- margin-top: -5;
+ margin-top: -5px;
float: right;
font-size: 20;
path {
@@ -78,10 +78,10 @@
display: flex;
button {
- width: 15;
- height: 15;
- padding: 0;
- margin-top: -5;
+ width: 15px;
+ height: 15px;
+ padding: 0px;
+ margin-top: -5px;
}
}
@@ -89,8 +89,8 @@
display: flex;
button {
- width: 5;
- height: 5;
+ width: 5px;
+ height: 5px;
}
input {
@@ -171,16 +171,16 @@
display: flex;
button {
- width: 15;
- height: 15;
- padding: 0;
- margin-top: -5;
+ width: 15px;
+ height: 15px;
+ padding: 0px;
+ margin-top: -5px;
}
}
button {
- width: 5;
- height: 5;
+ width: 5px;
+ height: 5px;
}
input {
@@ -301,8 +301,8 @@
padding: 7px;
border-radius: 7px;
margin-right: 32px;
- width: 32;
- height: 32;
+ width: 32px;
+ height: 32px;
padding-top: 9px;
margin-left: 18px;
@@ -318,8 +318,8 @@
padding: 7px;
border-radius: 7px;
margin-right: 32px;
- width: 32;
- height: 32;
+ width: 32px;
+ height: 32px;
padding-top: 9px;
padding-left: 10px;
@@ -334,8 +334,8 @@
background-color: #333333;
padding: 7px;
border-radius: 7px;
- width: 32;
- height: 32;
+ width: 32px;
+ height: 32px;
padding-top: 9px;
padding-left: 10px;
@@ -410,7 +410,7 @@
.color-palette {
width: 160px;
- height: 360;
+ height: 360px;
}
.strokeAndFill {
@@ -465,7 +465,7 @@
.propertiesView-selectedList {
min-width: max-content;
width: 100%;
- max-height: 180;
+ max-height: 180px;
overflow: hidden;
overflow-y: scroll;
border-left: solid 1px darkgrey;
@@ -475,7 +475,7 @@
.selectedList-items {
font-size: 12;
font-weight: 300;
- margin-top: 1;
+ margin-top: 1px;
}
}
}
@@ -500,7 +500,7 @@
.width-range {
margin-right: 1px;
- margin-bottom: 6;
+ margin-bottom: 6px;
}
}
diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss
index abfd04f11..c2b9dcce5 100644
--- a/src/client/views/SidebarAnnos.scss
+++ b/src/client/views/SidebarAnnos.scss
@@ -2,7 +2,7 @@
position: absolute;
width: 100%;
height: 100%;
- right: 0;
+ right: 0px;
.sidebarAnnos-stacking {
width: 100%;
position: relative;
@@ -20,12 +20,12 @@
.sidebarAnnos-filterUser-active {
font-weight: bold;
font-size: 10px;
- padding-left: 5;
- padding-right: 5;
+ padding-left: 5px;
+ padding-right: 5px;
box-shadow: black 1px 1px 3px;
- border-radius: 5;
- margin: 2;
- height: 15;
+ border-radius: 5px;
+ margin: 2px;
+ height: 15px;
background-color: lightgrey;
}
.sidebarAnnos-filterUser,
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 99796f1fb..cbb1fd5d5 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -4,11 +4,11 @@
.styleProvider-lock {
z-index: 2; // has to be above title which is z-index 1
font-size: 10;
- width: 20;
- height: 20;
+ width: 20px;
+ height: 20px;
position: absolute;
- right: -20;
- top: 0;
+ right: -20px;
+ top: 0px;
background: black;
pointer-events: all;
opacity: 0.3;
@@ -20,10 +20,10 @@
cursor: default;
}
.styleProvider-filter {
- right: 20;
+ right: 20px;
.styleProvider-filterShift {
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
position: absolute;
}
.dropdown-container {
@@ -33,10 +33,10 @@
}
.styleProvider-paint-selected,
.styleProvider-paint {
- top: 15;
+ top: 15px;
}
.styleProvider-paint-selected {
- right: -40;
+ right: -40px;
}
.styleProvider-lock:hover,
.styleProvider-filter:hover {
@@ -45,8 +45,8 @@
.styleProvider-treeView-icon,
.styleProvider-treeView-icon-active {
- margin-left: 0;
- margin-right: 0;
+ margin-left: 0px;
+ margin-right: 0px;
}
.styleProvider-treeView-icon {
diff --git a/src/client/views/StyleProviderQuiz.scss b/src/client/views/StyleProviderQuiz.scss
index 84b3f1fef..53ca34c1b 100644
--- a/src/client/views/StyleProviderQuiz.scss
+++ b/src/client/views/StyleProviderQuiz.scss
@@ -13,8 +13,8 @@
.check-icon {
position: absolute;
- right: 40;
- bottom: 10;
+ right: 40px;
+ bottom: 10px;
color: green;
display: inline-block;
font-size: 20px;
@@ -23,8 +23,8 @@
.redo-icon {
position: absolute;
- right: 10;
- bottom: 10;
+ right: 10px;
+ bottom: 10px;
color: black;
display: inline-block;
font-size: 20px;
diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts
index d8dab8e89..514dc4ae8 100644
--- a/src/client/views/ViewBoxInterface.ts
+++ b/src/client/views/ViewBoxInterface.ts
@@ -24,6 +24,9 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React
promoteCollection?: () => void; // moves contents of collection to parent
hasChildDocs?: () => Doc[];
docEditorView?: () => void;
+ autoTag?: () => void; // auto tag the document
+ isOutpaintable?: () => boolean; // can document be resized and outpainted
+ showBorderRounding?: () => boolean; // can document borders be rounded
showSmartDraw?: (x: number, y: number, regenerate?: boolean) => void;
updateIcon?: (usePanelDimensions?: boolean) => Promise<void>; // updates the icon representation of the document
getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
diff --git a/src/client/views/animationtimeline/Timeline.scss b/src/client/views/animationtimeline/Timeline.scss
index e1d3b190c..3a50183d2 100644
--- a/src/client/views/animationtimeline/Timeline.scss
+++ b/src/client/views/animationtimeline/Timeline.scss
@@ -112,8 +112,8 @@ $timelineDark: #77a1aa;
input {
position: absolute;
opacity: 0;
- height: 0;
- width: 0;
+ height: 0px;
+ width: 0px;
}
.round-toggle-slider {
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 0d7873931..79283479c 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -19,7 +17,7 @@ export class TimelineMenu extends React.Component {
@observable private _y = 0;
@observable private _currentMenu: JSX.Element[] = [];
- constructor(props: any) {
+ constructor(props: object) {
super(props);
makeObservable(this);
TimelineMenu.Instance = this;
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 7bf685c9e..fff756980 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/no-unused-prop-types */
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -14,7 +13,7 @@ interface TimelineOverviewProps {
isAuthoring: boolean;
parent: Timeline;
changeCurrentBarX: (pixel: number) => void;
- movePanX: (pixel: number) => any;
+ movePanX: (pixel: number) => void;
time: number;
tickSpacing: number;
tickIncrement: number;
@@ -139,7 +138,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
const percentVisible = this.visibleTime / this.props.time;
const visibleBarWidth = percentVisible * this.activeOverviewWidth;
- const percentScrubberStart = this.currentX / this.props.time;
+ // const percentScrubberStart = this.currentX / this.props.time;
let scrubberStart = (this.props.currentBarX / this.props.totalLength) * this.activeOverviewWidth;
if (scrubberStart > this.activeOverviewWidth) scrubberStart = this.activeOverviewWidth;
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index e6cc398af..981e528cc 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -18,7 +18,7 @@
}
.collectionCardView-flashcardUI {
- top: 0;
+ top: 0px;
position: absolute;
width: 100%;
height: 100%;
@@ -33,7 +33,7 @@
}
.collectionCardView-cardSizeDragger {
position: absolute;
- top: 0;
+ top: 0px;
width: 28px;
height: 28px;
> svg {
diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 13e6b54c2..361d88cb6 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -12,7 +12,7 @@
position: absolute;
top: global.$CAROUSEL3D_TOP * 1%;
height: (global.$CAROUSEL3D_SIDE_SCALE * 100) * 1%;
- align-items: center;
+ //align-items: center;
transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
.collectionCarousel3DView-item,
@@ -67,8 +67,8 @@
.carousel3DView-fwd-scroll-hidden {
position: absolute;
display: flex;
- width: 30;
- height: 30;
+ width: 30px;
+ height: 30px;
align-items: center;
border-radius: 5px;
justify-content: center;
@@ -78,7 +78,7 @@
.carousel3DView-fwd,
.carousel3DView-back {
- top: 0;
+ top: 0px;
background: transparent;
width: calc((1 - #{global.$CAROUSEL3D_CENTER_SCALE} * 0.33) / 2 * 100%);
height: 100%;
@@ -94,13 +94,13 @@
.carousel3DView-fwd,
.carousel3DView-fwd-scroll,
.carousel3DView-fwd-scroll-hidden {
- right: 0;
+ right: 0px;
}
.carousel3DView-back,
.carousel3DView-back-scroll,
.carousel3DView-back-scroll-hidden {
- left: 0;
+ left: 0px;
}
.carousel3DView-fwd-scroll-hidden,
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index 962b590c8..4c999b6dd 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -6,10 +6,10 @@
transform-origin: top left;
.collectionCarouselView-caption {
- height: 50;
+ height: 50px;
display: inline-block;
width: 100%;
- bottom: 0;
+ bottom: 0px;
position: absolute;
}
.collectionCarouselView-image {
@@ -18,8 +18,8 @@
width: 100%;
user-select: none;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
}
.collectionCarouselView-recentlyMissed {
@@ -34,8 +34,8 @@
.carouselView-fwd {
position: absolute;
display: flex;
- width: 30;
- height: 30;
+ width: 30px;
+ height: 30px;
align-items: center;
border-radius: 5px;
justify-content: center;
@@ -47,12 +47,12 @@
}
.carouselView-fwd {
top: calc(50% - 15px);
- right: 0;
+ right: 0px;
transform-origin: right top;
}
.carouselView-back {
top: calc(50% - 15px);
- left: 0;
+ left: 0px;
transform-origin: top left;
}
.carouselView-back:hover,
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 7c19d39da..de214e2ef 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -17,8 +17,8 @@
}
.lm_maximised {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 40;
}
.lm_maximise_placeholder {
@@ -62,8 +62,8 @@
text-align: center;
}
.lm_header ul {
- margin: 0;
- padding: 0;
+ margin: 0px;
+ padding: 0px;
list-style-type: none;
}
.lm_header .lm_tab {
@@ -81,11 +81,11 @@
position: absolute;
}
.lm_header .lm_tab i.lm_left {
- top: 0;
+ top: 0px;
left: -2px;
}
.lm_header .lm_tab i.lm_right {
- top: 0;
+ top: 0px;
right: -2px;
}
.lm_header .lm_tab .lm_title {
@@ -97,8 +97,8 @@
width: 14px;
height: 14px;
position: absolute;
- top: 0;
- right: 0;
+ top: 0px;
+ right: 0px;
text-align: center;
}
.lm_stack.lm_left .lm_header,
@@ -118,14 +118,14 @@
.lm_stack.lm_left .lm_header .lm_tabs,
.lm_stack.lm_right .lm_header .lm_tabs {
transform-origin: left top;
- top: 0;
+ top: 0px;
width: 1000px;
}
.lm_dragProxy.lm_left .lm_header .lm_controls,
.lm_dragProxy.lm_right .lm_header .lm_controls,
.lm_stack.lm_left .lm_header .lm_controls,
.lm_stack.lm_right .lm_header .lm_controls {
- bottom: 0;
+ bottom: 0px;
}
.lm_dragProxy.lm_left .lm_items,
.lm_dragProxy.lm_right .lm_items,
@@ -136,7 +136,7 @@
.lm_dragProxy.lm_left .lm_header .lm_tabs,
.lm_stack.lm_left .lm_header .lm_tabs {
transform: rotate(-90deg) scaleX(-1);
- left: 0;
+ left: 0px;
}
.lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab,
.lm_stack.lm_left .lm_header .lm_tabs .lm_tab {
@@ -156,7 +156,7 @@
.lm_stack.lm_right .lm_header .lm_tabs {
transform: rotate(90deg) scaleX(1);
left: 100%;
- margin-left: 0;
+ margin-left: 0px;
}
.lm_dragProxy.lm_right .lm_header .lm_controls,
.lm_stack.lm_right .lm_header .lm_controls {
@@ -169,7 +169,7 @@
}
.lm_dragProxy.lm_bottom .lm_header .lm_tab,
.lm_stack.lm_bottom .lm_header .lm_tab {
- margin-top: 0;
+ margin-top: 0px;
border-top: none;
}
.lm_dragProxy.lm_bottom .lm_header .lm_controls,
@@ -189,8 +189,8 @@
}
.lm_header .lm_controls .lm_tabdropdown:before {
content: '';
- width: 0;
- height: 0;
+ width: 0px;
+ height: 0px;
vertical-align: middle;
display: inline-block;
border-top: 5px dashed;
@@ -201,14 +201,14 @@
.lm_header .lm_tabdropdown_list {
position: absolute;
top: 20px;
- right: 0;
+ right: 0px;
z-index: 5;
overflow: hidden;
}
.lm_header .lm_tabdropdown_list .lm_tab {
clear: both;
padding-right: 10px;
- margin: 0;
+ margin: 0px;
}
.lm_header .lm_tabdropdown_list .lm_tab .lm_title {
width: 100px;
@@ -218,8 +218,8 @@
}
.lm_dragProxy {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 30;
}
.lm_dragProxy .lm_header {
@@ -238,32 +238,32 @@
width: 100%;
height: 100%;
position: relative;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
.lm_transition_indicator {
display: none;
width: 20px;
height: 20px;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 20;
}
.lm_popin {
width: 20px;
height: 20px;
position: absolute;
- bottom: 0;
- right: 0;
+ bottom: 0px;
+ right: 0px;
z-index: 9999;
}
.lm_popin > * {
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
.lm_popin > .lm_bg {
z-index: 10;
@@ -307,7 +307,7 @@
width: max-content;
height: 100%;
display: flex;
- max-width: 100;
+ max-width: 100px;
text-overflow: ellipsis;
}
@@ -328,7 +328,7 @@
ul.lm_tabs::before {
content: ' ';
position: absolute;
- bottom: 0;
+ bottom: 0px;
width: 100%;
z-index: 1;
pointer-events: none;
@@ -349,9 +349,9 @@ ul.lm_tabs::before {
}
}
.lm_header .lm_tab.lm_active {
- padding: 0;
+ padding: 0px;
opacity: 1;
- margin: 0;
+ margin: 0px;
box-shadow: none;
height: 27px;
margin-right: 2px;
@@ -405,7 +405,7 @@ ul.lm_tabs::before {
}
.lm_drag_tab {
- padding: 0;
+ padding: 0px;
width: 15px !important;
height: 15px !important;
position: relative !important;
@@ -418,7 +418,7 @@ ul.lm_tabs::before {
.lm_close_tab {
display: inline-flex !important;
- padding: 0;
+ padding: 0px;
opacity: 1 !important;
align-self: center;
margin-right: 5px;
@@ -455,7 +455,7 @@ ul.lm_tabs::before {
content: 'x';
margin: auto;
position: relative;
- top: -2;
+ top: -2px;
font-size: medium;
font-family: sans-serif;
}
@@ -478,8 +478,8 @@ ul.lm_tabs::before {
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
.collectionDockingView-drag {
touch-action: none;
@@ -514,7 +514,7 @@ ul.lm_tabs::before {
border-bottom-left-radius: 10px;
background: #93939347;
z-index: 100;
- //left: -3;
+ //left: -3px;
&:hover {
background: gray;
color: white !important;
@@ -524,7 +524,7 @@ ul.lm_tabs::before {
content: '+';
margin: auto;
font-size: x-large;
- top: -4;
+ top: -4px;
position: relative;
}
.lm_maximise {
@@ -548,10 +548,10 @@ ul.lm_tabs::before {
}
.flexlayout__layout {
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
+ left: 0px;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
position: absolute;
overflow: hidden;
}
@@ -691,8 +691,8 @@ ul.lm_tabs::before {
.flexlayout__tabset_header {
position: absolute;
- left: 0;
- right: 0;
+ left: 0px;
+ right: 0px;
color: #eee;
background-color: #212121;
padding: 3px 3px 3px 5px;
@@ -702,17 +702,17 @@ ul.lm_tabs::before {
.flexlayout__tab_header_inner {
position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
+ left: 0px;
+ top: 0px;
+ bottom: 0px;
width: 10000px;
}
.flexlayout__tab_header_outer {
background-color: global.$dark-gray;
position: absolute;
- left: 0;
- right: 0;
+ left: 0px;
+ right: 0px;
/*top: 0px;*/
/*height: 100px;*/
overflow: hidden;
@@ -731,23 +731,23 @@ ul.lm_tabs::before {
display: flex;
flex-direction: row-reverse;
align-items: center;
- top: 0;
- bottom: 0;
- right: 0;
+ top: 0px;
+ bottom: 0px;
+ right: 0px;
}
.flexlayout__tab_toolbar_button-min {
width: 20px;
height: 20px;
border: none;
- outline-width: 0;
+ outline-width: 0px;
}
.flexlayout__tab_toolbar_button-max {
width: 20px;
height: 20px;
border: none;
- outline-width: 0;
+ outline-width: 0px;
}
.flexlayout__popup_menu_item {
@@ -870,9 +870,9 @@ ul.lm_tabs::before {
display: flex;
flex-direction: column-reverse;
align-items: center;
- bottom: 0;
- left: 0;
- right: 0;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
}
.flexlayout__border_toolbar_right {
@@ -880,9 +880,9 @@ ul.lm_tabs::before {
display: flex;
flex-direction: column-reverse;
align-items: center;
- bottom: 0;
- left: 0;
- right: 0;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
}
.flexlayout__border_toolbar_top {
@@ -890,9 +890,9 @@ ul.lm_tabs::before {
display: flex;
flex-direction: row-reverse;
align-items: center;
- top: 0;
- bottom: 0;
- right: 0;
+ top: 0px;
+ bottom: 0px;
+ right: 0px;
}
.flexlayout__border_toolbar_bottom {
@@ -900,8 +900,8 @@ ul.lm_tabs::before {
display: flex;
flex-direction: row-reverse;
align-items: center;
- top: 0;
- bottom: 0;
- right: 0;
+ top: 0px;
+ bottom: 0px;
+ right: 0px;
}
}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index d1f7971d4..164c6e831 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -7,6 +7,7 @@ import { emptyFunction, numberRange } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
+import { StrCast } from '../../../fields/Types';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { CompileScript } from '../../util/Scripting';
@@ -16,6 +17,7 @@ import { undoBatch, undoable } from '../../util/UndoManager';
import { EditableView } from '../EditableView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DocumentView } from '../nodes/DocumentView';
+import { ImportElementBox } from '../nodes/importBox/ImportElementBox';
import { CollectionStackingView } from './CollectionStackingView';
import './CollectionStackingView.scss';
@@ -254,7 +256,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor(this._props.panelWidth() / this._props.columnWidth())));
return this.collapsed ? null : (
<div style={{ position: 'relative' }}>
- {!this._props.chromeHidden ? (
+ {!this._props.chromeHidden && !StrCast(this._props.Doc.childLayoutString).includes(ImportElementBox.name) ? (
<div className="collectionStackingView-addDocumentButton">
<EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" />
</div>
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 0d24a56b5..231085338 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -82,7 +82,7 @@
height: 100%;
width: 100%;
position: absolute;
- top: 0;
+ top: 0px;
overflow-y: auto;
overflow-x: hidden;
transition: top 0.5s;
@@ -130,8 +130,8 @@
display: flex;
flex-direction: column;
align-items: center;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
position: absolute;
margin: auto;
@@ -152,20 +152,20 @@
}
.collectionNoteTakingView-columnDragger {
- width: 15;
- height: 15;
+ width: 15px;
+ height: 15px;
position: absolute;
- margin-left: -5;
+ margin-left: -5px;
}
.collectionNoteTakingView-sectionDelete {
display: none;
position: absolute;
- right: 0;
+ right: 0px;
width: max-content;
height: max-content;
- top: 10;
- padding: 2;
+ top: 10px;
+ padding: 2px;
}
// Documents in NoteTaking view
@@ -210,8 +210,8 @@
height: 5px;
&.active {
- margin-left: 0;
- margin-right: 0;
+ margin-left: 0px;
+ margin-right: 0px;
background: red;
}
}
@@ -303,8 +303,8 @@
.collectionNoteTakingView-sectionColor {
position: absolute;
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
height: 100%;
display: none;
@@ -345,8 +345,8 @@
.collectionNoteTakingView-sectionOptions {
position: absolute;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
height: 100%;
display: none;
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index d05c0ffde..e71df2164 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -38,7 +38,7 @@
height: 100%;
background-color: global.$dark-gray;
opacity: 0.3;
- top: 0;
+ top: 0px;
}
.collectionStackedTimeline-trim-controls {
@@ -49,8 +49,8 @@
display: flex;
justify-content: space-between;
max-width: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
.collectionStackedTimeline-trim-handle {
background-color: global.$medium-blue;
@@ -106,18 +106,18 @@
.collectionStackedTimeline-resizer {
background: global.$dark-gray;
position: absolute;
- top: 0;
+ top: 0px;
height: 100%;
width: 10px;
pointer-events: all;
z-index: 100;
}
.collectionStackedTimeline-resizer {
- right: 0;
+ right: 0px;
cursor: e-resize;
}
.collectionStackedTimeline-left-resizer {
- left: 0;
+ left: 0px;
cursor: w-resize;
}
}
@@ -126,8 +126,8 @@
position: absolute;
width: 100%;
height: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
pointer-events: none;
}
}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index d6e4943ff..2cf361847 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -16,10 +16,10 @@
width: 100%;
.collectionStackingView-columnDragger {
- width: 28;
- height: 28;
- position: absolute;
- margin-left: -5;
+ width: 28px;
+ height: 28px;
+ position: relative;
+ margin-left: -5px;
z-index: 10;
> svg {
width: 100%;
@@ -55,7 +55,7 @@
height: 100%;
width: 100%;
position: absolute;
- top: 0;
+ top: 0px;
overflow-y: auto;
overflow-x: hidden;
transition: top 0.5s;
@@ -93,8 +93,8 @@
.collectionStackingView-masonryGrid {
width: 100%;
display: grid;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
.collectionStackingView-masonrySingle {
@@ -115,8 +115,8 @@
position: absolute;
display: flex;
flex-direction: column;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
position: absolute;
}
@@ -159,12 +159,12 @@
width: 100%;
display: none;
position: absolute;
- top: 0;
+ top: 0px;
cursor: default;
&.active {
- margin-left: 0;
- margin-right: 0;
+ margin-left: 0px;
+ margin-right: 0px;
background: red;
}
}
@@ -262,8 +262,8 @@
.collectionStackingView-sectionColor {
position: absolute;
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
height: 100%;
display: none;
@@ -304,8 +304,8 @@
.collectionStackingView-sectionOptions {
position: absolute;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
height: 100%;
display: none;
@@ -339,7 +339,7 @@
.collectionStackingView-sectionDelete {
position: absolute;
right: 0px;
- top: 0;
+ top: 0px;
height: 100%;
display: none;
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4a0ddc631..bdeb7d944 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -64,7 +64,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@observable _cursor: CSS.Property.Cursor = 'ew-resize';
// gets reset whenever we scroll. Not sure what it is
@observable _scroll = 0; // used to force the document decoration to update when scrolling
- // does this mean whether the browser is hidden? Or is chrome something else entirely?
+ // whether ui/editing controls are shown
@computed get chromeHidden() {
return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
}
@@ -566,7 +566,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return (
<div key={(heading?.heading ?? '') + 'head'}>
{this._props.isContentActive() && !this.isStackingView && !this.chromeHidden ? this.columnDragger : null}
- <div style={{ top: this.yMargin }}>
+ <div style={{ position: 'relative' }}>
<CollectionMasonryViewFieldRow
showHandle={first}
Doc={this.Document}
@@ -715,9 +715,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string,
pointerEvents: this._props.pointerEvents?.() ?? this.backgroundEvents,
}}
- onScroll={action(e => {
- this._scroll = e.currentTarget.scrollTop;
- })}
+ onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
onDrop={this.onExternalDrop.bind(this)}
onContextMenu={this.onContextMenu}
onWheel={e => this.isContentActive() && e.stopPropagation()}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 8c7cb8276..8c535534a 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { DivHeight, DivWidth, returnEmptyString, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
@@ -26,7 +27,6 @@ import { EditableView } from '../EditableView';
import { DocumentView } from '../nodes/DocumentView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import './CollectionStackingView.scss';
-import { DocData } from '../../../fields/DocSymbols';
// So this is how we are storing a column
interface CSVFieldColumnProps {
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index d995cbcd2..d56999974 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -27,7 +27,7 @@
transform: rotate(45deg);
display: inline-block;
background: gray;
- bottom: 0;
+ bottom: 0px;
margin-bottom: -17px;
border-radius: 9px;
opacity: 0.25;
@@ -67,9 +67,9 @@
pointer-events: all;
padding: 5px;
border: 1px solid black;
- display:none;
+ display: none;
span {
- margin-left : 10px;
+ margin-left: 10px;
}
}
@@ -86,8 +86,9 @@
}
}
-.collectionTimeView:hover, .collectionTimeView-pivot:hover {
+.collectionTimeView:hover,
+.collectionTimeView-pivot:hover {
.pivotKeyEntry {
- display:unset;
+ display: unset;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2a03ea708..95faaa3f0 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -11,7 +11,7 @@
height: 100%;
width: 100%;
position: relative;
- top: 0;
+ top: 0px;
// background: global.$light-gray;
font-size: 13px;
overflow: auto;
@@ -33,12 +33,12 @@
}
.no-indent {
- padding-left: 0;
+ padding-left: 0px;
//width: max-content;
}
.no-indent-outline {
- padding-left: 0;
+ padding-left: 0px;
width: 100%;
}
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index 06c324bd0..837219e1d 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -1,7 +1,7 @@
@use '../global/globalCssVariables.module.scss' as global;
.collectionView {
- border-width: 0;
+ border-width: 0px;
border-color: global.$light-gray;
border-style: solid;
border-radius: 0 0 global.$border-radius global.$border-radius;
@@ -18,7 +18,7 @@
position: absolute;
top: 55%;
border: 1px black solid;
- border-radius: 0;
+ border-radius: 0px;
border-top-left-radius: 20px;
border-bottom-left-radius: 20px;
border-right: unset;
@@ -31,8 +31,8 @@
width: 200px;
height: 100%;
position: absolute;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
border-left: solid 1px;
z-index: 1;
diff --git a/src/client/views/collections/FlashcardPracticeUI.scss b/src/client/views/collections/FlashcardPracticeUI.scss
index 210c6798f..0cc4711b3 100644
--- a/src/client/views/collections/FlashcardPracticeUI.scss
+++ b/src/client/views/collections/FlashcardPracticeUI.scss
@@ -8,8 +8,8 @@
.FlashcardPracticeUI-check {
position: absolute;
display: flex;
- width: 30;
- height: 30;
+ width: 30px;
+ height: 30px;
align-items: center;
border-radius: 5px;
justify-content: center;
@@ -39,7 +39,7 @@
display: flex;
top: 0px;
left: 0px;
- width: 30;
+ width: 30px;
transform-origin: top left;
border-radius: 5px;
color: rgba(255, 255, 255, 0.5);
@@ -49,7 +49,7 @@
width: 100%;
display: flex;
flex-direction: column;
- top: 0;
+ top: 0px;
position: relative;
.FlashcardPracticeUI-quiz,
.FlashcardPracticeUI-practice {
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
deleted file mode 100644
index 7dc08389b..000000000
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-/* eslint-disable react/button-has-type */
-import { observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-
-interface IKeyRestrictionProps {
- contains: boolean;
- script: (value: string) => void;
- field: string;
- value: string;
-}
-
-@observer
-export default class KeyRestrictionRow extends React.Component<IKeyRestrictionProps> {
- @observable private _key = this.props.field;
- @observable private _value = this.props.value;
- @observable private _contains = this.props.contains;
-
- render() {
- if (this._key && this._value) {
- let parsedValue: string | number = `"${this._value}"`;
- const parsed = parseInt(this._value);
- let type = 'string';
- if (!isNaN(parsed)) {
- parsedValue = parsed;
- type = 'number';
- }
- const scriptText = `${this._contains ? '' : '!'}(((doc.${this._key} && (doc.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))) ||
- ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))))`;
- // let doc = new Doc();
- // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello"));
- this.props.script(scriptText);
- } else {
- this.props.script('');
- }
-
- return (
- <div className="collectionViewBaseChrome-viewSpecsMenu-row">
- <input
- className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
- value={this._key}
- onChange={e =>
- runInAction(() => {
- this._key = e.target.value;
- })
- }
- placeholder="KEY"
- />
- <button
- className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
- style={{ background: this._contains ? '#77dd77' : '#ff6961' }}
- onClick={() =>
- runInAction(() => {
- this._contains = !this._contains;
- })
- }>
- {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
- </button>
- <input
- className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- value={this._value}
- onChange={e =>
- runInAction(() => {
- this._value = e.target.value;
- })
- }
- placeholder="VALUE"
- />
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 931cdac2b..b705d17f3 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -92,6 +92,6 @@ input.lm_title {
.miniMap-hidden {
cursor: pointer;
position: absolute;
- bottom: 5;
- right: 5;
+ bottom: 5px;
+ right: 5px;
}
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 78794d112..542b0cc87 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -136,8 +136,8 @@
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 0;
filter: opacity(0);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
index 7951aff65..32cf3586f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
@@ -18,5 +18,5 @@
color: black;
// fontStyle: "italic",
margin-left: -12;
- margin-top: 4;
+ margin-top: 4px;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 6c47a71b0..ac1ef7d65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -2,8 +2,8 @@
.collectionfreeformview-none {
position: inherit;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
transform-origin: left top;
@@ -12,10 +12,10 @@
border-radius: inherit;
}
.collectionFreeForm-groupDropper {
- width: 10000;
- height: 10000;
- left: -5000;
- top: -5000;
+ width: 10000px;
+ height: 10000px;
+ left: -5000px;
+ top: -5000px;
position: absolute;
background: transparent;
pointer-events: all;
@@ -24,8 +24,8 @@
.collectionfreeformview-grid {
transform-origin: top left;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
pointer-events: none;
}
@@ -219,8 +219,8 @@
border-radius: inherit;
box-sizing: border-box;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
align-items: center;
@@ -264,7 +264,7 @@
.collectionFreeform-infoUI {
position: absolute;
display: block;
- top: 0;
+ top: 0px;
color: white;
background-color: #5075ef;
@@ -275,19 +275,19 @@
padding: 10px;
.collectionFreeform-infoUI-close {
position: absolute;
- top: -10;
- left: -10;
+ top: -10px;
+ left: -10px;
}
.collectionFreeform-infoUI-msg {
position: relative;
- max-width: 500;
- margin: 10;
+ max-width: 500px;
+ margin: 10px;
}
.collectionFreeform-infoUI-button {
border-radius: 50px;
font-size: 12px;
- padding: 6;
+ padding: 6px;
position: relative;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3571dab1a..32ace463d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -8,7 +8,7 @@ import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
-import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
+import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnTrue, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
import { DocData, DocLayout, Height, Width } from '../../../../fields/DocSymbols';
@@ -2065,6 +2065,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
});
+ showBorderRounding = returnTrue;
showPresPaths = () => SnappingManager.ShowPresPaths;
brushedView = () => this._brushedView;
gridColor = () => DashColor(lightOrDark(this.backgroundColor)).fade(0.5).toString(); // prettier-ignore
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss
index 0a001d84c..d0685e419 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss
@@ -2,7 +2,7 @@
display: flex;
height: max-content;
flex-direction: column;
- top: 0;
+ top: 0px;
position: absolute;
width: 100%;
height: 100%;
@@ -31,9 +31,9 @@
}
.face-document-top {
position: relative;
- top: 0;
+ top: 0px;
width: 100%;
- left: 0;
+ left: 0px;
}
.face-document-image-container {
@@ -69,8 +69,8 @@
.remove-item {
position: absolute;
- bottom: -5;
- right: -5;
+ bottom: -5px;
+ right: -5px;
background-color: rgba(0, 0, 0, 0.5); // Optional: to add a background behind the icon for better visibility
border-radius: 30%;
width: 10px; // Adjust size as needed
@@ -98,7 +98,7 @@
.faceCollectionBox {
width: 100%;
height: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
position: absolute;
}
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index ff9fb14e7..e3a3f9b05 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -160,15 +160,16 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
classifyImagesInBox = async () => {
this.startLoading();
+ const selectedImages = this._selectedImages;
// Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them.
- const imageInfos = this._selectedImages.map(async doc => {
+ const imageInfos = selectedImages.map(async doc => {
if (!doc.$tags_chat) {
const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? '';
return imageUrlToBase64(url).then(hrefBase64 =>
!hrefBase64 ? undefined :
- gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels =>
+ gptImageLabel(hrefBase64, 'Give three labels to describe this image.').then(labels =>
({ doc, labels }))) ; // prettier-ignore
}
});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index abd828945..2ec59e5d5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -16,6 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => Doc | void = unimplementedFunction;
public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public generateScrapbook: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
@@ -38,6 +39,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
<IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
<IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
<IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
+ <IconButton tooltip="Generate Scrapbook" onPointerDown={this.generateScrapbook} icon={<FontAwesomeIcon icon="palette" />} color={this.userColor} />
<IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
<IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
<IconButton tooltip="Classify and Sort Images" onPointerDown={this.classifyImages} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 7c9d0f6e1..135f4deac 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -1,7 +1,7 @@
.marqueeView {
position: inherit;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
overflow: hidden;
@@ -20,7 +20,7 @@
pointer-events: none;
.marquee-legend {
bottom: -18px;
- left: 0;
+ left: 0px;
position: absolute;
font-size: 9;
white-space: nowrap;
@@ -28,4 +28,4 @@
.marquee-legend::after {
content: 'Press <space> for lasso';
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c120cddf0..ff78b332a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -28,6 +28,9 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { SubCollectionViewProps } from '../CollectionSubView';
import { ImageLabelBoxData } from './ImageLabelBox';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
+import { StrListCast } from '../../../../fields/Doc';
+import { requestAiGeneratedPreset, DocumentDescriptor } from '../../nodes/scrapbook/AIPresetGenerator';
+import { buildPlaceholdersFromConfigs, slotRealDocIntoPlaceholders } from '../../nodes/scrapbook/ScrapbookBox';
import './MarqueeView.scss';
interface MarqueeViewProps {
@@ -76,6 +79,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
@observable _labelsVisibile: boolean = false;
@observable _lassoPts: [number, number][] = [];
@observable _lassoFreehand: boolean = false;
+ // ─── New Observables for “Pick 1 of N AI Scrapbook” ───
+ @observable aiChoices: Doc[] = []; // temporary hidden Scrapbook docs
+ @observable pickerX = 0; // popup x coordinate
+ @observable pickerY = 0; // popup y coordinate
+ @observable pickerVisible = false; // show/hide ScrapbookPicker
@computed get Transform() {
return this._props.getTransform();
@@ -276,6 +284,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
MarqueeOptionsMenu.Instance.createCollection = this.collection;
MarqueeOptionsMenu.Instance.delete = this.delete;
MarqueeOptionsMenu.Instance.summarize = this.summary;
+ MarqueeOptionsMenu.Instance.generateScrapbook = this.generateScrapbook;
MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -518,6 +527,77 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
MarqueeOptionsMenu.Instance.fadeOut(true);
});
+ getAiPresetsDescriptors = (): DocumentDescriptor[] =>
+ this.marqueeSelect(false).map(doc => ({
+ type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN',
+ tags: Array.from(new Set(StrListCast(doc.$tags_chat))),
+ }));
+
+ generateScrapbook = action(async () => {
+ const selectedDocs = this.marqueeSelect(false);
+ if (!selectedDocs.length) return;
+
+ const descriptors = this.getAiPresetsDescriptors();
+ if (descriptors.length === 0) {
+ alert('No documents selected to generate a scrapbook from!');
+ return;
+ }
+
+ const aiPreset = await requestAiGeneratedPreset(descriptors);
+ if (!aiPreset.length) {
+ alert('Failed to generate preset');
+ return;
+ }
+ const scrapbookPlaceholders: Doc[] = buildPlaceholdersFromConfigs(aiPreset);
+ /*
+ const scrapbookPlaceholders: Doc[] = aiPreset.map(cfg => {
+ const placeholderDoc = Docs.Create.TextDocument(cfg.tag);
+ placeholderDoc.placeholder_docType = cfg.type as DocumentType;
+ placeholderDoc.placeholder_acceptTags = new List<string>(cfg.acceptTags ?? [cfg.tag]);
+
+ const placeholder = new Doc();
+ placeholder.proto = placeholderDoc;
+ placeholder.original = placeholderDoc;
+ placeholder.x = cfg.x;
+ placeholder.y = cfg.y;
+ if (cfg.width != null) placeholder._width = cfg.width;
+ if (cfg.height != null) placeholder._height = cfg.height;
+
+ return placeholder;
+ });*/
+
+ const scrapbook = Docs.Create.ScrapbookDocument(scrapbookPlaceholders, {
+ backgroundColor: '#e2ad32',
+ x: this.Bounds.left,
+ y: this.Bounds.top,
+ _width: 500,
+ _height: 500,
+ title: 'AI-generated Scrapbook',
+ });
+
+ // 3) Now grab that new scrapbook’s flat placeholders
+ const allPlaceholders = DocUtils.unwrapPlaceholders(scrapbookPlaceholders);
+
+ // 4) Slot each selectedDocs[i] into the first matching placeholder
+ selectedDocs.forEach(realDoc => slotRealDocIntoPlaceholders(realDoc, allPlaceholders));
+
+ const selected = selectedDocs.map(d => {
+ this._props.removeDocument?.(d);
+ d.x = NumCast(d.x) - this.Bounds.left;
+ d.y = NumCast(d.y) - this.Bounds.top;
+ return d;
+ });
+
+ this._props.addDocument?.(scrapbook);
+ const portal = Docs.Create.FreeformDocument(selected, { title: 'docs in scrapbook', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+ DocUtils.MakeLink(scrapbook, portal, { link_relationship: 'scrapbook of:in scrapbook' });
+
+ portal.hidden = true;
+ this._props.addDocument?.(portal);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ });
+
@action
marqueeCommand = (e: KeyboardEvent) => {
const ee = e as unknown as KeyboardEvent & { propagationIsStopped?: boolean };
@@ -539,6 +619,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
if (e.key === 'g') this.collection(e, true);
if (e.key === 'c' || e.key === 't') this.collection(e);
if (e.key === 's' || e.key === 'S') this.summary();
+ if (e.key === 'g' || e.key === 'G') this.generateScrapbook(); // ← scrapbook shortcut
if (e.key === 'p') this.pileup();
this.cleanupInteractions(false);
}
@@ -683,25 +764,27 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
};
render() {
return (
- <div
- className="marqueeView"
- ref={r => {
- r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject);
- this.MarqueeRef = r;
- }}
- style={{
- overflow: StrCast(this._props.Document._overflow),
- cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer',
- }}
- onDragOver={e => e.preventDefault()}
- onScroll={e => {
- e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0;
- }}
- onClick={this.onClick}
- onPointerDown={this.onPointerDown}>
- {this._visible ? this.marqueeDiv : null}
- {this.props.children}
- </div>
+ <>
+ <div
+ className="marqueeView"
+ ref={r => {
+ r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject);
+ this.MarqueeRef = r;
+ }}
+ style={{
+ overflow: StrCast(this._props.Document._overflow),
+ cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer',
+ }}
+ onDragOver={e => e.preventDefault()}
+ onScroll={e => {
+ e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0;
+ }}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}>
+ {this._visible ? this.marqueeDiv : null}
+ {this.props.children}
+ </div>
+ </>
);
}
}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index 4edaf9745..b95d3ea44 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -39,8 +39,8 @@
background: #d3d3d3;
position: absolute;
- height: 3;
- left: 5;
+ height: 3px;
+ left: 5px;
transform-origin: left;
transform: rotate(90deg);
outline: none;
@@ -133,7 +133,7 @@
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
- margin: 0;
+ margin: 0px;
}
/* Firefox */
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 0dfaed38a..0053d3e60 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -47,7 +47,7 @@
background: global.$medium-blue;
display: flex;
border-radius: 10px;
- height: 35;
+ height: 35px;
transform: translate3d(6px, 0px, 0px);
align-content: center;
justify-content: center;
@@ -95,11 +95,11 @@
pointer-events: all;
cursor: pointer;
background-color: global.$medium-blue;
- padding: 5;
+ padding: 5px;
border-radius: 2px;
height: 100%;
- min-width: 25;
- margin: 0;
+ min-width: 25px;
+ margin: 0px;
color: global.$white;
display: flex;
font-weight: 100;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index 9ed247d50..1dc46102f 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -1,8 +1,8 @@
.collectionMulticolumnView_drop {
height: 100%;
width: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
position: absolute;
.collectionMulticolumnView_contents {
@@ -18,8 +18,8 @@
align-items: center;
position: relative;
> .iconButton-container {
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
position: absolute;
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index 91779065d..eb157d155 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -1,8 +1,8 @@
.collectionMultirowView_drop {
height: 100%;
width: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
position: absolute;
.collectionMultirowView_contents {
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 10a6fa2e9..2ff99f134 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index 66215f109..4f57e1656 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 918365700..1954b4743 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -66,7 +65,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
style={{
pointerEvents: this.props.isContentActive?.() ? 'all' : 'none',
height: this.props.height,
- backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string,
+ backgroundColor: !this.props.isContentActive?.() ? '' : (this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string),
}}>
<div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
</div>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 53c0823ea..e975ae6f6 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -18,7 +18,7 @@
.schema-add {
position: relative;
- height: 35;
+ height: 35px;
display: flex;
align-items: center;
top: -10px;
@@ -147,7 +147,7 @@
flex-direction: row;
justify-content: space-between;
align-items: center;
- padding: 0;
+ padding: 0px;
z-index: 1;
border: 1px solid global.$medium-gray;
@@ -231,7 +231,7 @@
overflow-x: hidden;
overflow-y: auto;
display: inline-flex;
- padding: 0;
+ padding: 0px;
align-items: center;
input[type='text'] {
border: unset;
@@ -272,8 +272,8 @@
.row-menu-infos {
position: absolute;
- top: 3;
- left: 3;
+ top: 3px;
+ left: 3px;
z-index: 1;
display: flex;
justify-content: flex-end;
diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss
index 82f6caa52..7641d4929 100644
--- a/src/client/views/global/globalCssVariables.module.scss
+++ b/src/client/views/global/globalCssVariables.module.scss
@@ -75,7 +75,7 @@ $CAROUSEL3D_CENTER_SCALE: 1.3;
$CAROUSEL3D_SIDE_SCALE: 0.6;
$CAROUSEL3D_TOP: 15;
-$DATA_VIZ_TABLE_ROW_HEIGHT: 30;
+$DATA_VIZ_TABLE_ROW_HEIGHT: 30px;
:export {
contextMenuZindex: $contextMenu-zindex;
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 3cd60c87f..cc8c168cf 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -114,7 +114,7 @@
.linkMenu-deleteButton {
width: 20px;
height: 20px;
- margin: 0;
+ margin: 0px;
margin-right: 4px;
padding-right: 6px;
border-radius: 50%;
@@ -134,7 +134,7 @@
}
&:last-child {
- margin-right: 0;
+ margin-right: 0px;
}
&:hover {
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
index 4bfb4b0b9..f8d724767 100644
--- a/src/client/views/linking/LinkPopup.scss
+++ b/src/client/views/linking/LinkPopup.scss
@@ -1,7 +1,9 @@
.linkPopup-container {
background: white;
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
- top: 0;
+ box-shadow:
+ 0 10px 20px rgba(0, 0, 0, 0.19),
+ 0 6px 6px rgba(0, 0, 0, 0.23);
+ top: 0px;
height: 200px;
width: 200px;
// padding: 15px;
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index b654f9bd0..760850241 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
diff --git a/src/client/views/newlightbox/NewLightboxView.scss b/src/client/views/newlightbox/NewLightboxView.scss
index 76c34bcf9..c76a7d60d 100644
--- a/src/client/views/newlightbox/NewLightboxView.scss
+++ b/src/client/views/newlightbox/NewLightboxView.scss
@@ -2,8 +2,8 @@
.newLightboxView-frame {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
background: #474545bb;
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
index cf6b5ccb1..09d3ccc62 100644
--- a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
@@ -135,7 +135,7 @@
font-size: 10px;
width: 100%;
background: newstyles.$blue-l1;
- border-radius: 0;
+ border-radius: 0px;
padding: 10px;
.concepts-container {
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 933a383ea..c25c09af9 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -138,7 +138,7 @@
input[type='range']::-webkit-slider-thumb {
box-shadow: 0;
- border: 0;
+ border: 0px;
height: 10px;
width: 10px;
border-radius: 10px;
@@ -168,7 +168,7 @@
.audiobox-button {
width: 15px;
height: 15px;
- margin: 0;
+ margin: 0px;
svg {
width: 10px;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index 7f0a39550..300533df8 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -3,7 +3,7 @@
position: absolute;
background-color: transparent;
touch-action: manipulation;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
pointer-events: none;
}
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index d2ba9796b..cbbd6bde3 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -18,7 +18,7 @@
}
.input-box {
position: absolute;
- top: 50;
+ top: 50px;
padding: 10px;
width: 100%;
height: 70%;
@@ -33,7 +33,7 @@
padding-right: 5px;
border-radius: 2px;
height: 17%;
- bottom: 0;
+ bottom: 0px;
overflow: hidden;
display: flex;
width: 100%;
@@ -101,7 +101,7 @@
position: absolute;
display: inline-block;
margin-top: 150px;
- bottom: 0;
+ bottom: 0px;
}
.dropup-content {
@@ -145,8 +145,8 @@
.clip-div {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
height: 100%;
overflow: hidden;
@@ -180,8 +180,8 @@
.afterBox-cont {
position: absolute;
- top: 0;
- right: 0;
+ top: 0px;
+ right: 0px;
height: 100%;
width: 100%;
overflow: hidden;
@@ -331,8 +331,8 @@
justify-content: space-between;
height: max-content;
position: absolute;
- bottom: 0;
- right: 2;
+ bottom: 0px;
+ right: 2px;
flex-direction: row-reverse;
display: flex;
cursor: pointer;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index 9825d926f..32a01355e 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -37,25 +37,25 @@
margin-left: 10px;
margin-bottom: -10px;
}
-
+
.displaySchemaLive {
margin-bottom: 20px;
}
.dataviz-sidebar {
position: absolute;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
height: 100%;
}
.button-container {
pointer-events: unset;
}
- .dataVizBox-annotationLayer{
+ .dataVizBox-annotationLayer {
position: absolute;
transform-origin: left top;
- top: 0;
+ top: 0px;
width: 100%;
pointer-events: none;
mix-blend-mode: multiply;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 6eb7fa96a..e2261b9e2 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -110,12 +110,12 @@
&::before {
content: '';
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
border-bottom: 20px solid rgb(50, 50, 50);
border-left: 12px solid transparent;
border-right: 12px solid transparent;
- height: 0;
+ height: 0px;
width: 50px;
}
@@ -127,7 +127,7 @@
border-bottom: 22px solid rgb(180, 180, 180);
border-left: 12px solid transparent;
border-right: 12px solid transparent;
- height: 0;
+ height: 0px;
width: 52px;
z-index: -1;
}
@@ -418,8 +418,8 @@
}
.div {
- width: 200;
- height: 200;
+ width: 200px;
+ height: 200px;
border: solid 1px white;
}
@@ -588,7 +588,7 @@
}
.docCreatorMenu-configuration-bar {
- width: 200;
+ width: 200px;
gap: 5px;
display: flex;
flex-direction: row;
@@ -709,8 +709,8 @@
width: 100%;
aspect-ratio: 1;
//height: auto;
- // max-width: 240;
- // max-height: 240;
+ // max-width: 240px;
+ // max-height: 240px;
border: 1px solid rgb(180, 180, 180);
border-radius: 5px;
background-color: rgb(34, 34, 37);
@@ -718,8 +718,8 @@
scrollbar-width: none;
&.small {
- max-width: 100;
- max-height: 100;
+ max-width: 100px;
+ max-height: 100px;
}
.docCreatorMenu-layout-preview-item {
@@ -1009,8 +1009,8 @@
}
&:hover .operator-dropdown-current {
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0px;
+ border-bottom-left-radius: 0px;
}
&:hover .operator-dropdown-option {
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss
index 63a693918..0acc2c847 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss
@@ -13,7 +13,7 @@ $highlightedText: #82e0ff;
min-height: 200px;
border-radius: 15px;
padding: 15px;
- padding-bottom: 0;
+ padding-bottom: 0px;
z-index: 999;
display: flex;
flex-direction: column;
@@ -40,7 +40,7 @@ $highlightedText: #82e0ff;
font-size: 12px;
font-weight: 400;
letter-spacing: 1px;
- margin: 0;
+ margin: 0px;
padding-right: 5px;
}
@@ -124,8 +124,8 @@ $highlightedText: #82e0ff;
.img-container::after {
content: '';
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index ff1fa343d..a22e1153c 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -91,7 +91,7 @@
margin: 5px;
margin-left: 25px;
margin-right: 10px;
- margin-bottom: 0;
+ margin-bottom: 0px;
.tableBox-table {
height: 100%;
width: 100%;
@@ -101,7 +101,7 @@
text-overflow: ellipsis;
width: 100%;
white-space: pre;
- max-width: 150;
+ max-width: 150px;
overflow: hidden;
margin-left: 2px;
}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 9e0868cd5..cc08cf269 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -401,7 +401,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
}
})}>
<thead>
- <tr style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
+ <tr style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT.replace("px","")) }} />
<tr>
{this.columns.map((col, i) => (
<th
@@ -470,7 +470,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
})}
</tr>
))}
- <tr style={{ display: this._tableDataIds.length - this.endID ? undefined : 'none', height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
+ <tr style={{ display: this._tableDataIds.length - this.endID ? undefined : 'none', height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT.replace("px","")) }} />
</tbody>
</table>
</div>
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index e1b83dc59..43b1e083f 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -17,8 +17,8 @@
}
.documentLinksButton-cont {
- min-width: 20;
- min-height: 20;
+ min-width: 20px;
+ min-height: 20px;
position: absolute;
}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c4351a200..98ca76339 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,6 +1,7 @@
@use '../global/globalCssVariables.module.scss' as global;
.documentView-effectsWrapper {
+ height: 100%;
border-radius: inherit;
transition: inherit;
}
@@ -14,13 +15,13 @@
width: 100%;
height: 100%;
position: absolute;
- top: 0;
+ top: 0px;
}
.documentView-node {
position: inherit;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
border-radius: inherit;
@@ -55,7 +56,7 @@
.documentView-htmlOverlay {
position: absolute;
display: flex;
- top: 0;
+ top: 0px;
height: 100%;
width: 100%;
.documentView-htmlOverlayInner {
@@ -79,9 +80,9 @@
.documentView-audioBackground {
display: inline-block;
width: 25px;
- height: 25;
+ height: 25px;
position: absolute;
- top: 0;
+ top: 0px;
left: 50%;
border-radius: 25px;
background: white;
@@ -130,7 +131,7 @@
width: 30px;
border-radius: 50%;
position: absolute;
- right: -15;
+ right: -15px;
opacity: 0.9;
pointer-events: auto;
background-color: #9dca96;
@@ -147,8 +148,8 @@
.documentView-anchorCont {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
display: inline-block;
@@ -160,8 +161,8 @@
position: absolute;
width: 100%;
height: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
.documentView-styleWrapper {
@@ -183,9 +184,9 @@
.documentView-titleWrapper-hover {
color: global.$black;
transform-origin: top left;
- top: 0;
+ top: 0px;
width: 100%;
- height: 14;
+ height: 14px;
opacity: 0.5;
text-align: center;
text-overflow: ellipsis;
@@ -211,7 +212,7 @@
.documentView-captionWrapper {
position: absolute;
- bottom: 0;
+ bottom: 0px;
width: 100%;
overflow-y: auto;
transform-origin: bottom left;
@@ -275,20 +276,20 @@
.documentView-noAIWidgets {
transform-origin: top left;
position: absolute;
- bottom: 0;
+ bottom: 0px;
pointer-events: none;
}
.documentView-widgetDecorations {
transform-origin: top right;
position: absolute;
- top: 0;
- right: 0;
+ top: 0px;
+ right: 0px;
}
.documentView-editorView-history {
position: absolute;
transform-origin: top right;
- right: 0;
+ right: 0px;
top: 0;
overflow-y: scroll;
scrollbar-width: thin;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9b73cc073..fe95f15af 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -720,7 +720,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
@computed get tagsOverlay() {
return (
<div
- className="documentView-noAiWidgets"
+ className="documentView-noAIWidgets"
style={{
width: `${100 / this.uiBtnScaling}%`, //
transform: `scale(${this.uiBtnScaling})`,
diff --git a/src/client/views/nodes/FontIconBox/FontIconBadge.scss b/src/client/views/nodes/FontIconBox/FontIconBadge.scss
index 2ff5c651f..e741936db 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBadge.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBadge.scss
@@ -6,7 +6,7 @@
color: black;
display: block;
position: absolute;
- right: 5;
+ right: 5px;
border-radius: 50%;
text-align: center;
}
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index 8bc68c131..52eebba54 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -42,7 +42,7 @@
letter-spacing: normal;
background-color: inherit;
border-radius: 8px;
- padding: 0;
+ padding: 0px;
width: 100%;
font-family: 'system-ui';
text-transform: uppercase;
@@ -96,22 +96,22 @@
display: inline-block;
width: 100%;
height: 25px;
- margin: 0;
+ margin: 0px;
}
.switch input {
opacity: 0;
- width: 0;
- height: 0;
+ width: 0px;
+ height: 0px;
}
.slider {
position: absolute;
cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
background-color: lightgrey;
-webkit-transition: 0.4s;
transition: 0.4s;
@@ -223,7 +223,7 @@
height: fit-content;
color: black;
top: 100%;
- left: 0;
+ left: 0px;
z-index: 21;
background-color: #e3e3e3;
box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
@@ -430,7 +430,7 @@
border-radius: 0px 7px 7px 0px;
width: 13px;
height: 100%;
- right: 0;
+ right: 0px;
}
.menuButton-dropdown-header {
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index d6cf95958..c0977dfc5 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -15,7 +15,7 @@
width: 20px;
height: 20px;
margin: auto;
- padding: 0;
+ padding: 0px;
border-radius: 50%;
background-color: global.$dark-gray;
background-color: transparent;
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 5a6292fab..90ede69dc 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -3,14 +3,14 @@
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
transform-origin: top left;
.imageBox-annotationLayer {
position: absolute;
transform-origin: left top;
- top: 0;
+ top: 0px;
width: 100%;
pointer-events: none;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
@@ -24,8 +24,8 @@
#upload-icon {
position: absolute;
- bottom: 0;
- right: 0;
+ bottom: 0px;
+ right: 0px;
width: 20px;
height: 20px;
}
@@ -51,8 +51,8 @@
.imageBox-dot {
position: absolute;
- bottom: 10;
- left: 0;
+ bottom: 10px;
+ left: 0px;
border-radius: 10px;
width: 20px;
height: 20px;
@@ -131,8 +131,8 @@
position: absolute;
color: white;
background: black;
- right: 0;
- bottom: 0;
+ right: 0px;
+ bottom: 0px;
z-index: 2;
transform-origin: bottom right;
cursor: default;
@@ -142,13 +142,13 @@
}
}
.imageBox-regenerateDropTarget {
- right: 35;
+ right: 35px;
transform-origin: 70px 35px;
}
.imageBox-fader img {
position: absolute;
- left: 0;
+ left: 0px;
}
.imageBox-fadeBlocker-hover {
@@ -223,7 +223,7 @@
max-width: 90%;
width: 100%;
.imageBox-aiView-similarity {
- max-width: 65;
+ max-width: 65px;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
@@ -250,7 +250,7 @@
z-index: 10000;
h3 {
- margin-top: 0;
+ margin-top: 0px;
}
input {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8ed59c6e1..5b738ee19 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { extname } from 'path';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
-import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
+import { ClientUtils, imageUrlToBase64, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon, returnTrue } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -16,7 +16,7 @@ import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast, ImageCastWithSuffix } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
@@ -45,6 +45,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
+import { gptImageLabel } from '../../apis/gpt/GPT';
const DefaultPath = '/assets/unknown-file-icon-hi.png';
export class ImageEditorData {
@@ -112,7 +113,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._props.setContentViewBox?.(this);
}
- @computed get oupaintOriginalSize(): { width: number; height: number } {
+ @computed get outpaintOriginalSize(): { width: number; height: number } {
return {
width: NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']),
height: NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']),
@@ -139,6 +140,42 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
};
+
+ autoTag = async () => {
+ if (this.Document.$tags_chat) return;
+ try {
+ // 1) grab the full-size URL
+ const layoutKey = Doc.LayoutDataKey(this.Document);
+ const url = ImageCastWithSuffix(this.Document[layoutKey], '_o');
+ if (!url) throw new Error('No image URL found');
+
+ // 2) convert to base64
+ const base64 = await imageUrlToBase64(url);
+ if (!base64) throw new Error('Failed to load image data');
+
+ // 3) ask GPT for labels one label: PERSON or LANDSCAPE
+ const label = await gptImageLabel(
+ base64,
+ `Classify this image as PERSON or LANDSCAPE. You may only respond with one of these two options.
+ Then provide five additional descriptive tags to describe the image for a total of 6 words outputted, delimited by spaces.
+ For example: "LANDSCAPE BUNNY NATURE FOREST PEACEFUL OUTDOORS".
+ Then add one final lengthier summary tag (separated by underscores) that describes the image.`
+ ).then(raw => raw.trim().toUpperCase());
+
+ const { nativeWidth, nativeHeight } = this.nativeSize;
+ const aspectRatio = ((nativeWidth || 1) / (nativeHeight || 1)).toFixed(2);
+
+ // 5) stash it on the Doc
+ // overwrite any old tags so re-runs still work
+ this.Document.$tags_chat = new List<string>([...label.split(/\s+/), `ASPECT_${aspectRatio}`]);
+
+ // 6) flip on “show tags” in the layout
+ this.Document._layout_showTags = true;
+ } catch (err) {
+ console.error('autoTag failed:', err);
+ }
+ };
+
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const visibleAnchor = this._getAnchor?.(this._savedAnnotations, true); // use marquee anchor, otherwise, save zoom/pan as anchor
const anchor =
@@ -225,9 +262,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
- handleSelection = async (selection: string) => {
- this._searchInput = selection;
- };
+ handleSelection = (selection: string) => (this._searchInput = selection);
drop = undoable(
action((e: Event, de: DragManager.DropEvent) => {
@@ -385,16 +420,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
cancelOutpaintPrompt = () => {
- [this.Document._width, this.Document._height] = [this.oupaintOriginalSize.width, this.oupaintOriginalSize.height];
+ [this.Document._width, this.Document._height] = [this.outpaintOriginalSize.width, this.outpaintOriginalSize.height];
this._outpaintingInProgress = false;
this.outpaintOriginalSize = undefined;
this.closeOutpaintPrompt();
};
@action
- handlePromptChange = (val: string | number) => {
- this._outpaintPromptInput = '' + val;
- };
+ handlePromptChange = (val: string | number) => (this._outpaintPromptInput = '' + val);
@action
submitOutpaintPrompt = () => {
@@ -435,7 +468,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
this._mainCont?.appendChild(loadingOverlay);
- const { width: origWidth, height: origHeight } = this.oupaintOriginalSize;
+ const { width: origWidth, height: origHeight } = this.outpaintOriginalSize;
const response = await Networking.PostToServer('/outpaintImage', {
imageUrl: currentPath,
prompt: customPrompt,
@@ -495,6 +528,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight;
}
+ isOutpaintable = () => true;
+
componentUI = (/* boundsLeft: number, boundsTop: number*/) =>
!this._showOutpaintPrompt ? null : (
<div
@@ -959,11 +994,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return { width, height };
};
savedAnnotations = () => this._savedAnnotations;
+ showBorderRounding = returnTrue;
rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView | undefined) => (this.dataDoc[this.fieldKey] === undefined ? true : (this._props.rejectDrop?.(de, subView) ?? false));
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 borderRadius = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string;
return (
<>
<div
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 441fceba4..80ace6ae0 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -93,9 +93,9 @@ $header-height: 30px;
height: 30px;
width: 5px;
z-index: 20;
- right: 0;
- top: 0;
- border-radius: 0;
+ right: 0px;
+ top: 0px;
+ border-radius: 0px;
background: black;
pointer-events: all;
}
@@ -105,8 +105,8 @@ $header-height: 30px;
float: left;
height: 37px;
z-index: 20;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
background: transparent;
pointer-events: none;
}
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 913ab641c..154fbdcfa 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -17,7 +17,7 @@
}
.keyValuePair-td-key-check {
position: relative;
- margin: 0;
+ margin: 0px;
}
.keyValuePair-keyField {
width: 100%;
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 889cdc0ca..e1974d6a0 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -24,8 +24,8 @@
.answer-icon {
position: absolute;
- right: 8;
- bottom: 5;
+ right: 8px;
+ bottom: 5px;
color: black;
display: inline-block;
font-size: 10px;
@@ -36,8 +36,8 @@
.q-icon {
position: absolute;
- right: 6;
- bottom: 5;
+ right: 6px;
+ bottom: 5px;
color: white;
display: inline-block;
font-size: 10px;
@@ -48,8 +48,8 @@
.edit-icon {
position: absolute;
- right: 20;
- bottom: 5;
+ right: 20px;
+ bottom: 5px;
display: inline-block;
font-size: 10px;
cursor: pointer;
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
index 28216394d..7d99247e7 100644
--- a/src/client/views/nodes/LinkDocPreview.scss
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -42,7 +42,7 @@
.linkDocPreview-button {
display: inline-flex;
- margin: 0;
+ margin: 0px;
margin-right: 3px;
border-radius: 50%;
pointer-events: auto;
diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss
index cabd4de05..5a8f49000 100644
--- a/src/client/views/nodes/LoadingBox.scss
+++ b/src/client/views/nodes/LoadingBox.scss
@@ -26,7 +26,7 @@
}
.loadingBox-spinner {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
}
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.scss b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
index c36d98afe..217576203 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.scss
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.scss
@@ -6,19 +6,19 @@
}
.anchorMenu-highlighter {
padding-right: 5px;
- .antimodeMenu-button {
- padding: 0;
- padding: 0;
+ .antimodeMenu-button {
+ padding: 0px;
+ padding: 0px;
padding-right: 0px;
padding-left: 0px;
width: 5px;
}
}
-.anchor-color-preview-button {
- width: 25px !important;
+.anchor-color-preview-button {
+ width: 25px !important;
.anchor-color-preview {
display: flex;
- flex-direction: column;
+ flex-direction: column;
padding-right: 3px;
width: unset !important;
.color-preview {
@@ -72,12 +72,11 @@
}
}
- .MuiInputBase-input{
+ .MuiInputBase-input {
color: white !important;
}
-
-
- .css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input.Mui-disabled{
+
+ .css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input.Mui-disabled {
-webkit-text-fill-color: #b3b2b2 !important;
}
@@ -91,7 +90,7 @@
gap: 5px;
}
- .selected-route-details-container{
+ .selected-route-details-container {
display: flex;
flex-direction: column;
gap: 3px;
@@ -99,33 +98,25 @@
align-items: flex-start;
padding: 5px;
}
-
-
}
- .customized-marker-container{
+ .customized-marker-container {
display: flex;
flex-direction: column;
gap: 10px;
- .current-marker-container{
+ .current-marker-container {
display: flex;
align-items: center;
gap: 5px;
}
- .all-markers-container{
+ .all-markers-container {
display: flex;
- align-items: center;
+ align-items: center;
gap: 10px;
flex-wrap: wrap;
max-width: 400px;
}
}
-
-
-
-
}
-
-
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index bd4b51038..89d381070 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -21,7 +21,7 @@
.mapBox-infoWindow {
background-color: white;
opacity: 0.75;
- padding: 12;
+ padding: 12px;
font-size: 17;
}
.mapBox-searchbar {
@@ -119,7 +119,7 @@
width: 100%;
label {
- margin-bottom: 0;
+ margin-bottom: 0px;
}
.speed-label {
@@ -197,12 +197,12 @@
}
.mapBox-sidebar {
position: absolute;
- right: 0;
+ right: 0px;
height: 100%;
}
.mapBox-sidebar-handle {
- top: 0;
+ top: 0px;
//top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views
width: 10px;
height: 100%;
@@ -215,7 +215,7 @@
left: 50%;
margin-left: 120px;
right: unset !important;
- margin-top: -10;
+ margin-top: -10px;
height: max-content;
}
.searchbox {
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index f09a2630a..e34ca61d4 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -17,8 +17,8 @@
height: 100%;
z-index: 1;
pointer-events: none;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
// glr: This should really be the same component as text and PDFs
.pdfBox-sidebarBtn {
@@ -72,16 +72,16 @@
align-items: center;
height: 20px;
background: none;
- padding: 0;
+ padding: 0px;
position: absolute;
pointer-events: all;
color: white;
- bottom: 0;
- right: 0;
+ bottom: 0px;
+ right: 0px;
.pdfBox-overlayButton-arrow {
- width: 0;
- height: 0;
+ width: 0px;
+ height: 0px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 15px solid #121721;
@@ -122,8 +122,8 @@
.pdfBox-settingsCont {
position: absolute;
- right: 0;
- top: 3;
+ right: 0px;
+ top: 3px;
pointer-events: all;
.pdfBox-settingsButton {
@@ -133,11 +133,11 @@
align-items: center;
height: 20px;
background: none;
- padding: 0;
+ padding: 0px;
.pdfBox-settingsButton-arrow {
- width: 0;
- height: 0;
+ width: 0px;
+ height: 0px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 15px solid #121721;
@@ -189,7 +189,7 @@
width: calc(100% - 40px);
height: 20px;
background: #121721;
- bottom: 0;
+ bottom: 0px;
display: flex;
justify-content: center;
align-items: center;
@@ -253,13 +253,13 @@
.pdfBox-container {
position: absolute;
transform-origin: top left;
- top: 0;
+ top: 0px;
}
.pdfBox-sidebarContainer {
position: absolute;
height: 100%;
- right: 0;
- top: 0;
+ right: 0px;
+ top: 0px;
}
.pdfBox-interactive {
@@ -290,7 +290,7 @@
}
.pdfBox-settingsButton-arrow {
- height: 60;
+ height: 60px;
border-top: 30px solid transparent;
border-bottom: 30px solid transparent;
border-right: 30px solid #121721;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 45fa5cc12..5501f0a31 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -33,6 +33,9 @@ import { ImageBox } from './ImageBox';
import { OpenWhere } from './OpenWhere';
import './PDFBox.scss';
import { CreateImage } from './WebBoxRenderer';
+import { gptAPICall } from '../../apis/gpt/GPT';
+import { List } from '../../../fields/List';
+import { GPTCallType } from '../../apis/gpt/GPT';
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -78,6 +81,36 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
}
+ autoTag = async () => {
+ if (!this.Document.$tags_chat && this._pdf) {
+ if (!this.dataDoc.text) {
+ // 1) Extract text from the first few pages (e.g., first 2 pages)
+ const maxPages = Math.min(2, this._pdf.numPages);
+ const promises: Promise<string>[] = [];
+ for (let pageNum = 1; pageNum <= maxPages; pageNum++) {
+ promises.push(
+ this._pdf
+ .getPage(pageNum)
+ .then(page => page.getTextContent())
+ .then(content => content.items.map(item => ('str' in item ? item.str : '')).join(' '))
+ );
+ }
+ this.dataDoc.text = (await Promise.all(promises)).join(' ');
+ }
+
+ const text = StrCast(this.dataDoc.text).trim().slice(0, 2000);
+ if (text) {
+ // 2) Ask GPT to classify and provide descriptive tags, then normalize the results
+ const label = await gptAPICall(`"${text}"`, GPTCallType.CLASSIFYTEXTFULL).then(raw => raw.trim().toUpperCase());
+
+ this.Document.$tags_chat = new List<string>(label.split(/\s+/));
+
+ // 4) Show tags in layout
+ this.Document._layout_showTags = true;
+ }
+ }
+ };
+
replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
if (oldDiv.childNodes) {
for (let i = 0; i < oldDiv.childNodes.length; i++) {
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss
index ac2c611c7..78aa526bf 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.scss
@@ -29,8 +29,8 @@
.wedge {
pointer-events: none;
position: absolute;
- left: 0;
- top: 0;
+ left: 0px;
+ top: 0px;
}
}
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.scss b/src/client/views/nodes/RecordingBox/ProgressBar.scss
index 28ad25ffa..ec01f0241 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.scss
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.scss
@@ -1,36 +1,34 @@
-
.progressbar {
- touch-action: none;
- vertical-align: middle;
- text-align: center;
-
- align-items: center;
- cursor: default;
-
-
- position: absolute;
- display: flex;
- justify-content: flex-start;
- bottom: 2px;
- width: 99%;
- height: 30px;
- background-color: gray;
-
- &.done {
- top: 0;
- width: 0px;
- height: 5px;
- background-color: red;
- z-index: 2;
- }
-
- &.mark {
- top: 0;
- background-color: transparent;
- border-right: 2px solid white;
- z-index: 3;
- pointer-events: none;
- }
+ touch-action: none;
+ vertical-align: middle;
+ text-align: center;
+
+ align-items: center;
+ cursor: default;
+
+ position: absolute;
+ display: flex;
+ justify-content: flex-start;
+ bottom: 2px;
+ width: 99%;
+ height: 30px;
+ background-color: gray;
+
+ &.done {
+ top: 0px;
+ width: 0px;
+ height: 5px;
+ background-color: red;
+ z-index: 2;
+ }
+
+ &.mark {
+ top: 0px;
+ background-color: transparent;
+ border-right: 2px solid white;
+ z-index: 3;
+ pointer-events: none;
+ }
}
.progressbar-disabled {
@@ -43,37 +41,41 @@
// citation: https://codepen.io/_Master_/pen/PRdjmQ
@keyframes blinker {
- from {opacity: 1.0;}
- to {opacity: 0.0;}
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
}
.blink {
- text-decoration: blink;
- animation-name: blinker;
- animation-duration: 0.6s;
- animation-iteration-count:infinite;
- animation-timing-function:ease-in-out;
- animation-direction: alternate;
+ text-decoration: blink;
+ animation-name: blinker;
+ animation-duration: 0.6s;
+ animation-iteration-count: infinite;
+ animation-timing-function: ease-in-out;
+ animation-direction: alternate;
}
.segment {
- border: 3px solid black;
- background-color: red;
- margin: 1px;
- padding: 0;
- cursor: pointer;
- transition-duration: .5s;
- user-select: none;
-
- vertical-align: middle;
- text-align: center;
+ border: 3px solid black;
+ background-color: red;
+ margin: 1px;
+ padding: 0px;
+ cursor: pointer;
+ transition-duration: 0.5s;
+ user-select: none;
+
+ vertical-align: middle;
+ text-align: center;
}
.segment-expanding {
-border-color: red;
- background-color: white;
- transition-duration: 0s;
- opacity: .75;
- pointer-events: none;
+ border-color: red;
+ background-color: white;
+ transition-duration: 0s;
+ opacity: 0.75;
+ pointer-events: none;
}
.segment-expanding:hover {
@@ -82,10 +84,10 @@ border-color: red;
}
.segment-disabled {
- pointer-events: none;
- opacity: 0.5;
- transition-duration: 0s;
- /* Hide the text. */
+ pointer-events: none;
+ opacity: 0.5;
+ transition-duration: 0s;
+ /* Hide the text. */
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
@@ -99,25 +101,26 @@ border-color: red;
}
.segment:first-child {
- margin-left: 2px;
+ margin-left: 2px;
}
.segment:last-child {
- margin-right: 2px;
+ margin-right: 2px;
}
.segment:hover {
background-color: white;
}
-.segment:hover, .segment-selected {
- margin: 0px;
- border: 4px solid red;
- border-radius: 2px;
+.segment:hover,
+.segment-selected {
+ margin: 0px;
+ border: 4px solid red;
+ border-radius: 2px;
}
.segment-selected {
- border: 4px solid #202020;
- background-color: red;
- opacity: .75;
- cursor: grabbing;
+ border: 4px solid #202020;
+ background-color: red;
+ opacity: 0.75;
+ cursor: grabbing;
}
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss
index f2d5a980d..15b48c111 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.scss
+++ b/src/client/views/nodes/RecordingBox/RecordingView.scss
@@ -28,7 +28,7 @@ video {
justify-content: center;
// overflow: hidden;
border-radius: 10px;
- margin: 0;
+ margin: 0px;
}
.video-wrapper:hover .controls {
@@ -108,7 +108,7 @@ video {
.timer {
font-size: 15px;
color: white;
- margin: 0;
+ margin: 0px;
}
.dot {
@@ -148,7 +148,7 @@ video {
height: 80%;
width: 80%;
align-self: center;
- margin: 0;
+ margin: 0px;
&:hover {
height: 85%;
@@ -163,7 +163,7 @@ video {
height: 70%;
width: 70%;
align-self: center;
- margin: 0;
+ margin: 0px;
// &:hover {
// width: 40px;
@@ -178,8 +178,8 @@ video {
flex-direction: row;
align-content: center;
position: relative;
- top: 0;
- bottom: 0;
+ top: 0px;
+ bottom: 0px;
&.video-edit-wrapper {
// right: 50% - 15;
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
index 1e9b64a0b..1714d87c2 100644
--- a/src/client/views/nodes/ScreenshotBox.scss
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -32,10 +32,10 @@
.screenshotBox-uiButtons {
position: absolute;
- right: 25;
- top: 0;
- width: 22;
- height: 25;
+ right: 25px;
+ top: 0px;
+ width: 22px;
+ height: 25px;
.screenshotBox-recorder {
color: white;
diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss
index 9789da55a..de70dbe74 100644
--- a/src/client/views/nodes/ScriptingBox.scss
+++ b/src/client/views/nodes/ScriptingBox.scss
@@ -82,14 +82,14 @@
}
.rta__autocomplete--top {
- margin-top: 0;
+ margin-top: 0px;
margin-bottom: 1em;
max-height: 100px;
}
.rta__list {
- margin: 0;
- padding: 0;
+ margin: 0px;
+ padding: 0px;
background: #fff;
border: 1px solid #dfe2e5;
border-radius: 3px;
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b5405f0fb..27f419198 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -3,8 +3,8 @@
.mini-viewer {
cursor: grab;
position: absolute;
- right: 10;
- top: 10;
+ right: 10px;
+ top: 10px;
opacity: 0.1;
transition: all 0.4s;
color: white;
@@ -38,7 +38,7 @@
.videoBox-annotationLayer {
position: relative;
transform-origin: left top;
- top: 0;
+ top: 0px;
width: 100%;
pointer-events: none;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
@@ -81,8 +81,8 @@
// }
.videoBox-ui-wrapper {
- width: 0;
- height: 0;
+ width: 0px;
+ height: 0px;
position: relative;
z-index: 2000;
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b3cb0e1db..f994bdbb5 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -30,6 +30,7 @@ import { StyleProp } from '../StyleProp';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
+import { gptImageLabel } from '../../apis/gpt/GPT';
import './VideoBox.scss';
/**
@@ -109,6 +110,52 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return this._videoRef;
}
+ autoTag = async () => {
+ if (this.Document.$tags_chat) return;
+ try {
+ if (!this.player) throw new Error('Video element not available.');
+
+ // 1) Extract a frame at the video's midpoint
+ const videoDuration = this.player.duration;
+ const snapshotTime = videoDuration / 2;
+
+ // Seek the video element to the midpoint
+ await new Promise<void>(resolve => {
+ const onSeeked = () => {
+ this.player!.removeEventListener('seeked', onSeeked);
+ resolve();
+ };
+ this.player!.addEventListener('seeked', onSeeked);
+ this.player!.currentTime = snapshotTime;
+ });
+
+ // 2) Draw the frame onto a canvas and get a base64 representation
+ const canvas = document.createElement('canvas');
+ canvas.width = this.player.videoWidth;
+ canvas.height = this.player.videoHeight;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) throw new Error('Failed to create canvas context.');
+ ctx.drawImage(this.player, 0, 0, canvas.width, canvas.height);
+ const base64Image = canvas.toDataURL('image/png');
+
+ // 3) Send the image data to GPT for classification and descriptive tags
+ const label = await gptImageLabel(
+ base64Image,
+ `Classify this video frame as either a PERSON or LANDSCAPE.
+ Then provide five additional descriptive tags (single words) separated by spaces.
+ Finally, add one detailed summary phrase using underscores.`
+ ).then(raw => raw.trim().toUpperCase());
+
+ // 4) Normalize and store labels in the Document's tags
+ const aspect = this.player!.videoWidth / (this.player!.videoHeight || 1);
+ this.Document.$tags_chat = new List<string>([...label.split(/\s+/), `ASPECT_${aspect}`]);
+ // 5) Turn on tag display in layout
+ this.Document._layout_showTags = true;
+ } catch (err) {
+ console.error('Video autoTag failed:', err);
+ }
+ };
+
componentDidMount() {
this.unmounting = false;
this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 05d5babf9..e7c9cf095 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -3,8 +3,8 @@
.webBox {
height: 100%;
width: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
position: relative;
display: flex;
overflow: hidden;
@@ -28,8 +28,8 @@
height: 100%;
z-index: 1;
pointer-events: none;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
overflow: hidden;
.webBox-overlayButton {
@@ -39,16 +39,16 @@
align-items: center;
height: 20px;
background: none;
- padding: 0;
+ padding: 0px;
position: absolute;
pointer-events: all;
color: white;
- bottom: 0;
- right: 0;
+ bottom: 0px;
+ right: 0px;
.webBox-overlayButton-arrow {
- width: 0;
- height: 0;
+ width: 0px;
+ height: 0px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 15px solid #121721;
@@ -92,7 +92,7 @@
width: calc(100% - 40px);
height: 20px;
background: #121721;
- bottom: 0;
+ bottom: 0px;
display: flex;
justify-content: center;
align-items: center;
@@ -137,7 +137,7 @@
.webBox-annotationLayer {
position: absolute;
transform-origin: left top;
- top: 0;
+ top: 0px;
width: 100%;
pointer-events: none;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
@@ -156,8 +156,8 @@
.webBox-htmlSpan {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
cursor: text;
padding: 15px;
height: 100%;
@@ -171,8 +171,8 @@
.webBox-cont-interactive {
padding: 0vw;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
transform-origin: top left;
@@ -181,8 +181,8 @@
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
body {
::selection {
color: white;
@@ -203,8 +203,8 @@
height: 100%;
position: absolute;
transform-origin: top left;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
overflow: auto;
.webBox-innerContent {
@@ -224,7 +224,7 @@
}
.webBox-buttons {
- margin-left: 44;
+ margin-left: 44px;
background: lightGray;
width: 100%;
}
@@ -232,8 +232,8 @@
.webBox-annotationToggle {
z-index: 901;
position: absolute;
- top: 2;
- left: 2;
+ top: 2px;
+ left: 2px;
cursor: pointer;
box-shadow: black 0.3em 0.3em 1em;
border-radius: 5px;
diff --git a/src/client/views/nodes/audio/AudioWaveform.scss b/src/client/views/nodes/audio/AudioWaveform.scss
index 6cbd1759a..c6b0da9c8 100644
--- a/src/client/views/nodes/audio/AudioWaveform.scss
+++ b/src/client/views/nodes/audio/AudioWaveform.scss
@@ -1,17 +1,17 @@
-.audioWaveform {
+.audioWaveform {
position: relative;
width: 100%;
height: 200%;
overflow: hidden;
z-index: -1000;
- bottom: 0;
+ bottom: 0px;
pointer-events: none;
div {
height: 100% !important;
- width: 100% !important;
+ width: 100% !important;
}
- canvas {
+ canvas {
height: 100% !important;
- width: 100% !important;
+ width: 100% !important;
}
}
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
index 3d27fa887..4db5cec3d 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
@@ -28,7 +28,7 @@ $transition: all 0.2s ease-in-out;
box-shadow: 0 1px 4px $shadow-color;
h2 {
- margin: 0;
+ margin: 0px;
font-size: 1.5em;
font-weight: 500;
}
@@ -126,7 +126,7 @@ $transition: all 0.2s ease-in-out;
animation: fadeIn 0.3s ease-in-out;
p {
- margin: 0;
+ margin: 0px;
font-size: 14px;
}
@@ -263,10 +263,10 @@ $transition: all 0.2s ease-in-out;
.uploading-overlay {
position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
@@ -285,7 +285,7 @@ $transition: all 0.2s ease-in-out;
@media (max-width: 768px) {
.chat-box {
- border-radius: 0;
+ border-radius: 0px;
}
.message {
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss
index ff5be4a38..77d452830 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ProgressBar.scss
@@ -21,8 +21,8 @@
background-color: #4a90e2;
opacity: 0.6;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
animation: bounce 2s infinite ease-in-out;
}
@@ -42,10 +42,10 @@
.uploading-overlay {
position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 3734ad9cc..1b2f76bbe 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -17,7 +17,7 @@
min-width: 12px;
position: relative;
display: inline-block;
- margin: 0;
+ margin: 0px;
transform: scale(0.7);
background-color: rgba(155, 155, 155, 0.24);
}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.scss b/src/client/views/nodes/formattedText/EquationEditor.scss
index b0c17e56e..602135a30 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.scss
+++ b/src/client/views/nodes/formattedText/EquationEditor.scss
@@ -32,7 +32,7 @@
margin-left: -1px;
position: relative;
z-index: 1;
- padding: 0;
+ padding: 0px;
display: -moz-inline-box;
display: inline-block;
}
@@ -128,8 +128,8 @@
.mq-math-mode * {
font-size: inherit;
line-height: inherit;
- margin: 0;
- padding: 0;
+ margin: 0px;
+ padding: 0px;
border-color: black;
-webkit-user-select: none;
-moz-user-select: none;
@@ -178,7 +178,7 @@
margin-left: 0.1em;
}
.mq-math-mode .mq-roman var.mq-f {
- margin: 0;
+ margin: 0px;
}
.mq-math-mode big {
font-size: 200%;
@@ -323,7 +323,7 @@
padding: 0.1em;
}
.mq-math-mode .mq-sqrt-prefix {
- padding-top: 0;
+ padding-top: 0px;
position: relative;
top: 0.1em;
vertical-align: top;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 547a2efa8..d5e566226 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -46,7 +46,7 @@
}
audiotag {
- left: 0;
+ left: 0px;
position: absolute;
cursor: pointer;
border-radius: 10px;
@@ -62,7 +62,7 @@ audiotag:hover {
.formattedTextBox {
touch-action: none;
background: inherit;
- padding: 0;
+ padding: 0px;
border-width: 0px;
border-color: global.$medium-gray;
box-sizing: border-box;
@@ -77,14 +77,14 @@ audiotag:hover {
width: 100%;
position: relative;
transform-origin: left top;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
.formattedTextBox-cont {
touch-action: none;
background: inherit;
- padding: 0;
+ padding: 0px;
border-width: 0px;
border-radius: inherit;
border-color: global.$medium-gray;
@@ -111,7 +111,7 @@ audiotag:hover {
.answer-tooltip {
font-size: 15px;
padding: 2px;
- max-width: 150;
+ max-width: 150px;
line-height: 150%;
position: relative;
}
@@ -122,10 +122,10 @@ audiotag:hover {
position: absolute;
color: white;
background: black;
- right: 0;
- bottom: 0;
- width: 15;
- height: 22;
+ right: 0px;
+ bottom: 0px;
+ width: 15px;
+ height: 22px;
cursor: default;
}
@@ -139,8 +139,8 @@ audiotag:hover {
.formattedTextBox-sidebar-handle {
position: absolute;
- top: 0;
- right: 0;
+ top: 0px;
+ right: 0px;
width: 20px;
height: 20px;
font-size: 11px;
@@ -168,7 +168,7 @@ audiotag:hover {
height: 100%;
display: inline-block;
position: absolute;
- right: 0;
+ right: 0px;
overflow: hidden;
.collectionfreeformview-container {
@@ -302,8 +302,8 @@ footnote::before {
position: absolute;
top: -0.5em;
content: ' ';
- height: 0;
- width: 0;
+ height: 0px;
+ width: 0px;
}
.formattedTextBox-inlineComment {
@@ -346,7 +346,7 @@ footnote::before {
.prosemirror-linkBtn {
background: unset;
color: unset;
- padding: 0;
+ padding: 0px;
text-transform: unset;
letter-spacing: unset;
font-size: unset;
@@ -357,7 +357,7 @@ footnote::before {
background-color: dimgray;
margin-top: 1.5em;
z-index: 1;
- padding: 5;
+ padding: 5px;
border-radius: 2px;
}
.prosemirror-hrefoptions {
@@ -396,7 +396,7 @@ footnote::before {
blockquote {
padding: 10px 10px;
font-size: smaller;
- margin: 0;
+ margin: 0px;
font-style: italic;
background: lightgray;
border-left: solid 2px dimgray;
@@ -415,7 +415,7 @@ footnote::before {
p {
font-family: inherit;
}
- margin-left: 0;
+ margin-left: 0px;
}
.bullet1 {
p {
@@ -439,7 +439,7 @@ footnote::before {
display: inline-block;
font-family: inherit;
}
- margin-left: 0;
+ margin-left: 0px;
background-color: inherit;
}
.decimal2-ol {
@@ -506,7 +506,7 @@ footnote::before {
display: inline-block;
font-family: inherit;
}
- margin-left: 0;
+ margin-left: 0px;
padding-left: 1.2em;
background-color: inherit;
}
@@ -661,7 +661,7 @@ footnote::before {
.formattedTextBox-cont {
touch-action: none;
background: inherit;
- padding: 0;
+ padding: 0px;
border-width: 0px;
border-radius: inherit;
border-color: global.$medium-gray;
@@ -706,7 +706,7 @@ footnote::before {
height: 100%;
display: inline-block;
position: absolute;
- right: 0;
+ right: 0px;
.collectionfreeformview-container {
position: relative;
@@ -832,8 +832,8 @@ footnote::before {
position: absolute;
top: -0.5em;
content: ' ';
- height: 0;
- width: 0;
+ height: px;
+ width: 0px;
}
.formattedTextBox-inlineComment {
@@ -892,7 +892,7 @@ footnote::before {
display: inline;
font-family: inherit;
}
- margin-left: 0;
+ margin-left: 0px;
}
.decimal2-ol {
counter-reset: deci2;
@@ -952,7 +952,7 @@ footnote::before {
display: inline;
font-family: inherit;
}
- margin-left: 0;
+ margin-left: 0px;
padding-left: 1.2em;
}
.multi2-ol {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c8df6e50f..07cb795f1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView, NodeViewConstructor } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
@@ -308,6 +308,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
};
+ autoTag = () => {
+ const rawText = RTFCast(this.Document[this.fieldKey])?.Text ?? StrCast(this.Document[this.fieldKey]);
+ if (rawText && !this.Document.$tags_chat) {
+ const callType = rawText.includes('[placeholder]') ? GPTCallType.CLASSIFYTEXTMINIMAL : GPTCallType.CLASSIFYTEXTFULL;
+
+ gptAPICall(rawText, callType).then(
+ action(desc => {
+ // Split GPT response into tokens and push individually & clear existing tags
+ this.Document.$tags_chat = new List<string>(desc.trim().split(/\s+/));
+ this.Document._layout_showTags = true;
+ })
+ );
+ }
+ };
+
leafText = (node: Node) => {
if (node.type === this.EditorView?.state.schema.nodes.dashField) {
const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
@@ -369,6 +384,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
dataDoc[this.fieldKey] =
numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined;
textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
+ if (textChange) this.dataDoc.$tags_chat = undefined;
this.ApplyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
unchanged = false;
}
@@ -1072,6 +1088,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return anchorDoc ?? this.Document;
}
+ showBorderRounding = returnTrue;
getView = (doc: Doc, options: FocusViewOptions) => {
if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index bc0810f22..92f3e3290 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -11,8 +11,8 @@
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
box-shadow: 3px 3px 1.5px grey;
- max-width: 400;
- max-height: 235;
+ max-width: 400px;
+ max-height: 235px;
height: max-content;
.formattedTextBox-tooltipText {
height: max-content;
@@ -22,26 +22,26 @@
.formattedTextBox-tooltip:before {
content: '';
- height: 0;
- width: 0;
+ height: 0px;
+ width: 0px;
position: absolute;
left: 50%;
margin-left: -5px;
bottom: -6px;
border: 5px solid transparent;
- border-bottom-width: 0;
+ border-bottom-width: 0px;
border-top-color: silver;
}
.formattedTextBox-tooltip:after {
content: '';
- height: 0;
- width: 0;
+ height: 0px;
+ width: 0px;
position: absolute;
left: 50%;
margin-left: -5px;
bottom: -4.5px;
border: 5px solid transparent;
- border-bottom-width: 0;
+ border-bottom-width: 0px;
border-top-color: white;
}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index fcc816447..7c747de1e 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -23,7 +23,7 @@
.dropdown {
position: absolute;
top: 35px;
- left: 0;
+ left: 0px;
background-color: #323232;
color: global.$light-gray;
border: 1px solid #4d4d4d;
@@ -47,7 +47,7 @@
}
&:last-child {
- margin-bottom: 0;
+ margin-bottom: 0px;
}
}
}
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index 87320943d..8980a93a2 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -195,7 +195,7 @@
left: 1px;
width: 24px;
height: 4px;
- margin-top: 0;
+ margin-top: 0px;
}
}
@@ -221,7 +221,7 @@
display: inline-block;
width: 1em;
height: 1em;
- stroke-width: 0;
+ stroke-width: 0px;
stroke: currentColor;
fill: currentColor;
margin-right: 15px;
@@ -231,7 +231,7 @@
display: inline-block;
width: 1em;
height: 1em;
- stroke-width: 3;
+ stroke-width: 3px;
fill: greenyellow;
margin-right: 15px;
}
@@ -270,7 +270,7 @@
&.ProseMirror-menu-dropdown {
width: 10px;
height: 25px;
- margin: 0;
+ margin: 0px;
padding: 0 2px;
background-color: #323232;
text-align: center;
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss
index c691e6a18..942a7d4c6 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.scss
+++ b/src/client/views/nodes/imageEditor/ImageEditor.scss
@@ -4,8 +4,8 @@ $scale: 0.5;
.imageEditorContainer {
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 9999;
height: 100vh;
width: 100vw;
@@ -78,7 +78,7 @@ $scale: 0.5;
.sideControlsContainer {
width: 160px;
position: absolute;
- left: 0;
+ left: 0px;
height: 100%;
.sideControls {
@@ -129,8 +129,8 @@ $scale: 0.5;
.originalImageLabel {
position: absolute;
- bottom: 10;
- left: 10;
+ bottom: 10px;
+ left: 10px;
color: #ffffff;
font-size: 0.8rem;
letter-spacing: 1px;
diff --git a/src/client/views/nodes/scrapbook/AIPresetGenerator.ts b/src/client/views/nodes/scrapbook/AIPresetGenerator.ts
new file mode 100644
index 000000000..1f159222b
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/AIPresetGenerator.ts
@@ -0,0 +1,31 @@
+import { ScrapbookItemConfig } from './ScrapbookPreset';
+import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
+
+// Represents the descriptor for each document
+export interface DocumentDescriptor {
+ type: string;
+ tags: string[];
+}
+
+// Main function to request AI-generated presets
+export async function requestAiGeneratedPreset(descriptors: DocumentDescriptor[]): Promise<ScrapbookItemConfig[]> {
+ const prompt = createPrompt(descriptors);
+ let aiResponse = await gptAPICall(prompt, GPTCallType.GENERATESCRAPBOOK);
+ // Strip out ```json and ``` if the model wrapped its answer in fences
+ aiResponse = aiResponse
+ .trim()
+ .replace(/^```(?:json)?\s*/, "") // remove leading ``` or ```json
+ .replace(/\s*```$/, ""); // remove trailing ```
+ const parsedPreset = JSON.parse(aiResponse) as ScrapbookItemConfig[];
+ return parsedPreset;
+}
+
+// Helper to generate prompt text for AI
+function createPrompt(descriptors: DocumentDescriptor[]): string {
+ let prompt = "";
+ descriptors.forEach((desc, index) => {
+ prompt += `${index + 1}. Type: ${desc.type}, Tags: ${desc.tags.join(', ')}\n`;
+ });
+
+ return prompt;
+}
diff --git a/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx b/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx
deleted file mode 100644
index e99bf67c7..000000000
--- a/src/client/views/nodes/scrapbook/EmbeddedDocView.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION
-import * as React from "react";
-import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
-import { DocumentView } from "../DocumentView";
-import { Transform } from "../../../util/Transform";
-
-interface EmbeddedDocViewProps {
- doc: Doc;
- width?: number;
- height?: number;
- slotId?: string;
-}
-
-@observer
-export class EmbeddedDocView extends React.Component<EmbeddedDocViewProps> {
- render() {
- const { doc, width = 300, height = 200, slotId } = this.props;
-
- // Use either an existing embedding or create one
- let docToDisplay = doc;
-
- // If we need an embedding, create or use one
- if (!docToDisplay.isEmbedding) {
- docToDisplay = Doc.BestEmbedding(doc) || Doc.MakeEmbedding(doc);
- // Set the container to the slot's ID so we can track it
- if (slotId) {
- docToDisplay.embedContainer = `scrapbook-slot-${slotId}`;
- }
- }
-
- return (
- <DocumentView
- Document={docToDisplay}
- renderDepth={0}
- // Required sizing functions
- NativeWidth={() => width}
- NativeHeight={() => height}
- PanelWidth={() => width}
- PanelHeight={() => height}
- // Required state functions
- isContentActive={() => true}
- childFilters={() => []}
- ScreenToLocalTransform={() => new Transform()}
- // Display options
- hideDeleteButton={true}
- hideDecorations={true}
- hideResizeHandles={true}
- />
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.scss b/src/client/views/nodes/scrapbook/ScrapbookBox.scss
new file mode 100644
index 000000000..6ac2220f9
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.scss
@@ -0,0 +1,66 @@
+.scrapbook-box {
+ /* Make sure the container fills its parent, and set a base background */
+ position: relative; /* so that absolute children (loading overlay, etc.) are positioned relative to this */
+ width: 100%;
+ height: 100%;
+ background: beige;
+ overflow: hidden; /* prevent scrollbars if children overflow */
+}
+
+/* Loading overlay that covers the entire scrapbook while AI-generation is in progress */
+.scrapbook-box-loading-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: rgba(255, 255, 255, 0.8);
+ z-index: 10; /* sits above the ImageBox and other content */
+}
+
+/* The <select> dropdown for choosing presets */
+.scrapbook-box-preset-select {
+ position: relative;
+ top: 8px;
+ left: 8px;
+ z-index: 20;
+ padding: 4px 8px;
+ font-size: 14px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background: white;
+}
+
+/* Container for the “Regenerate Background” button */
+.scrapbook-box-ui {
+ position: relative;
+ top: 8px;
+ right: 8px;
+ z-index: 20;
+ background: white;
+ width: 40px;
+ display: flex;
+ justify-content: center;
+}
+
+/* The button itself */
+.scrapbook-box-ui-button {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 8px;
+ font-size: 14px;
+ color: black;
+ background: white;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.scrapbook-box-ui-button:hover {
+ background: #f5f5f5;
+}
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
index 6cfe9a62c..d0ae6194f 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -1,130 +1,260 @@
-import { action, makeObservable, observable } from 'mobx';
+import { IconButton, Size } from '@dash/components';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../../fields/Doc';
+import ReactLoading from 'react-loading';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
+import { DateCast, DocCast, NumCast, toList } from '../../../../fields/Types';
import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { undoable } from '../../../util/UndoManager';
import { CollectionView } from '../../collections/CollectionView';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { AspectRatioLimits, FireflyImageDimensions } from '../../smartdraw/FireflyConstants';
+import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
-import { DragManager } from '../../../util/DragManager';
-import { RTFCast, StrCast, toList } from '../../../../fields/Types';
-import { undoable } from '../../../util/UndoManager';
-// Scrapbook view: a container that lays out its child items in a grid/template
-export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
- @observable createdDate: string;
+import { ImageBox } from '../ImageBox';
+import './ScrapbookBox.scss';
+import { ScrapbookItemConfig } from './ScrapbookPreset';
+import { createPreset, getPresetNames } from './ScrapbookPresetRegistry';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { DocUtils } from '../../../documents/DocUtils';
+import { returnTrue } from '../../../../ClientUtils';
- constructor(props: FieldViewProps) {
- super(props);
- makeObservable(this);
- this.createdDate = this.getFormattedDate();
+function createPlaceholder(cfg: ScrapbookItemConfig, doc: Doc) {
+ const placeholder = new Doc();
+ placeholder.proto = doc;
+ placeholder.original = doc;
+ placeholder.x = cfg.x;
+ placeholder.y = cfg.y;
+ if (cfg.width !== null) placeholder._width = cfg.width;
+ if (cfg.height !== null) placeholder._height = cfg.height;
+ return placeholder;
+}
- // ensure we always have a List<Doc> in dataDoc['items']
- if (!this.dataDoc[this.fieldKey]) {
- this.dataDoc[this.fieldKey] = new List<Doc>();
+function createMessagePlaceholder(cfg: ScrapbookItemConfig) {
+ return createPlaceholder(cfg,
+ Docs.Create.TextDocument(cfg.message ?? ('[placeholder] ' + cfg.acceptTags?.[0]), { placeholder: "", placeholder_docType: cfg.type, placeholder_acceptTags: new List<string>(cfg.acceptTags) })
+ ); // prettier-ignore
+}
+export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]) {
+ return configs.map(cfg => {
+ if (cfg.children?.length) {
+ const childDocs = cfg.children.map(createMessagePlaceholder);
+ const protoW = cfg.containerWidth ?? cfg.width;
+ const protoH = cfg.containerHeight ?? cfg.height;
+ // Create a stacking document with the child placeholders
+ const containerProto = Docs.Create.StackingDocument(childDocs, {
+ ...(protoW !== null ? { _width: protoW } : {}),
+ ...(protoH !== null ? { _height: protoH } : {}),
+ title: cfg.message,
+ });
+ return createPlaceholder(cfg, containerProto);
}
- this.createdDate = this.getFormattedDate();
- this.setTitle();
+ return createMessagePlaceholder(cfg);
+ });
+}
+export async function slotRealDocIntoPlaceholders(realDoc: Doc, placeholders: Doc[]) {
+ if (!realDoc.$tags_chart) {
+ await DocumentView.getFirstDocumentView(realDoc)?.ComponentView?.autoTag?.();
}
+ const realTags = new Set<string>(StrListCast(realDoc.$tags_chat).map(t => t.toLowerCase?.() ?? ''));
+ // Find placeholder with most matching tags
+ let bestMatch: Doc | null = null;
+ let maxMatches = 0;
+
+ // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
+ placeholders
+ .filter(ph => ph.placeholder_docType === realDoc.$type) // Skip this placeholder entirely if types do not match.
+ .forEach(ph => {
+ const matches = StrListCast(ph.placeholder_acceptTags)
+ .map(t => t.toLowerCase?.())
+ .filter(tag => realTags.has(tag));
+
+ if (matches.length > maxMatches) {
+ maxMatches = matches.length;
+ bestMatch = ph;
+ }
+ });
+
+ if (bestMatch && maxMatches > 0) {
+ setTimeout(undoable(() => (bestMatch!.proto = realDoc), 'Scrapbook add'));
+ return true;
+ }
+
+ return false;
+}
+
+// Scrapbook view: a container that lays out its child items in a template
+@observer
+export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(ScrapbookBox, fieldStr);
}
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _imageBoxRef = React.createRef<ImageBox>();
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @observable _selectedPreset = getPresetNames()[0];
+ @observable _loading = false;
- getFormattedDate(): string {
- return new Date().toLocaleDateString(undefined, {
+ @computed get createdDate() {
+ return DateCast(this.dataDoc.$author_date)?.date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
});
}
+ @computed get ScrapbookLayoutDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } // prettier-ignore
+ @computed get BackgroundDoc() { return DocCast(this.dataDoc[this.fieldKey + '_background']); } // prettier-ignore
+ set ScrapbookLayoutDocs(doc: Doc[]) { this.dataDoc[this.fieldKey] = new List(doc); } // prettier-ignore
+ set BackgroundDoc(doc: Opt<Doc>) { this.dataDoc[this.fieldKey + '_background'] = doc; } // prettier-ignore
@action
- setTitle() {
- const title = `Scrapbook - ${this.createdDate}`;
- if (this.dataDoc.title !== title) {
- this.dataDoc.title = title;
-
- const image = Docs.Create.TextDocument('image');
- image.accepts_docType = DocumentType.IMG;
- const placeholder = new Doc();
- placeholder.proto = image;
- placeholder.original = image;
- placeholder._width = 250;
- placeholder._height = 200;
- placeholder.x = 0;
- placeholder.y = -100;
- //placeholder.overrideFields = new List<string>(['x', 'y']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos
-
- const summary = Docs.Create.TextDocument('summary');
- summary.accepts_docType = DocumentType.RTF;
- summary.accepts_textType = 'one line';
- const placeholder2 = new Doc();
- placeholder2.proto = summary;
- placeholder2.original = summary;
- placeholder2.x = 0;
- placeholder2.y = 200;
- placeholder2._width = 250;
- //placeholder2.overrideFields = new List<string>(['x', 'y', '_width']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos
- this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2]);
- }
- }
+ setDefaultPlaceholder = () => {
+ this.ScrapbookLayoutDocs = [
+ createMessagePlaceholder({
+ message: 'To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.',
+ type: DocumentType.RTF,
+ width: 250,
+ height: 200,
+ x: 0,
+ y: 0,
+ }),
+ ];
+
+ const placeholder1 = createMessagePlaceholder({ acceptTags: ['PERSON'], type: DocumentType.IMG, width: 250, height: 200, x: 0, y: -100 });
+ const placeholder2 = createMessagePlaceholder({ acceptTags: ['lengthy description'], type: DocumentType.RTF, width: 250, height: undefined, x: 0, y: 200 });
+ const placeholder3 = createMessagePlaceholder({ acceptTags: ['title'], type: DocumentType.RTF, width: 50, height: 200, x: 280, y: -50 });
+ const placeholder4 = createPlaceholder( { width: 100, height: 200, x: -200, y: -100 }, Docs.Create.StackingDocument([
+ createMessagePlaceholder({ acceptTags: ['LANDSCAPE'], type: DocumentType.IMG, width: 50, height: 100, x: 0, y: -100 })
+ ], { _width: 300, _height: 300, title: 'internal coll' })); // prettier-ignore
+ console.log('UNUSED', placeholder4, placeholder3, placeholder2, placeholder1);
+ /* note-to-self
+ would doing:
+ const collection = Docs.Create.ScrapbookDocument([placeholder, placeholder2, placeholder3]);
+ create issues with references to the same object? */
+
+ /*note-to-self
+ Should we consider that there are more collections than just COL type collections?
+ when spreading */
+
+ /*note-to-self
+ difference between passing a new List<Doc> versus just the raw array? */
+ };
+
+ selectPreset = action((presetName: string) => (this.ScrapbookLayoutDocs = buildPlaceholdersFromConfigs(createPreset(presetName))));
componentDidMount() {
- this.setTitle();
+ const title = `Scrapbook - ${this.createdDate}`;
+ if (!this.ScrapbookLayoutDocs.length) this.setDefaultPlaceholder();
+ if (!this.BackgroundDoc) this.generateAiImage(this.regenPrompt);
+ if (this.dataDoc.title !== title) this.dataDoc.title = title; // ensure title is set
+
+ this._disposers.propagateResize = reaction(
+ () => ({ w: this.layoutDoc._width, h: this.layoutDoc._height }),
+ (dims, prev) => {
+ const imageBox = this._imageBoxRef.current;
+ // prev is undefined on the first run
+ if (prev && SnappingManager.ShiftKey && this.BackgroundDoc && imageBox) {
+ this.BackgroundDoc[imageBox.fieldKey + '_outpaintOriginalWidth'] = prev.w;
+ this.BackgroundDoc[imageBox.fieldKey + '_outpaintOriginalHeight'] = prev.h;
+ imageBox.layoutDoc._width = dims.w;
+ imageBox.layoutDoc._height = dims.h;
+ }
+ }
+ );
}
- childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
- return true; // disable dropping documents onto any child of the scrapbook.
- };
- rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
- // Test to see if the dropped doc is dropped on an acceptable location (anywerhe? on a specific box).
- // const draggedDocs = de.complete.docDragData?.draggedDocuments;
- return false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision.
+ isOutpaintable = returnTrue;
+ showBorderRounding = returnTrue;
+
+ @action
+ generateAiImage = (prompt: string) => {
+ this._loading = true;
+
+ const ratio = NumCast(this.layoutDoc._width, 1) / NumCast(this.layoutDoc._height, 1); // Measure the scrapbook’s current aspect
+ const choosePresetForDimensions = (() => { // Pick the Firefly preset that best matches the aspect ratio
+ if (ratio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) return FireflyImageDimensions.Widescreen;
+ if (ratio > AspectRatioLimits[FireflyImageDimensions.Landscape]) return FireflyImageDimensions.Landscape;
+ if (ratio < AspectRatioLimits[FireflyImageDimensions.Portrait]) return FireflyImageDimensions.Portrait;
+ return FireflyImageDimensions.Square;
+ })(); // prettier-ignore
+
+ SmartDrawHandler.CreateWithFirefly(prompt, choosePresetForDimensions) // Call exactly the same CreateWithFirefly that ImageBox uses
+ .then(action(doc => {
+ if (doc instanceof Doc) {
+ this.BackgroundDoc = doc; // set the background image directly on the scrapbook
+ } else {
+ alert('Failed to generate document.');
+ }
+ }))
+ .catch(e => alert(`Generation error: ${e}`))
+ .finally(action(() => (this._loading = false))); // prettier-ignore
};
- filterAddDocument = (docIn: Doc | Doc[]) => {
- const docs = toList(docIn);
- if (docs?.length === 1) {
- const placeholder = DocListCast(this.dataDoc[this.fieldKey]).find(d =>
- (d.accepts_docType === docs[0].$type || // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
- RTFCast(d[Doc.LayoutDataKey(d)])?.Text.includes(StrCast(docs[0].$type)))
- ); // prettier-ignore
-
- if (placeholder) {
- // ugh. we have to tell the underlying view not to add the Doc so that we can add it where we want it.
- // However, returning 'false' triggers an undo. so this settimeout is needed to make the assignment happen after the undo.
- setTimeout(
- undoable(() => {
- //StrListCast(placeholder.overrideFields).map(field => (docs[0][field] = placeholder[field])); // // shouldn't need to do this for layout fields since the placeholder already overrides its protos
- placeholder.proto = docs[0];
- }, 'Scrapbook add')
- );
- return false;
- }
- }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => true; // disable dropping documents onto any child of the scrapbook.
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision.
+
+ /**
+ * Filter function to determine if a document can be added to the scrapbook.
+ * This checks if the document matches any of the placeholder slots in the scrapbook.
+ * @param docs - The document(s) being added to the scrapbook.
+ * @returns true if the document can be added, false otherwise.
+ */
+ filterAddDocument = (docs: Doc | Doc[]) => {
+ toList(docs).forEach(doc => slotRealDocIntoPlaceholders(doc, DocUtils.unwrapPlaceholders(this.ScrapbookLayoutDocs)));
return false;
};
+ @computed get regenPrompt() {
+ const allDocs = DocUtils.unwrapPlaceholders(this.ScrapbookLayoutDocs); // find all non-collections in scrapbook (e.g., placeholder content docs)
+ const internalTagsSet = new Set<string>(allDocs.flatMap(doc => StrListCast(doc.$tags_chat).filter(tag => !tag.startsWith?.('ASPECT_'))));
+ const internalTags = Array.from(internalTagsSet).join(', ');
+
+ return internalTags ? `Create a new scrapbook background featuring: ${internalTags}` : 'A serene mountain landscape at sunrise, ultra-wide, pastel sky, abstract, scrapbook background';
+ }
+
render() {
return (
- <div style={{ background: 'beige', width: '100%', height: '100%' }}>
- <CollectionView
- {...this._props} //
- setContentViewBox={emptyFunction}
- rejectDrop={this.rejectDrop}
- childRejectDrop={this.childRejectDrop}
- filterAddDocument={this.filterAddDocument}
- />
- {/* <div style={{ border: '1px black', borderStyle: 'dotted', position: 'absolute', top: '50%', width: '100%', textAlign: 'center' }}>Drop an image here</div> */}
+ <div className="scrapbook-box">
+ <div style={{ display: this._loading ? undefined : 'none' }} className="scrapbook-box-loading-overlay">
+ <ReactLoading type="spin" width={50} height={50} />
+ </div>
+
+ {this.BackgroundDoc && <ImageBox ref={this._imageBoxRef} {...this._props} Document={this.BackgroundDoc} fieldKey="data" />}
+ <div style={{ display: this._props.isContentActive() ? 'flex' : 'none', alignItems: 'center', justifyContent: 'space-between', padding: '0 10px' }}>
+ <select className="scrapbook-box-preset-select" value={this._selectedPreset} onChange={action(e => this.selectPreset((this._selectedPreset = e.currentTarget.value)))}>
+ {getPresetNames().map(name => (
+ <option key={name} value={name}>
+ {name}
+ </option>
+ ))}
+ </select>
+ <div className="scrapbook-box-ui" style={{ opacity: this._loading ? 0.5 : 1 }}>
+ <IconButton size={Size.SMALL} tooltip="regenerate a new background" label="back-ground" icon={<FontAwesomeIcon icon="redo-alt" size="sm" />} onClick={() => !this._loading && this.generateAiImage(this.regenPrompt)} />
+ </div>
+ </div>
+
+ <CollectionView {...this._props} setContentViewBox={emptyFunction} rejectDrop={this.rejectDrop} childRejectDrop={this.childRejectDrop} filterAddDocument={this.filterAddDocument} />
</div>
);
}
}
-// Register scrapbook
Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
layout: { view: ScrapbookBox, dataField: 'items' },
options: {
diff --git a/src/client/views/nodes/scrapbook/ScrapbookContent.tsx b/src/client/views/nodes/scrapbook/ScrapbookContent.tsx
deleted file mode 100644
index ad1d308e8..000000000
--- a/src/client/views/nodes/scrapbook/ScrapbookContent.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from "react";
-import { observer } from "mobx-react-lite";
-// Import the Doc type from your actual module.
-import { Doc } from "../../../../fields/Doc";
-
-export interface ScrapbookContentProps {
- doc: Doc;
-}
-
-// A simple view that displays a document's title and content.
-// Adjust how you extract the text if your Doc fields are objects.
-export const ScrapbookContent: React.FC<ScrapbookContentProps> = observer(({ doc }) => {
- // If doc.title or doc.content are not plain strings, convert them.
- const titleText = doc.title ? doc.title.toString() : "Untitled";
- const contentText = doc.content ? doc.content.toString() : "No content available.";
-
- return (
- <div className="scrapbook-content">
- <h3>{titleText}</h3>
- <p>{contentText}</p>
- </div>
- );
-});
diff --git a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx
new file mode 100644
index 000000000..a3405083b
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx
@@ -0,0 +1,94 @@
+import { DocumentType } from '../../../documents/DocumentTypes';
+
+export enum ScrapbookPresetType {
+ None = 'None',
+ Collage = 'Collage',
+ Spotlight = 'Spotlight',
+ Gallery = 'Gallery',
+ Default = 'Default',
+ Classic = 'Classic',
+}
+
+export interface ScrapbookItemConfig {
+ x: number;
+ y: number;
+
+ message?: string; // optional text to display instead of [placeholder] + acceptTags[0]
+ type?: DocumentType;
+ /** what this slot actually accepts (defaults to `tag`) */
+ acceptTags?: string[];
+ /** the frame this placeholder occupies */
+ width?: number;
+ height?: number;
+ /** if this is a container with children, use these for the proto’s own size */
+ containerWidth?: number;
+ containerHeight?: number;
+ children?: ScrapbookItemConfig[];
+}
+
+export class ScrapbookPreset {
+ static createPreset(presetType: ScrapbookPresetType): ScrapbookItemConfig[] {
+ switch (presetType) {
+ case ScrapbookPresetType.None: return ScrapbookPreset.createNonePreset();
+ case ScrapbookPresetType.Classic: return ScrapbookPreset.createClassicPreset();
+ case ScrapbookPresetType.Collage: return ScrapbookPreset.createCollagePreset();
+ case ScrapbookPresetType.Spotlight: return ScrapbookPreset.createSpotlightPreset();
+ case ScrapbookPresetType.Default: return ScrapbookPreset.createDefaultPreset();
+ case ScrapbookPresetType.Gallery: return ScrapbookPreset.createGalleryPreset();
+ default:
+ throw new Error(`Unknown preset type: ${presetType}`);
+ } // prettier-ignore
+ }
+
+ private static createNonePreset(): ScrapbookItemConfig[] {
+ return [{ message: 'To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.', type: DocumentType.RTF, acceptTags: [], x: 0, y: 0, width: 250, height: 200 }];
+ }
+
+ private static createClassicPreset(): ScrapbookItemConfig[] {
+ return [
+ { type: DocumentType.IMG, message: '[placeholder] landscape', acceptTags: ['LANDSCAPE'], x: 0, y: -100, width: 250, height: 200 },
+ { type: DocumentType.RTF, message: '[placeholder] lengthy caption', acceptTags: ['paragraphs'], x: 0, y: 138, width: 250, height: 172 },
+ { type: DocumentType.RTF, message: '[placeholder] brief description', acceptTags: ['sentence'], x: 280, y: -50, width: 50, height: 200 },
+ { type: DocumentType.IMG, message: '[placeholder] person', acceptTags: ['PERSON'], x: -200, y: -100, width: 167, height: 200 },
+ ];
+ }
+
+ private static createGalleryPreset(): ScrapbookItemConfig[] {
+ return [
+ { type: DocumentType.IMG, message: 'Gallery 1 <drop person images into the gallery!>', acceptTags: ['PERSON'], x: -150, y: -150, width: 150, height: 150 },
+ { type: DocumentType.IMG, message: 'Gallery 2', acceptTags: ['PERSON'], x: 0, y: -150, width: 150, height: 150 },
+ { type: DocumentType.IMG, message: 'Gallery 3', acceptTags: ['PERSON'], x: 150, y: -150, width: 150, height: 150 },
+ { type: DocumentType.IMG, message: 'Gallery 4', acceptTags: ['PERSON'], x: -150, y: 0, width: 150, height: 150 },
+ { type: DocumentType.IMG, message: 'Gallery 5', acceptTags: ['PERSON'], x: 0, y: 0, width: 150, height: 150 },
+ { type: DocumentType.IMG, message: 'Gallery 6', acceptTags: ['PERSON'], x: 150, y: 0, width: 150, height: 150 },
+ ];
+ }
+
+ private static createDefaultPreset(): ScrapbookItemConfig[] {
+ return [
+ { type: DocumentType.IMG, message: 'drop a landscape image', acceptTags: ['LANDSCAPE'], x: 44, y: -50, width: 200, height: 120 },
+ { type: DocumentType.PDF, message: 'summary pdf', acceptTags: ['word', 'sentence', 'paragraphs'], x: 45, y: 93, width: 184, height: 273 },
+ { type: DocumentType.RTF, message: 'sidebar text', acceptTags: ['paragraphs'], x: 250, y: -50, width: 100, height: 200 },
+ { containerWidth: 200, containerHeight: 425, x: -171, y: -54, width: 200, height: 425,
+ children: [{ type: DocumentType.IMG, message: 'drop a person image', acceptTags: ['PERSON'], x: -350, y: 200, width: 162, height: 137 }], },
+ ]; // prettier-ignore
+ }
+
+ private static createCollagePreset(): ScrapbookItemConfig[] {
+ return [
+ { type: DocumentType.IMG, message: 'landscape image', acceptTags: ['LANDSCAPE'], x: -174, y: 100, width: 160, height: 150 },
+ { type: DocumentType.IMG, message: 'person image', acceptTags: ['PERSON'], x: 0, y: 100, width: 150, height: 150 },
+ { type: DocumentType.RTF, message: 'caption', acceptTags: ['sentence'], x: -174, y: 50, width: 150, height: 40 },
+ { type: DocumentType.RTF, message: 'caption', acceptTags: ['sentence'], x: 0, y: 50, width: 150, height: 40 },
+ { type: DocumentType.RTF, message: 'lengthy description', acceptTags: ['paragraphs'], x: -180, y: -60, width: 350, height: 100 },
+ ]; // prettier-ignore
+ }
+
+ private static createSpotlightPreset(): ScrapbookItemConfig[] {
+ return [
+ { type: DocumentType.RTF, message: 'title text', acceptTags: ['word'], x: 0, y: -30, width: 300, height: 40 },
+ { type: DocumentType.IMG, message: 'drop a landscape image', acceptTags: ['LANDSCAPE'], x: 0, y: 20, width: 300, height: 200 },
+ { type: DocumentType.RTF, message: 'caption text', acceptTags: ['sentence'], x: 0, y: 230, width: 300, height: 50 },
+ ];
+ }
+}
diff --git a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts
new file mode 100644
index 000000000..3a2189d00
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts
@@ -0,0 +1,36 @@
+import { ScrapbookItemConfig } from './ScrapbookPreset';
+import { ScrapbookPresetType } from './ScrapbookPreset';
+
+type PresetGenerator = () => ScrapbookItemConfig[];
+
+// Internal map of preset name to generator
+const presetRegistry = new Map<string, PresetGenerator>();
+
+/**
+ * Register a new scrapbook preset under the given name.
+ */
+export function registerPreset(name: string, gen: PresetGenerator) {
+ presetRegistry.set(name, gen);
+}
+
+/**
+ * List all registered preset names.
+ */
+export function getPresetNames(): string[] {
+ return Array.from(presetRegistry.keys());
+}
+
+/**
+ * Create the config array for the named preset.
+ */
+export function createPreset(name: string): ScrapbookItemConfig[] {
+ const gen = presetRegistry.get(name);
+ if (!gen) throw new Error(`Unknown scrapbook preset: ${name}`);
+ return gen();
+}
+
+// ------------------------
+// Register built-in presets
+import { ScrapbookPreset } from './ScrapbookPreset';
+
+Object.keys(ScrapbookPresetType).forEach(key => registerPreset(key, () => ScrapbookPreset.createPreset(key as ScrapbookPresetType))); // pretter-ignore
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss b/src/client/views/nodes/scrapbook/ScrapbookSlot.scss
deleted file mode 100644
index ae647ad36..000000000
--- a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss
+++ /dev/null
@@ -1,85 +0,0 @@
-//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION
-.scrapbook-slot {
- position: absolute;
- background-color: rgba(245, 245, 245, 0.7);
- border: 2px dashed #ccc;
- border-radius: 5px;
- box-sizing: border-box;
- transition: all 0.2s ease;
- overflow: hidden;
-
- &.scrapbook-slot-over {
- border-color: #4a90e2;
- background-color: rgba(74, 144, 226, 0.1);
- }
-
- &.scrapbook-slot-filled {
- border-style: solid;
- border-color: rgba(0, 0, 0, 0.1);
- background-color: transparent;
-
- &.scrapbook-slot-over {
- border-color: #4a90e2;
- background-color: rgba(74, 144, 226, 0.1);
- }
- }
-
- .scrapbook-slot-empty {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- }
-
- .scrapbook-slot-placeholder {
- text-align: center;
- color: #888;
- }
-
- .scrapbook-slot-title {
- font-weight: bold;
- margin-bottom: 5px;
- }
-
- .scrapbook-slot-instruction {
- font-size: 0.9em;
- font-style: italic;
- }
-
- .scrapbook-slot-content {
- width: 100%;
- height: 100%;
- position: relative;
- }
-
- .scrapbook-slot-controls {
- position: absolute;
- top: 5px;
- right: 5px;
- z-index: 10;
- opacity: 0;
- transition: opacity 0.2s ease;
-
- .scrapbook-slot-remove-btn {
- background-color: rgba(255, 255, 255, 0.8);
- border: 1px solid #ccc;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- font-size: 10px;
-
- &:hover {
- background-color: rgba(255, 0, 0, 0.1);
- }
- }
- }
-
- &:hover .scrapbook-slot-controls {
- opacity: 1;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
deleted file mode 100644
index 2c8f93778..000000000
--- a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-
-//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION
-export interface SlotDefinition {
- id: string;
- x: number; y: number;
- defaultWidth: number;
- defaultHeight: number;
- }
-
- export interface SlotContentMap {
- slotId: string;
- docId?: string;
- }
-
- export interface ScrapbookConfig {
- slots: SlotDefinition[];
- contents?: SlotContentMap[];
- }
-
- export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = {
- slots: [
- { id: "slot1", x: 10, y: 10, defaultWidth: 180, defaultHeight: 120 },
- { id: "slot2", x: 200, y: 10, defaultWidth: 180, defaultHeight: 120 },
- // …etc
- ],
- contents: []
- };
- \ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts
deleted file mode 100644
index 686917d9a..000000000
--- a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-// ScrapbookSlotTypes.ts
-export interface SlotDefinition {
- id: string;
- title: string;
- x: number;
- y: number;
- defaultWidth: number;
- defaultHeight: number;
- }
-
- export interface ScrapbookConfig {
- slots: SlotDefinition[];
- contents?: { slotId: string; docId: string }[];
- }
-
- // give it three slots by default:
- export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = {
- slots: [
- { id: "main", title: "Main Content", x: 20, y: 20, defaultWidth: 360, defaultHeight: 200 },
- { id: "notes", title: "Notes", x: 20, y: 240, defaultWidth: 360, defaultHeight: 160 },
- { id: "resources", title: "Resources", x: 400, y: 20, defaultWidth: 320, defaultHeight: 380 },
- ],
- contents: [],
- };
- \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index e24b47bd1..8875f7012 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -229,7 +229,7 @@
.toolbar-transition {
display: flex;
font-size: 10;
- width: 100;
+ width: 100px;
background-color: rgba(0, 0, 0, 0);
min-width: max-content;
@@ -358,7 +358,7 @@
font-weight: 200;
padding: 8px;
border-radius: 4px;
- // height: 20;
+ // height: 20px;
// display: flex;
// margin-left: 5px;
// margin-top: 5px;
@@ -371,8 +371,8 @@
}
.ribbon-propertyUpDown {
- height: 20;
- width: 20;
+ height: 20px;
+ width: 20px;
margin-top: 5px;
display: grid;
grid-template-rows: 10px 10px;
@@ -486,7 +486,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- margin: 0;
+ margin: 0px;
margin-right: 3px;
border-radius: 100%;
height: 15px;
@@ -658,9 +658,9 @@
height: 25px;
color: white;
width: 100%;
- max-width: 120;
- padding-left: 10;
- padding-right: 10;
+ max-width: 120px;
+ padding-left: 10px;
+ padding-right: 10px;
border-radius: 10px;
background-color: global.$medium-gray;
}
@@ -683,9 +683,9 @@
height: 25px;
color: global.$light-gray;
width: 100%;
- max-width: 120;
- padding-left: 10;
- padding-right: 10;
+ max-width: 120px;
+ padding-left: 10px;
+ padding-right: 10px;
border-radius: 10px;
background-color: global.$black;
}
@@ -745,9 +745,9 @@
.selectedList {
display: block;
- min-width: 50;
- max-width: 120;
- height: 70;
+ min-width: 50px;
+ max-width: 120px;
+ height: 70px;
overflow-y: scroll;
.selectedList-items {
@@ -760,7 +760,7 @@
cursor: pointer;
font-size: 10.5;
font-weight: 300;
- height: 20;
+ height: 20px;
background-color: global.$medium-gray;
color: white;
display: flex;
@@ -788,7 +788,7 @@
cursor: pointer;
font-size: 10.5;
font-weight: 200;
- height: 20;
+ height: 20px;
background-color: global.$white;
display: inline-flex;
color: global.$black;
@@ -813,7 +813,7 @@
}
svg.svg-inline--fa.fa-thumbtack.fa-w-12.toolbar-thumbtack {
- right: 40;
+ right: 40px;
position: absolute;
transform: rotate(45deg);
}
@@ -850,7 +850,7 @@
background-color: global.$light-gray;
border-radius: 5px;
font-size: 10;
- height: 25;
+ height: 25px;
color: global.$black;
padding-left: 5px;
align-items: center;
@@ -868,8 +868,8 @@
display: block;
padding-left: 10px;
padding-right: 5px;
- padding-top: 3;
- padding-bottom: 3;
+ padding-top: 3px;
+ padding-bottom: 3px;
opacity: 0.8;
}
@@ -959,8 +959,8 @@
border-radius: 4px;
padding-left: 7px;
padding-right: 7px;
- border-bottom-right-radius: 0;
- border-top-right-radius: 0;
+ border-bottom-right-radius: 0px;
+ border-top-right-radius: 0px;
}
.presBox-button-right {
@@ -976,8 +976,8 @@
border-radius: 4px;
padding-left: 7px;
padding-right: 7px;
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
+ border-bottom-left-radius: 0px;
+ border-top-left-radius: 0px;
}
.presBox-button-right.active {
@@ -1043,8 +1043,8 @@
cursor: pointer;
align-self: center;
justify-self: center;
- margin-top: 5;
- margin-bottom: 5;
+ margin-top: 5px;
+ margin-bottom: 5px;
position: relative;
height: 55px;
min-width: 90px;
@@ -1063,7 +1063,7 @@
padding-left: 3px;
margin-left: 3px;
margin-right: 3px;
- height: 13;
+ height: 13px;
font-size: 12;
display: flex;
background-color: global.$white;
@@ -1076,7 +1076,7 @@
margin-left: 3px;
margin-right: 3px;
font-weight: 400;
- height: 13;
+ height: 13px;
font-size: 9;
display: flex;
background-color: global.$white;
@@ -1089,11 +1089,11 @@
padding-left: 3px;
margin-left: 3px;
margin-right: 3px;
- height: 13;
+ height: 13px;
font-size: 10;
display: flex;
background-color: global.$white;
- height: 33;
+ height: 33px;
text-align: left;
font-size: 8px;
}
@@ -1112,7 +1112,7 @@
.presBox-viewPicker {
cursor: pointer;
- height: 25;
+ height: 25px;
position: relative;
display: inline-block;
grid-column: 1;
@@ -1184,9 +1184,9 @@
}
.collectionViewBaseChrome-viewPicker {
- min-width: 50;
+ min-width: 50px;
width: 5%;
- height: 25;
+ height: 25px;
position: relative;
display: inline-block;
left: 8px;
@@ -1210,11 +1210,11 @@
}
.presBox-backward {
- left: 5;
+ left: 5px;
}
.presBox-forward {
- right: 5;
+ right: 5px;
}
// CSS adjusted for mobile devices
@@ -1233,8 +1233,8 @@
.presBox-button {
margin-top: 5%;
- height: 250;
- width: 300;
+ height: 250px;
+ width: 300px;
font-size: 100;
display: flex;
align-items: center;
@@ -1256,7 +1256,7 @@
}
.presBox-cont .presBox-listCont {
- top: 50;
+ top: 50px;
height: calc(100% - 80px);
}
@@ -1269,8 +1269,8 @@
.miniPres {
cursor: grab;
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
opacity: 0.5;
transition: all 0.4s;
color: global.$white;
@@ -1298,7 +1298,7 @@
.presPanel-button-text {
cursor: pointer;
display: flex;
- height: 20;
+ height: 20px;
width: max-content;
font-family: Roboto;
font-weight: 400;
@@ -1327,8 +1327,8 @@
grid-template-columns: auto auto auto;
justify-content: space-around;
font-size: 11;
- margin-left: 7;
- width: 30;
+ margin-left: 7px;
+ width: 30px;
height: 85%;
background-color: rgba(91, 157, 221, 0.4);
border-radius: 5px;
@@ -1337,8 +1337,8 @@
.presPanel-button {
cursor: pointer;
display: flex;
- height: 20;
- min-width: 20;
+ height: 20px;
+ min-width: 20px;
margin-left: 3px;
margin-right: 3px;
border-radius: 100%;
@@ -1361,8 +1361,8 @@
// cursor: grab;
// position: absolute;
// overflow: hidden;
-// right: 10;
-// top: 10;
+// right: 10px;
+// top: 10px;
// opacity: 0.1;
// transition: all 0.4s;
// /* border: solid 1px; */
@@ -1380,7 +1380,7 @@
// .miniPres-button-text {
// cursor: pointer;
// display: flex;
-// height: 20;
+// height: 20px;
// font-weight: 400;
// min-width: 100%;
// border-radius: 5px;
@@ -1397,8 +1397,8 @@
// grid-template-columns: auto auto auto;
// justify-content: space-around;
// font-size: 11;
-// margin-left: 7;
-// width: 30;
+// margin-left: 7px;
+// width: 30px;
// height: 85%;
// background-color: rgba(91, 157, 221, 0.4);
// border-radius: 5px;
@@ -1413,8 +1413,8 @@
// .miniPres-button {
// cursor: pointer;
// display: flex;
-// height: 20;
-// min-width: 20;
+// height: 20px;
+// min-width: 20px;
// border-radius: 100%;
// align-items: center;
// justify-content: center;
diff --git a/src/client/views/nodes/trails/PresSlideBox.scss b/src/client/views/nodes/trails/PresSlideBox.scss
index 9ac2b5a94..740bcae5f 100644
--- a/src/client/views/nodes/trails/PresSlideBox.scss
+++ b/src/client/views/nodes/trails/PresSlideBox.scss
@@ -117,8 +117,8 @@ $slide-active: #5b9fdd;
height: 100%;
position: absolute;
border-radius: 3px;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
z-index: 1;
overflow: hidden;
}
@@ -209,7 +209,7 @@ $slide-active: #5b9fdd;
position: absolute;
/* grid-row: 3; */
/* grid-column: 1/8; */
- top: 28;
+ top: 28px;
display: block;
background: #92adb9;
width: 100%;
@@ -276,7 +276,7 @@ $slide-active: #5b9fdd;
cursor: pointer;
position: absolute;
border-radius: 100px;
- bottom: 0;
+ bottom: 0px;
left: -18;
z-index: 300;
width: 15px;
@@ -302,7 +302,7 @@ $slide-active: #5b9fdd;
color: #d5dce2;
position: absolute;
left: -15px;
- top: 1;
+ top: 1px;
font-weight: 600;
font-size: 12;
}
diff --git a/src/client/views/pdf/AnchorMenu.scss b/src/client/views/pdf/AnchorMenu.scss
index 6990bdcf1..98eeaf80e 100644
--- a/src/client/views/pdf/AnchorMenu.scss
+++ b/src/client/views/pdf/AnchorMenu.scss
@@ -6,19 +6,19 @@
}
.anchorMenu-highlighter {
padding-right: 5px;
- .antimodeMenu-button {
- padding: 0;
- padding: 0;
+ .antimodeMenu-button {
+ padding: 0px;
+ padding: 0px;
padding-right: 0px;
padding-left: 0px;
width: 5px;
}
}
-.anchor-color-preview-button {
- width: 25px !important;
+.anchor-color-preview-button {
+ width: 25px !important;
.anchor-color-preview {
display: flex;
- flex-direction: column;
+ flex-direction: column;
padding-right: 3px;
width: unset !important;
.color-preview {
@@ -51,4 +51,4 @@
border: 2px solid white;
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index f6fa45221..85c1f6759 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -20,8 +20,8 @@ $headingHeight: 32px;
left: 75px;
width: 100%;
height: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
border-top: solid gray 20px;
border-radius: 16px;
z-index: 999;
@@ -58,7 +58,7 @@ $headingHeight: 32px;
font-size: 12px;
font-weight: 400;
letter-spacing: 1px;
- margin: 0;
+ margin: 0px;
padding-right: 5px;
}
@@ -214,8 +214,8 @@ $headingHeight: 32px;
.img-container::after {
content: '';
position: absolute;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index a54505443..dafa908a2 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -3,8 +3,8 @@
width: 100%;
position: absolute;
display: inline-block;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
}
:root {
@@ -16,8 +16,8 @@
position: absolute;
width: 100%;
height: 100%;
- top: 0;
- left: 0;
+ top: 0px;
+ left: 0px;
position: absolute;
overflow-y: auto;
overflow-x: hidden;
@@ -99,7 +99,7 @@
.pdfViewerDash-annotationLayer {
position: absolute;
transform-origin: left top;
- top: 0;
+ top: 0px;
width: 100%;
pointer-events: none;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index 3ad5bc844..84404d65a 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -9,6 +9,7 @@ import { ImageField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
+import { DocumentView } from '../nodes/DocumentView';
/**
* A singleton class that handles face recognition and manages face Doc collections for each face found.
@@ -33,7 +34,7 @@ export class FaceRecognitionHandler {
// eslint-disable-next-line no-use-before-define
static _instance: FaceRecognitionHandler;
private _apiModelReady = false;
- private _pendingAPIModelReadyDocs: Doc[] = [];
+ private _pendingAPIModelReadyDocs: DocumentView[] = [];
public static get Instance() {
return FaceRecognitionHandler._instance ?? new FaceRecognitionHandler();
@@ -126,7 +127,7 @@ export class FaceRecognitionHandler {
constructor() {
FaceRecognitionHandler._instance = this;
this.loadAPIModels().then(() => this._pendingAPIModelReadyDocs.forEach(this.classifyFacesInImage));
- DocumentManager.Instance.AddAnyViewRenderedCB(dv => FaceRecognitionHandler.Instance.classifyFacesInImage(dv.Document));
+ DocumentManager.Instance.AddAnyViewRenderedCB(dv => FaceRecognitionHandler.Instance.classifyFacesInImage(dv));
}
/**
@@ -199,16 +200,18 @@ export class FaceRecognitionHandler {
* match them to existing unique faces, otherwise new unique face(s) are created.
* @param imgDoc The document being analyzed.
*/
- private classifyFacesInImage = async (imgDoc: Doc) => {
+ private classifyFacesInImage = async (imgDocView: DocumentView) => {
+ const imgDoc = imgDocView.Document;
if (!Doc.UserDoc().recognizeFaceImages) return;
const activeDashboard = Doc.ActiveDashboard;
if (!this._apiModelReady || !activeDashboard) {
- this._pendingAPIModelReadyDocs.push(imgDoc);
+ this._pendingAPIModelReadyDocs.push(imgDocView);
} else if (imgDoc.type === DocumentType.LOADING && !imgDoc.loadingError) {
- setTimeout(() => this.classifyFacesInImage(imgDoc), 1000);
+ setTimeout(() => this.classifyFacesInImage(imgDocView), 1000);
} else {
const imgUrl = ImageCast(imgDoc[Doc.LayoutDataKey(imgDoc)]);
if (imgUrl && !DocListCast(Doc.MyFaceCollection?.examinedFaceDocs).includes(imgDoc[DocData])) {
+ imgDocView.ComponentView?.autoTag?.();
// only examine Docs that have an image and that haven't already been examined.
Doc.MyFaceCollection && Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]);
FaceRecognitionHandler.loadImage(imgUrl).then(
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 35a3da312..ca177c746 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -125,7 +125,7 @@
.topbar-lozenge-user,
.topbar-lozenge {
- height: 23;
+ height: 23px;
font-size: 12;
color: white;
font-family: 'Roboto';