aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json45
-rw-r--r--package.json2
-rw-r--r--src/client/util/CurrentUserUtils.ts8
-rw-r--r--src/client/views/DashboardView.tsx4
-rw-r--r--src/client/views/Main.scss1
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/StyleProvider.tsx5
-rw-r--r--src/client/views/collections/CollectionTreeView.scss2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss7
-rw-r--r--src/client/views/global/globalCssVariables.scss4
-rw-r--r--src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss15
-rw-r--r--src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx53
-rw-r--r--src/client/views/newlightbox/ButtonMenu/index.ts1
-rw-r--r--src/client/views/newlightbox/ButtonMenu/utils.ts3
-rw-r--r--src/client/views/newlightbox/ExploreView/ExploreView.scss44
-rw-r--r--src/client/views/newlightbox/ExploreView/ExploreView.tsx30
-rw-r--r--src/client/views/newlightbox/ExploreView/index.ts1
-rw-r--r--src/client/views/newlightbox/ExploreView/utils.ts20
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.scss71
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.tsx62
-rw-r--r--src/client/views/newlightbox/Header/index.ts1
-rw-r--r--src/client/views/newlightbox/Header/utils.ts4
-rw-r--r--src/client/views/newlightbox/NewLightboxStyles.scss73
-rw-r--r--src/client/views/newlightbox/NewLightboxView.scss34
-rw-r--r--src/client/views/newlightbox/NewLightboxView.tsx388
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.scss117
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.tsx196
-rw-r--r--src/client/views/newlightbox/RecommendationList/index.ts1
-rw-r--r--src/client/views/newlightbox/RecommendationList/utils.ts9
-rw-r--r--src/client/views/newlightbox/components/EditableText/EditableText.scss34
-rw-r--r--src/client/views/newlightbox/components/EditableText/EditableText.tsx65
-rw-r--r--src/client/views/newlightbox/components/EditableText/index.ts1
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.scss176
-rw-r--r--src/client/views/newlightbox/components/Recommendation/Recommendation.tsx90
-rw-r--r--src/client/views/newlightbox/components/Recommendation/index.ts2
-rw-r--r--src/client/views/newlightbox/components/Recommendation/utils.ts23
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss82
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx22
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/index.ts1
-rw-r--r--src/client/views/newlightbox/components/SkeletonDoc/utils.ts5
-rw-r--r--src/client/views/newlightbox/components/Template/Template.scss15
-rw-r--r--src/client/views/newlightbox/components/Template/Template.tsx10
-rw-r--r--src/client/views/newlightbox/components/Template/index.ts1
-rw-r--r--src/client/views/newlightbox/components/Template/utils.ts3
-rw-r--r--src/client/views/newlightbox/components/index.ts3
-rw-r--r--src/client/views/newlightbox/utils.ts121
-rw-r--r--src/client/views/nodes/DocumentView.scss5
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx71
-rw-r--r--src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx77
-rw-r--r--src/client/views/nodes/button/colorDropdown/index.ts1
-rw-r--r--src/client/views/search/SearchBox.scss65
-rw-r--r--src/client/views/search/SearchBox.tsx57
-rw-r--r--src/client/views/topbar/TopBar.tsx20
-rw-r--r--src/server/server_Initialization.ts2
55 files changed, 1970 insertions, 187 deletions
diff --git a/package-lock.json b/package-lock.json
index 4da0600b0..b1cb63540 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4497,9 +4497,9 @@
}
},
"browndash-components": {
- "version": "0.0.28",
- "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.28.tgz",
- "integrity": "sha512-pg8qAmaILk6GpUfrdDub6Bg5+LD7547UDzw44yntKsNQC8FDNE9KkDa1ShH5yAn+cwKuTMa/a9Q9yZxMtm94bQ==",
+ "version": "0.0.35",
+ "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.35.tgz",
+ "integrity": "sha512-xHScZmY6y7s3RH7N4rQ4oEu0iAeeP4FbR2Nv289JREThcqiHQqII7vV4rF0tiL9jJepwGLaSIO3k45kUhm62ZQ==",
"requires": {
"color": "^4.2.3",
"react-color": "^2.19.3",
@@ -5955,16 +5955,6 @@
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true
},
- "d": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
- "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
- "dev": true,
- "requires": {
- "es5-ext": "^0.10.50",
- "type": "^1.0.1"
- }
- },
"d3": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz",
@@ -7345,28 +7335,6 @@
"is-symbol": "^1.0.2"
}
},
- "es5-ext": {
- "version": "0.10.62",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
- "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
- "dev": true,
- "requires": {
- "es6-iterator": "^2.0.3",
- "es6-symbol": "^3.1.3",
- "next-tick": "^1.1.0"
- }
- },
- "es6-iterator": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
- "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
- "dev": true,
- "requires": {
- "d": "1",
- "es5-ext": "^0.10.35",
- "es6-symbol": "^3.1.1"
- }
- },
"es6-promise": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -7378,7 +7346,6 @@
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dev": true,
"requires": {
- "d": "^1.0.1",
"ext": "^1.1.2"
}
},
@@ -23051,12 +23018,6 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
- "type": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
- "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
- "dev": true
- },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 6cd271c96..da3097bf4 100644
--- a/package.json
+++ b/package.json
@@ -177,7 +177,7 @@
"body-parser": "^1.19.2",
"bootstrap": "^4.6.1",
"brotli": "^1.3.3",
- "browndash-components": "^0.0.28",
+ "browndash-components": "0.0.35",
"browser-assert": "^1.2.1",
"bson": "^4.6.1",
"canvas": "^2.9.3",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 11a8dcaf6..4c5fea260 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -693,13 +693,13 @@ export class CurrentUserUtils {
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid, CollectionViewType.NoteTaking]),
title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
- { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 20, scripts: { onClick: 'pinWithView(altKey)'}},
- { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
+ { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}},
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
{ title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}},
{ title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
{ title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
- { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
{ title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
{ title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
{ title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 6aae302ac..d91c17545 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, FontSize, IconButton, Size } from 'browndash-components';
+import { Button, ColorPicker, FontSize, IconButton, Size, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -155,7 +155,7 @@ export class DashboardView extends React.Component {
<div className="dashboard-view">
<div className="left-menu">
<div className="new-dashboard-button">
- <Button icon={<FaPlus />} size={Size.MEDIUM} text="New" onClick={() => this.setNewDashboardName('')} />
+ <Button icon={<FaPlus />} size={Size.LARGE} text="New" type={Type.SEC} onClick={() => this.setNewDashboardName('')} />
</div>
<div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.MyDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)}>
My Dashboards
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index c7a7614ac..a403a10e3 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -11,6 +11,7 @@ body {
height: 100%;
overflow: hidden;
font-family: $sans-serif;
+ font-size: $body-text;
margin: 0;
position: absolute;
top: 0;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ab2e0f7c5..ec8f5989f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,6 +49,7 @@ import { InkTranscription } from './InkTranscription';
import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import './MainView.scss';
+import { NewLightboxView } from './newlightbox/NewLightboxView';
import { AudioBox } from './nodes/AudioBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
@@ -1018,6 +1019,7 @@ export class MainView extends React.Component {
<InkTranscription />
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+ <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
</div>
);
}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 330ccc583..0af9bfed2 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -116,6 +116,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
allSorts[TreeSort.None] = { color: 'darkgray', label: '\u00A0\u00A0\u00A0' };
return allSorts;
case StyleProp.Highlighting:
+ if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;
if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
const selected = SelectionManager.Views().some(dv => dv.rootDoc === doc);
const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
@@ -221,8 +222,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
docColor = docColor || (Doc.IsSystem(doc)
? darkScheme()
- ? Colors.DARK_GRAY
- : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background
+ ? undefined
+ : undefined // system docs (seen in treeView) get a grayish background
: doc.annotationOn
? '#00000010' // faint interior for collections on PDFs, images, etc
: doc?._isGroup
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 273b08247..2bf649caf 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -13,7 +13,7 @@
width: 100%;
position: relative;
top: 0;
- background: $light-gray;
+ // background: $light-gray;
font-size: 13px;
overflow: auto;
user-select: none;
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 3e3709827..6b3318bf3 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -17,12 +17,7 @@
.collectionLinearView-menuOpener {
user-select: none;
}
-
- &.true {
- border-left: $standard-border;
- background-color: $medium-blue-alt;
- }
-
+
> input:not(:checked) ~ &.true {
background-color: transparent;
}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 422dae15b..7b2ac5713 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -36,8 +36,8 @@ $icon-size: 28px;
// fonts
$sans-serif: 'Roboto', sans-serif;
$large-header: 16px;
-$body-text: 12px;
-$small-text: 9px;
+$body-text: 13px;
+$small-text: 10px;
// $sans-serif: "Roboto Slab", sans-serif;
// misc values
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
new file mode 100644
index 000000000..74fbfbb2c
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
@@ -0,0 +1,15 @@
+@import '../NewLightboxStyles.scss';
+
+.newLightboxButtonMeny-container {
+ width: 100vw;
+ height: 100vh;
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
new file mode 100644
index 000000000..0ede75407
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -0,0 +1,53 @@
+import './ButtonMenu.scss';
+import * as React from 'react';
+import { IButtonMenu } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { CollectionDockingView } from '../../collections/CollectionDockingView';
+import { OpenWhereMod } from '../../nodes/DocumentView';
+import { Doc } from '../../../../fields/Doc';
+import { InkTool } from '../../../../fields/InkField';
+import { MainView } from '../../MainView';
+import { action } from 'mobx';
+
+export const ButtonMenu = (props: IButtonMenu) => {
+
+ return <div className={`newLightboxButtonMenu-container`}>
+ <div
+ className="newLightboxView-navBtn"
+ title="toggle fit width"
+ onClick={e => {
+ e.stopPropagation();
+ NewLightboxView.NewLightboxDoc!._fitWidth = !NewLightboxView.NewLightboxDoc!._fitWidth;
+ }}>
+ </div>
+ <div
+ className="newLightboxView-tabBtn"
+ title="open in tab"
+ onClick={e => {
+ e.stopPropagation();
+ CollectionDockingView.AddSplit(NewLightboxView.NewLightboxDoc || NewLightboxView.NewLightboxDoc!, OpenWhereMod.none);
+ SelectionManager.DeselectAll();
+ NewLightboxView.SetNewLightboxDoc(undefined);
+ }}>
+ </div>
+ <div
+ className="newLightboxView-penBtn"
+ title="toggle pen annotation"
+ style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }}
+ onClick={e => {
+ e.stopPropagation();
+ Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
+ }}>
+ </div>
+ <div
+ className="newLightboxView-exploreBtn"
+ title="toggle explore mode to navigate among documents only"
+ style={{ background: MainView.Instance._exploreMode ? 'white' : undefined }}
+ onClick={action(e => {
+ e.stopPropagation();
+ MainView.Instance._exploreMode = !MainView.Instance._exploreMode;
+ })}>
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ButtonMenu/index.ts b/src/client/views/newlightbox/ButtonMenu/index.ts
new file mode 100644
index 000000000..f53a8c729
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/index.ts
@@ -0,0 +1 @@
+export * from './ButtonMenu' \ No newline at end of file
diff --git a/src/client/views/newlightbox/ButtonMenu/utils.ts b/src/client/views/newlightbox/ButtonMenu/utils.ts
new file mode 100644
index 000000000..096ea87ad
--- /dev/null
+++ b/src/client/views/newlightbox/ButtonMenu/utils.ts
@@ -0,0 +1,3 @@
+export interface IButtonMenu {
+
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.scss b/src/client/views/newlightbox/ExploreView/ExploreView.scss
new file mode 100644
index 000000000..5a8ab2f87
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.scss
@@ -0,0 +1,44 @@
+@import '../NewLightboxStyles.scss';
+
+.exploreView-container {
+ width: 100%;
+ height: 100%;
+ border-radius: 20px;
+ position: relative;
+ // transform: scale(1);
+ background: $gray-l1;
+ border-top: $standard-border;
+ border-color: $gray-l2;
+ border-radius: 0px 0px 20px 20px;
+ transform-origin: 50% 50%;
+ overflow: hidden;
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $gray-l1;
+ }
+
+ .exploreView-doc {
+ width: 60px;
+ height: 80px;
+ position: absolute;
+ background: $blue-l2;
+ // opacity: 0.8;
+ transform-origin: 50% 50%;
+ transform: translate(-50%, -50%) scale(1);
+ cursor: pointer;
+ transition: 0.2s ease;
+ overflow: hidden;
+ font-size: 9px;
+ padding: 10px;
+ border-radius: 5px;
+
+ &:hover {
+ transform: translate(calc(-50% * 1.125), calc(-50% * 1.125)) scale(1.5);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.tsx b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
new file mode 100644
index 000000000..855bfd9e2
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.tsx
@@ -0,0 +1,30 @@
+import './ExploreView.scss';
+import { IBounds, IExploreView, emptyBounds } from "./utils";
+import { IRecommendation } from "../components";
+import * as React from 'react';
+import { NewLightboxView } from '../NewLightboxView';
+import { StrCast } from '../../../../fields/Types';
+
+
+
+export const ExploreView = (props: IExploreView) => {
+ const { recs, bounds=emptyBounds } = props
+
+ return <div className={`exploreView-container`}>
+ {recs && recs.map((rec) => {
+ console.log(rec.embedding, bounds)
+ const x_bound: number = Math.max(Math.abs(bounds.max_x), Math.abs(bounds.min_x))
+ const y_bound: number = Math.max(Math.abs(bounds.max_y), Math.abs(bounds.min_y))
+ console.log(x_bound, y_bound)
+ if (rec.embedding) {
+ const x = (rec.embedding.x / x_bound) * 50;
+ const y = (rec.embedding.y / y_bound) * 50;
+ console.log(x, y)
+ return <div className={`exploreView-doc`} onClick={() => {}} style={{top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)`}}>
+ {rec.title}
+ </div>
+ } else return (null)
+ })}
+ <div className={`exploreView-doc`} style={{top: `calc(50% + ${0}%)`, left: `calc(50% + ${0}%)`, background: '#073763', color: 'white'}}>{StrCast(NewLightboxView.NewLightboxDoc?.title)}</div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/index.ts b/src/client/views/newlightbox/ExploreView/index.ts
new file mode 100644
index 000000000..bf94eedcd
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/index.ts
@@ -0,0 +1 @@
+export * from './ExploreView' \ No newline at end of file
diff --git a/src/client/views/newlightbox/ExploreView/utils.ts b/src/client/views/newlightbox/ExploreView/utils.ts
new file mode 100644
index 000000000..7d9cf226d
--- /dev/null
+++ b/src/client/views/newlightbox/ExploreView/utils.ts
@@ -0,0 +1,20 @@
+import { IRecommendation } from "../components";
+
+export interface IExploreView {
+ recs?: IRecommendation[],
+ bounds?: IBounds
+}
+
+export const emptyBounds = {
+ max_x: 0,
+ max_y: 0,
+ min_x: 0,
+ min_y: 0
+}
+
+export interface IBounds {
+ max_x: number,
+ max_y: number,
+ min_x: number,
+ min_y: number
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.scss b/src/client/views/newlightbox/Header/LightboxHeader.scss
new file mode 100644
index 000000000..a9e60ea98
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.scss
@@ -0,0 +1,71 @@
+@import '../NewLightboxStyles.scss';
+
+.newLightboxHeader-container {
+ width: 100%;
+ height: 100%;
+ background: $gray-l1;
+ border-radius: 20px 20px 0px 0px;
+ padding: 20px;
+ display: grid;
+ grid-template-columns: 70% 30%;
+ grid-template-rows: 50% 50%;
+
+ .title-container,
+ .type-container {
+ display: flex;
+ flex-direction: row;
+ gap: 5px;
+ justify-content: flex-start;
+ align-items: center;
+ }
+
+ .title-container {
+ grid-column: 1;
+ grid-row: 1;
+ }
+
+ .type-container {
+ grid-column: 1;
+ grid-row: 2;
+ .type {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ }
+ }
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ }
+
+ .lb-button {
+ border: solid 1.5px black;
+ padding: 3px 5px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ align-items: center;
+ transition: 0.2s ease;
+ gap: 5px;
+ font-size: $body-size;
+ height: fit-content;
+
+ &:hover {
+ background: $gray-l2;
+ }
+
+ &.true {
+ background: $blue-l1;
+ }
+ }
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx
new file mode 100644
index 000000000..a272ce294
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -0,0 +1,62 @@
+import './LightboxHeader.scss';
+import * as React from 'react';
+import { INewLightboxHeader } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { StrCast } from '../../../../fields/Types';
+import { EditableText } from '../components/EditableText';
+import { getType } from '../utils';
+import { Button, IconButton, Size, Type } from 'browndash-components';
+import { MdExplore, MdTravelExplore } from 'react-icons/md'
+import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'
+import { Doc } from '../../../../fields/Doc';
+import { LightboxView } from '../../LightboxView';
+import { Colors } from '../../global/globalEnums';
+
+
+export const NewLightboxHeader = (props: INewLightboxHeader) => {
+ const {height = 100, width} = props;
+ const [doc, setDoc] = React.useState<Doc | undefined>(LightboxView.LightboxDoc)
+ const [editing, setEditing] = React.useState<boolean>(false)
+ const [title, setTitle] = React.useState<JSX.Element | null>(
+ (null)
+ )
+ React.useEffect(() => {
+ let lbDoc = LightboxView.LightboxDoc
+ setDoc(lbDoc)
+ if (lbDoc) {
+ setTitle(
+ <EditableText
+ editing={editing}
+ text={StrCast(lbDoc.title)}
+ onEdit={(newText: string) => {
+ if(lbDoc) lbDoc.title = newText;
+ }}
+ setEditing={setEditing}
+ />)
+ }
+ }, [LightboxView.LightboxDoc])
+
+ const [saved, setSaved] = React.useState<boolean>(false)
+
+ if (!doc) return null
+ else return <div className={`newLightboxHeader-container`} onPointerDown={(e) => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
+ <div className={`title-container`}>
+ <div className={`lb-label`}>Title</div>
+ {title}
+ </div>
+ <div className={`type-container`}>
+ <div className={`lb-label`}>Type</div>
+ <div className={`type`}>{getType(StrCast(doc.type))}</div>
+ </div>
+ <div style={{gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
+ <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
+ </div>
+ <div style={{gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+ <Button onClick={() => {
+ console.log(NewLightboxView.ExploreMode)
+ NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)
+ }} size={Size.XSMALL} color={Colors.DARK_GRAY} type={Type.SEC} text={"t-SNE 2D Embeddings"} icon={<MdTravelExplore/>}/>
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/index.ts b/src/client/views/newlightbox/Header/index.ts
new file mode 100644
index 000000000..090677c16
--- /dev/null
+++ b/src/client/views/newlightbox/Header/index.ts
@@ -0,0 +1 @@
+export * from './LightboxHeader' \ No newline at end of file
diff --git a/src/client/views/newlightbox/Header/utils.ts b/src/client/views/newlightbox/Header/utils.ts
new file mode 100644
index 000000000..22e0487c2
--- /dev/null
+++ b/src/client/views/newlightbox/Header/utils.ts
@@ -0,0 +1,4 @@
+export interface INewLightboxHeader {
+ height?: number
+ width?: number
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/NewLightboxStyles.scss b/src/client/views/newlightbox/NewLightboxStyles.scss
new file mode 100644
index 000000000..ff4a6c971
--- /dev/null
+++ b/src/client/views/newlightbox/NewLightboxStyles.scss
@@ -0,0 +1,73 @@
+$white: white;
+$black: black;
+
+// gray
+$gray-l1: rgba(230, 230, 230, 1);
+$gray-l2: rgb(201, 201, 201);
+$gray-l3: rgba(87, 87, 87, 1);
+
+// blue
+$blue-l1: #cfe2f3;
+$blue-l2: #6fa8dc;
+$blue-l3: #0b5394;
+$blue-l4: #073763;
+
+// view backgrounds
+$background-dm: black;
+$background-lm: white;
+$header-dm: $gray-l3;
+$header-lm: $gray-l1;
+
+// border
+$standard-border: solid 2px;
+
+// standard shadow
+
+
+$text-color-dm: $gray-l1;
+$text-color-lm: $gray-l3;
+
+
+// text / font
+$title-size: 2rem;
+$title-weight: 700;
+
+$h1-size: 15px;
+$h1-weight: 700;
+
+$h2-size: 13px;
+$h2-weight: 600;
+
+
+$body-size: 10px;
+$body-weight: 400;
+
+// header
+$header-height: 40px;
+
+@keyframes skeleton-loading-l3 {
+ 0% {
+ background-color: rgba(128, 128, 128, 1);
+ }
+ 100% {
+ background-color: rgba(128, 128, 128, 0.5);
+ }
+}
+
+@keyframes skeleton-loading-l2 {
+ 0% {
+ background-color: rgba(182, 182, 182, 1);
+ }
+ 100% {
+ background-color: rgba(182, 182, 182, 0.5);
+ }
+}
+
+@keyframes skeleton-loading-l1 {
+ 0% {
+ background-color: rgba(230, 230, 230, 1);
+ }
+ 100% {
+ background-color: rgba(230, 230, 230, 0.5);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/NewLightboxView.scss b/src/client/views/newlightbox/NewLightboxView.scss
new file mode 100644
index 000000000..76c34bcf9
--- /dev/null
+++ b/src/client/views/newlightbox/NewLightboxView.scss
@@ -0,0 +1,34 @@
+@import './NewLightboxStyles.scss';
+
+.newLightboxView-frame {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #474545bb;
+ backdrop-filter: blur(4px);
+ z-index: 1000;
+
+ .app-document {
+ width: 100%;
+ height: 100%;
+ display: grid;
+ }
+
+ .explore {
+ width: 100%;
+ height: 100%;
+ display: grid;
+ }
+
+ .newLightboxView-contents {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ .newLightboxView-doc {
+ position: relative;
+ }
+ }
+}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
new file mode 100644
index 000000000..c5e98da86
--- /dev/null
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -0,0 +1,388 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { DocUtils } from '../../documents/Documents';
+import { DocumentManager } from '../../util/DocumentManager';
+import { LinkManager } from '../../util/LinkManager';
+import { SelectionManager } from '../../util/SelectionManager';
+import { Transform } from '../../util/Transform';
+import { GestureOverlay } from '../GestureOverlay';
+import { MainView } from '../MainView';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { TabDocView } from '../collections/TabDocView';
+import { DocumentView, OpenWhere } from '../nodes/DocumentView';
+import { ExploreView } from './ExploreView';
+import { IBounds, emptyBounds } from './ExploreView/utils';
+import { NewLightboxHeader } from './Header';
+import './NewLightboxView.scss';
+import { RecommendationList } from './RecommendationList';
+import { IRecommendation } from './components';
+import { fetchKeywords, fetchRecommendations } from './utils';
+import { List } from '../../../fields/List';
+import { LightboxView } from '../LightboxView';
+
+enum LightboxStatus {
+ RECOMMENDATIONS = "recommendations",
+ ANNOTATIONS = "annotations",
+ NONE = "none"
+}
+
+interface LightboxViewProps {
+ PanelWidth: number;
+ PanelHeight: number;
+ maxBorder: number[];
+}
+
+type LightboxSavedState = {
+ panX: Opt<number>;
+ panY: Opt<number>;
+ scale: Opt<number>;
+ scrollTop: Opt<number>;
+ layout_fieldKey: Opt<string>;
+};
+@observer
+export class NewLightboxView extends React.Component<LightboxViewProps> {
+ @computed public static get LightboxDoc() {
+ return this._doc;
+ }
+ private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate;
+ @observable private static _layoutTemplate: Opt<Doc>;
+ @observable private static _layoutTemplateString: Opt<string>;
+ @observable private static _doc: Opt<Doc>;
+ @observable private static _docTarget: Opt<Doc>;
+ @observable private static _docFilters: string[] = []; // filters
+ private static _savedState: Opt<LightboxSavedState>;
+ private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
+ @observable private static _future: Opt<Doc[]> = [];
+ @observable private static _docView: Opt<DocumentView>;
+
+ // keywords
+ @observable private static _keywords: string[] = []
+ @action public static SetKeywords(kw: string[]) {
+ this._keywords = kw
+ }
+ @computed public static get Keywords() {
+ return this._keywords
+ }
+
+ // query
+ @observable private static _query: string = ''
+ @action public static SetQuery(query: string) {
+ this._query = query
+ }
+ @computed public static get Query() {
+ return this._query
+ }
+
+ // keywords
+ @observable private static _recs: IRecommendation[] = []
+ @action public static SetRecs(recs: IRecommendation[]) {
+ this._recs = recs
+ }
+ @computed public static get Recs() {
+ return this._recs
+ }
+
+ // bounds
+ @observable private static _bounds: IBounds = emptyBounds;
+ @action public static SetBounds(bounds: IBounds) {
+ this._bounds = bounds;
+ }
+ @computed public static get Bounds() {
+ return this._bounds;
+ }
+
+ // explore
+ @observable private static _explore: Opt<boolean> = false;
+ @action public static SetExploreMode(status: Opt<boolean>) {
+ this._explore = status;
+ }
+ @computed public static get ExploreMode() {
+ return this._explore;
+ }
+
+ // newLightbox sidebar status
+ @observable private static _sidebarStatus: Opt<string> = "";
+ @action public static SetSidebarStatus(sidebarStatus: Opt<string>) {
+ this._sidebarStatus = sidebarStatus;
+ }
+ @computed public static get SidebarStatus() {
+ return this._sidebarStatus;
+ }
+
+ static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<LightboxSavedState> }[] = [];
+ @action public static SetNewLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
+ if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
+ if (this._savedState.panX !== undefined) this.LightboxDoc._freeform_panX = this._savedState.panX;
+ if (this._savedState.panY !== undefined) this.LightboxDoc._freeform_panY = this._savedState.panY;
+ if (this._savedState.scrollTop !== undefined) this.LightboxDoc._layout_scrollTop = this._savedState.scrollTop;
+ if (this._savedState.scale !== undefined) this.LightboxDoc._freeform_scale = this._savedState.scale;
+ this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey;
+ }
+ if (!doc) {
+ this._docFilters && (this._docFilters.length = 0);
+ this._future = this._history = [];
+ Doc.ActiveTool = InkTool.None;
+ MainView.Instance._exploreMode = false;
+ } else {
+ const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+ CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
+ //TabDocView.PinDoc(doc, { hidePresBox: true });
+ this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
+ if (doc !== LightboxView.LightboxDoc) {
+ this._savedState = {
+ layout_fieldKey: StrCast(doc.layout_fieldKey),
+ panX: Cast(doc.freeform_panX, 'number', null),
+ panY: Cast(doc.freeform_panY, 'number', null),
+ scale: Cast(doc.freeform_scale, 'number', null),
+ scrollTop: Cast(doc.layout_scrollTop, 'number', null),
+ };
+ }
+ }
+ if (future) {
+ this._future = [
+ ...(this._future ?? []),
+ ...(this.LightboxDoc ? [this.LightboxDoc] : []),
+ ...future
+ .slice()
+ .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
+ .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length),
+ ];
+ }
+ this._doc = doc;
+ this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined;
+ if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) {
+ doc.layout_fieldKey = layoutTemplate;
+ }
+ this._docTarget = target || doc;
+
+ return true;
+ }
+ public static IsNewLightboxDocView(path: DocumentView[]) {
+ return (path ?? []).includes(this._docView!);
+ }
+ @computed get leftBorder() {
+ return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]);
+ }
+ @computed get topBorder() {
+ return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]);
+ }
+ newLightboxWidth = () => this.props.PanelWidth - 420;
+ newLightboxHeight = () => this.props.PanelHeight - 140;
+ newLightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
+ navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => {
+ return (
+ <div
+ className="newLightboxView-navBtn-frame"
+ style={{
+ display: display(),
+ left,
+ width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+ bottom,
+ }}>
+ <div className="newLightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}>
+ <div style={{ height: 10 }}>{color}</div>
+ <FontAwesomeIcon icon={icon as any} size="3x" />
+ </div>
+ </div>
+ );
+ };
+ public static GetSavedState(doc: Doc) {
+ return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
+ }
+
+ // adds a cookie to the newLightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie
+ @action
+ public static SetCookie(cookie: string) {
+ if (this.LightboxDoc && cookie) {
+ this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
+ }
+ }
+ public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => {
+ SelectionManager.DeselectAll();
+ return NewLightboxView.SetNewLightboxDoc(
+ doc,
+ undefined,
+ [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
+ (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
+ ),
+ layoutTemplate
+ );
+ };
+ docFilters = () => NewLightboxView._docFilters || [];
+ addDocTab = NewLightboxView.AddDocTab;
+ @action public static Next() {
+ const doc = NewLightboxView._doc!;
+ const target = (NewLightboxView._docTarget = this._future?.pop());
+ const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+ if (targetDocView && target) {
+ const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
+ l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
+ DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
+ } else {
+ if (!target && NewLightboxView.path.length) {
+ const saved = NewLightboxView._savedState;
+ if (LightboxView.LightboxDoc && saved) {
+ LightboxView.LightboxDoc._freeform_panX = saved.panX;
+ LightboxView.LightboxDoc._freeform_panY = saved.panY;
+ LightboxView.LightboxDoc._freeform_scale = saved.scale;
+ LightboxView.LightboxDoc._layout_scrollTop = saved.scrollTop;
+ }
+ const pop = NewLightboxView.path.pop();
+ if (pop) {
+ NewLightboxView._doc = pop.doc;
+ NewLightboxView._docTarget = pop.target;
+ NewLightboxView._future = pop.future;
+ NewLightboxView._history = pop.history;
+ NewLightboxView._savedState = pop.saved;
+ }
+ } else {
+ NewLightboxView.SetNewLightboxDoc(target);
+ }
+ }
+ }
+
+ @action public static Previous() {
+ const previous = NewLightboxView._history?.pop();
+ if (!previous || !NewLightboxView._history?.length) {
+ NewLightboxView.SetNewLightboxDoc(undefined);
+ return;
+ }
+ const { doc, target } = NewLightboxView._history?.lastElement();
+ const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+ if (docView) {
+ NewLightboxView._docTarget = target;
+ target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+ } else {
+ NewLightboxView.SetNewLightboxDoc(doc, target);
+ }
+ if (NewLightboxView._future?.lastElement() !== previous.target || previous.doc) NewLightboxView._future?.push(previous.target || previous.doc);
+ }
+ @action
+ stepInto = () => {
+ NewLightboxView.path.push({
+ doc: LightboxView.LightboxDoc,
+ target: NewLightboxView._docTarget,
+ future: NewLightboxView._future,
+ history: NewLightboxView._history,
+ saved: NewLightboxView._savedState,
+ });
+ const coll = NewLightboxView._docTarget;
+ if (coll) {
+ const fieldKey = Doc.LayoutFieldKey(coll);
+ const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])];
+ const links = LinkManager.Links(coll)
+ .map(link => LinkManager.getOppositeAnchor(link, coll))
+ .filter(doc => doc)
+ .map(doc => doc!);
+ NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links);
+ }
+ };
+
+ @computed
+ get documentView() {
+ if (!LightboxView.LightboxDoc) return null
+ else return (<GestureOverlay isActive={true}>
+ <DocumentView
+ ref={action((r: DocumentView | null) => (NewLightboxView._docView = r !== null ? r : undefined))}
+ Document={LightboxView.LightboxDoc}
+ DataDoc={undefined}
+ PanelWidth={this.newLightboxWidth}
+ PanelHeight={this.newLightboxHeight}
+ LayoutTemplate={NewLightboxView.LightboxDocTemplate}
+ isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
+ isContentActive={returnTrue}
+ styleProvider={DefaultStyleProvider}
+ ScreenToLocalTransform={this.newLightboxScreenToLocal}
+ renderDepth={0}
+ rootSelected={returnTrue}
+ docViewPath={returnEmptyDoclist}
+ docFilters={this.docFilters}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ addDocument={undefined}
+ removeDocument={undefined}
+ whenChildContentsActiveChanged={emptyFunction}
+ addDocTab={this.addDocTab}
+ pinToPres={TabDocView.PinDoc}
+ bringToFront={emptyFunction}
+ onBrowseClick={MainView.Instance.exploreMode}
+ focus={emptyFunction}
+ />
+ </GestureOverlay>)
+ }
+
+ future = () => NewLightboxView._future;
+ render() {
+ let newLightboxHeaderHeight = 100;
+ let downx = 0,
+ downy = 0;
+ return !LightboxView.LightboxDoc ? null : (
+ <div
+ className="newLightboxView-frame"
+ onPointerDown={e => {
+ downx = e.clientX;
+ downy = e.clientY;
+ }}
+ onClick={e => {
+ if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
+ NewLightboxView.SetNewLightboxDoc(undefined);
+ }
+ }}>
+ <div className={`app-document`} style={{gridTemplateColumns: `calc(100% - 400px) 400px`}}>
+ <div
+ className="newLightboxView-contents"
+ style={{
+ top: 20,
+ left: 20,
+ width: this.newLightboxWidth(),
+ height: this.newLightboxHeight() - 40,
+ }}>
+ <NewLightboxHeader height={newLightboxHeaderHeight} width={this.newLightboxWidth()} />
+ {!NewLightboxView._explore ?
+ <div className="newLightboxView-doc" style={{height: this.newLightboxHeight()}}>
+ {this.documentView}
+ </div>
+ :
+ <div className={`explore`}>
+ <ExploreView recs={NewLightboxView.Recs} bounds={NewLightboxView.Bounds}/>
+ </div>
+ }
+ </div>
+ <RecommendationList keywords={NewLightboxView.Keywords}/>
+ </div>
+
+ </div>
+ );
+ }
+}
+interface NewLightboxTourBtnProps {
+ navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
+ future: () => Opt<Doc[]>;
+ stepInto: () => void;
+}
+@observer
+export class NewLightboxTourBtn extends React.Component<NewLightboxTourBtnProps> {
+ render() {
+ return this.props.navBtn(
+ '50%',
+ 0,
+ 0,
+ 'chevron-down',
+ () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'),
+ e => {
+ e.stopPropagation();
+ this.props.stepInto();
+ },
+ ''
+ );
+ }
+}
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
new file mode 100644
index 000000000..40dd47e47
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
@@ -0,0 +1,117 @@
+@import '../NewLightboxStyles.scss';
+
+.recommendationlist-container {
+ height: calc(100% - 40px);
+ margin: 20px;
+ border-radius: 20px;
+ overflow-y: scroll;
+
+ .recommendations {
+ height: fit-content;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ background: $gray-l1;
+ border-radius: 0px 0px 20px 20px;
+ }
+
+ .header {
+ top: 0px;
+ position: sticky;
+ background: $gray-l1;
+ border-bottom: $standard-border;
+ border-color: $gray-l2;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ border-radius: 20px 20px 0px 0px;
+ padding: 20px;
+ z-index: 2;
+ gap: 10px;
+ color: $text-color-lm;
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ font-size: $body-size;
+ }
+
+ .lb-caret {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 5px;
+ cursor: pointer;
+ width: 100%;
+ user-select: none;
+ font-size: $body-size;
+ }
+
+ .more {
+ width: 100%;
+ }
+
+ &.dark {
+ color: $text-color-dm;
+ }
+
+ .title {
+ height: 30px;
+ min-height: 30px;
+ font-size: $h1-size;
+ font-weight: $h1-weight;
+ text-align: left;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ }
+
+ .keywords {
+ display: flex;
+ flex-flow: row wrap;
+ gap: 5px;
+
+ .keyword-input {
+ padding: 3px 7px;
+ background: $gray-l2;
+ outline: none;
+ border: none;
+ height: 21.5px;
+ color: $text-color-lm;
+ }
+
+ .keyword {
+ padding: 3px 7px;
+ width: fit-content;
+ background: $gray-l2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+ gap: 10px;
+ font-size: $body-size;
+ font-weight: $body-weight;
+
+ &.loading {
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ min-width: 70px;
+ height: 21.5px;
+ }
+ }
+
+ }
+ }
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $gray-l1;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
new file mode 100644
index 000000000..9f3c32e4e
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
@@ -0,0 +1,196 @@
+import { GrClose } from 'react-icons/gr';
+import { IRecommendation, Recommendation } from "../components";
+import './RecommendationList.scss';
+import * as React from 'react';
+import { IRecommendationList } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { DocCast, StrCast } from '../../../../fields/Types';
+import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
+import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
+import { IDocRequest, fetchKeywords, fetchRecommendations } from '../utils';
+import { IBounds } from '../ExploreView/utils';
+import { List } from '../../../../fields/List';
+import { Id } from '../../../../fields/FieldSymbols';
+import { LightboxView } from '../../LightboxView';
+import { IconButton, Size, Type } from 'browndash-components';
+import { Colors } from '../../global/globalEnums';
+
+export const RecommendationList = (props: IRecommendationList) => {
+ const {loading, keywords} = props
+ const [loadingKeywords, setLoadingKeywords] = React.useState<boolean>(true)
+ const [showMore, setShowMore] = React.useState<boolean>(false)
+ const [keywordsLoc, setKeywordsLoc] = React.useState<string[]>([])
+ const [update, setUpdate] = React.useState<boolean>(true)
+ const initialRecs: IRecommendation[] = [
+ {loading: true},
+ {loading: true},
+ {loading: true},
+ {loading: true},
+ {loading: true}
+ ];
+ const [recs, setRecs] = React.useState<IRecommendation[]>(initialRecs)
+
+ React.useEffect(() => {
+ const getKeywords = async () => {
+ let text = StrCast(LightboxView.LightboxDoc?.text)
+ console.log('[1] fetching keywords')
+ const response = await fetchKeywords(text, 5, true)
+ console.log('[2] response:', response)
+ const kw = response.keywords;
+ console.log(kw);
+ NewLightboxView.SetKeywords(kw);
+ if (LightboxView.LightboxDoc) {
+ console.log('setting keywords on doc')
+ LightboxView.LightboxDoc.keywords = new List<string>(kw);
+ setKeywordsLoc(NewLightboxView.Keywords);
+ }
+ setLoadingKeywords(false)
+ }
+ let keywordsList = StrListCast(LightboxView.LightboxDoc!.keywords)
+ if (!keywordsList || keywordsList.length < 2) {
+ setLoadingKeywords(true)
+ getKeywords()
+ setUpdate(!update)
+ } else {
+ setKeywordsLoc(keywordsList)
+ setLoadingKeywords(false)
+ setUpdate(!update)
+ }
+ }, [NewLightboxView.LightboxDoc])
+
+ // terms: vannevar bush, information spaces,
+ React.useEffect(() => {
+ const getRecommendations = async () => {
+ console.log('fetching recommendations')
+ let query = 'undefined'
+ if (keywordsLoc) query = keywordsLoc.join(',')
+ let src = StrCast(NewLightboxView.LightboxDoc?.text)
+ let dashDocs:IDocRequest[] = [];
+ // get linked docs
+ let linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links)
+ console.log("linked docs", linkedDocs)
+ // get context docs (docs that are also in the collection)
+ // let contextDocs: Doc[] = DocListCast(DocCast(LightboxView.LightboxDoc?.context).data)
+ // let docId = LightboxView.LightboxDoc && LightboxView.LightboxDoc[Id]
+ // console.log("context docs", contextDocs)
+ // contextDocs.forEach((doc: Doc) => {
+ // if (docId !== doc[Id]){
+ // dashDocs.push({
+ // title: StrCast(doc.title),
+ // text: StrCast(doc.text),
+ // id: doc[Id],
+ // type: StrCast(doc.type)
+ // })
+ // }
+ // })
+ console.log("dash docs", dashDocs)
+ if (query !== undefined) {
+ const response = await fetchRecommendations(src, query, [], true)
+ const num_recs = response.num_recommendations
+ const recs = response.recommendations
+ const keywords = response.keywords
+ const response_bounds: IBounds = {
+ max_x: response.max_x,
+ max_y: response.max_y,
+ min_x: response.min_x,
+ min_y: response.min_y
+ }
+ // if (NewLightboxView.NewLightboxDoc) {
+ // NewLightboxView.NewLightboxDoc.keywords = new List<string>(keywords);
+ // setKeywordsLoc(NewLightboxView.Keywords);
+ // }
+ // console.log(response_bounds)
+ NewLightboxView.SetBounds(response_bounds)
+ const recommendations: IRecommendation[] = [];
+ for (const key in recs) {
+ console.log(key)
+ const title = recs[key].title;
+ const url = recs[key].url
+ const type = recs[key].type
+ const text = recs[key].text
+ const transcript = recs[key].transcript
+ const previewUrl = recs[key].previewUrl
+ const embedding = recs[key].embedding
+ const distance = recs[key].distance
+ const source = recs[key].source
+ const related_concepts = recs[key].related_concepts
+ const docId = recs[key].doc_id
+ related_concepts.length >= 1 && recommendations.push({
+ title: title,
+ data: url,
+ type: type,
+ text: text,
+ transcript: transcript,
+ previewUrl: previewUrl,
+ embedding: embedding,
+ distance: Math.round(distance * 100) / 100,
+ source: source,
+ related_concepts: related_concepts,
+ docId: docId
+ })
+ }
+ recommendations.sort((a, b) => {
+ if (a.distance && b.distance) {
+ return a.distance - b.distance
+ } else return 0
+ })
+ console.log("[rec]: ", recommendations)
+ NewLightboxView.SetRecs(recommendations)
+ setRecs(recommendations)
+ }
+ }
+ getRecommendations();
+ }, [update])
+
+
+
+ return <div className={`recommendationlist-container`} onPointerDown={(e) => {e.stopPropagation()}}>
+ <div className={`header`}>
+ <div className={`title`}>
+ Recommendations
+ </div>
+ {NewLightboxView.LightboxDoc && <div style={{fontSize: 10}}>
+ The recommendations are produced based on the text in the document <b><u>{StrCast(NewLightboxView.LightboxDoc.title)}</u></b>. The following keywords are used to fetch the recommendations.
+ </div>}
+ <div className={`lb-label`}>Keywords</div>
+ {loadingKeywords ? <div className={`keywords`}>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ </div>
+ :
+ <div className={`keywords`}>
+ {keywordsLoc && keywordsLoc.map((word, ind) => {
+ return <div className={`keyword`}>
+ {word}
+ <IconButton type={Type.PRIM} size={Size.XSMALL} color={Colors.DARK_GRAY} icon={<GrClose/>} onClick={() => {
+ let kw = keywordsLoc
+ kw.splice(ind)
+ NewLightboxView.SetKeywords(kw)
+ }}/>
+ </div>
+ })}
+ </div>
+ }
+ {!showMore ?
+ <div className={`lb-caret`} onClick={() => {setShowMore(true)}}>
+ More <FaCaretDown/>
+ </div>
+ :
+ <div className={`more`}>
+ <div className={`lb-caret`} onClick={() => {setShowMore(false)}}>
+ Less <FaCaretUp/>
+ </div>
+ <div className={`lb-label`}>Type</div>
+ <div className={`lb-label`}>Sources</div>
+ </div>
+ }
+ </div>
+ <div className={`recommendations`}>
+ {recs && recs.map((rec: IRecommendation) => {
+ return <Recommendation {...rec} />
+ })}
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/index.ts b/src/client/views/newlightbox/RecommendationList/index.ts
new file mode 100644
index 000000000..f4555c1f2
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/index.ts
@@ -0,0 +1 @@
+export * from './RecommendationList' \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/utils.ts b/src/client/views/newlightbox/RecommendationList/utils.ts
new file mode 100644
index 000000000..cdfff3258
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/utils.ts
@@ -0,0 +1,9 @@
+import { IRecommendation } from "../components";
+
+export interface IRecommendationList {
+ loading?: boolean,
+ keywords?: string[],
+ recs?: IRecommendation[]
+ getRecs?: any
+}
+
diff --git a/src/client/views/newlightbox/components/EditableText/EditableText.scss b/src/client/views/newlightbox/components/EditableText/EditableText.scss
new file mode 100644
index 000000000..7828538ab
--- /dev/null
+++ b/src/client/views/newlightbox/components/EditableText/EditableText.scss
@@ -0,0 +1,34 @@
+@import '../../NewLightboxStyles.scss';
+
+.lb-editableText,
+.lb-displayText {
+ padding: 4px 7px !important;
+ border: $standard-border !important;
+ border-color: $gray-l2 !important;
+}
+
+.lb-editableText {
+ -webkit-appearance: none;
+ overflow: hidden;
+ font-size: inherit;
+ border: none;
+ outline: none;
+ width: 100%;
+ margin: 0px;
+ padding: 0px;
+ box-shadow: none !important;
+ background: none;
+
+ &:focus {
+ outline: none;
+ background-color: $blue-l1;
+ }
+}
+
+.lb-displayText {
+ cursor: text !important;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ font-size: inherit;
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/EditableText/EditableText.tsx b/src/client/views/newlightbox/components/EditableText/EditableText.tsx
new file mode 100644
index 000000000..e9e7ca264
--- /dev/null
+++ b/src/client/views/newlightbox/components/EditableText/EditableText.tsx
@@ -0,0 +1,65 @@
+import * as React from 'react'
+import './EditableText.scss'
+import { Size } from 'browndash-components'
+
+export interface IEditableTextProps {
+ text: string
+ placeholder?: string
+ editing: boolean
+ onEdit: (newText: string) => void
+ setEditing: (editing: boolean) => void
+ backgroundColor?: string
+ size?: Size
+ height?: number
+}
+
+/**
+ * Editable Text is used for inline renaming of some text.
+ * It appears as normal UI text but transforms into a text input field when the user clicks on or focuses it.
+ * @param props
+ * @returns
+ */
+export const EditableText = (props: IEditableTextProps) => {
+ const {
+ editing,
+ height,
+ size,
+ text,
+ onEdit,
+ setEditing,
+ backgroundColor,
+ placeholder,
+ } = props
+
+ const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ onEdit(event.target.value)
+ }
+
+ return editing ? (
+ <input
+ style={{ background: backgroundColor, height: height }}
+ placeholder={placeholder}
+ size={1}
+ className="lb-editableText"
+ autoFocus
+ onChange={handleOnChange}
+ onBlur={() => setEditing(false)}
+ defaultValue={text}
+ ></input>
+ ) : (
+ <input
+ style={{ background: backgroundColor, height: height }}
+ placeholder={placeholder}
+ size={1}
+ className="lb-editableText"
+ autoFocus
+ onChange={handleOnChange}
+ onBlur={() => setEditing(false)}
+ defaultValue={text}
+ ></input>
+ // <div className="lb-displayText" onClick={(e) => {
+ // e.stopPropagation()
+ // setEditing(true)
+ // }}>{text}</div>
+ )
+}
diff --git a/src/client/views/newlightbox/components/EditableText/index.ts b/src/client/views/newlightbox/components/EditableText/index.ts
new file mode 100644
index 000000000..e3367b175
--- /dev/null
+++ b/src/client/views/newlightbox/components/EditableText/index.ts
@@ -0,0 +1 @@
+export * from './EditableText'
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
new file mode 100644
index 000000000..c86c63ba0
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
@@ -0,0 +1,176 @@
+@import '../../NewLightboxStyles.scss';
+
+.recommendation-container {
+ width: 100%;
+ height: fit-content;
+ min-height: 180px;
+ border-radius: 20px;
+ display: grid;
+ grid-template-columns: 0% 100%;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 5px 0px;
+ padding: 10px;
+ cursor: pointer;
+ transition: 0.2s ease;
+ border: $standard-border;
+ border-color: $gray-l2;
+ background: white;
+
+ &:hover {
+ // background: white !important;
+ transform: scale(1.02);
+ z-index: 0;
+
+ .title {
+ text-decoration: underline;
+ }
+ }
+
+ &.previewUrl {
+ grid-template-columns: calc(30% - 10px) 70%;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 5px 10px;
+ }
+
+ &.loading {
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ border: none;
+ grid-template-columns: calc(30% - 10px) 70%;
+ grid-template-rows: auto auto auto auto auto;
+ gap: 5px 10px;
+
+ .image-container,
+ .title,
+ .info,
+ .source,
+ .explainer,
+ .hide-rec {
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ }
+
+ .title {
+ border-radius: 20px;
+ }
+ }
+
+ .distance-container,
+ .type-container,
+ .source-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 5px;
+ }
+
+ .image-container {
+ grid-row: 2/5;
+ grid-column: 1;
+ border-radius: 20px;
+ overflow: hidden;
+
+ .image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ .title {
+ grid-row: 1;
+ grid-column: 1/3;
+ border-radius: 20px;
+ font-size: $h2-size;
+ font-weight: $h2-weight;
+ overflow: hidden;
+ border-radius: 0px;
+ min-height: 30px;
+ }
+
+ .info {
+ grid-row: 2;
+ grid-column: 2;
+ border-radius: 20px;
+ display: flex;
+ flex-direction: row;
+ gap: 5px;
+ font-size: $body-size;
+
+ .lb-type {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ }
+ }
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ font-size: $body-size;
+ }
+
+ .source {
+ grid-row: 3;
+ grid-column: 2;
+ border-radius: 20px;
+ font-size: $body-size;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+
+ .lb-source {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ border-radius: 10px;
+ white-space: nowrap;
+ max-width: 130px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ }
+
+ .explainer {
+ grid-row: 4;
+ grid-column: 2;
+ border-radius: 20px;
+ font-size: 10px;
+ width: 100%;
+ background: $blue-l1;
+ border-radius: 0;
+ padding: 10px;
+
+ .concepts-container {
+ display: flex;
+ flex-flow: row wrap;
+ margin-top: 3px;
+ gap: 3px;
+ .concept {
+ padding: 2px 7px !important;
+ background: $gray-l2;
+ }
+ }
+ }
+
+ .hide-rec {
+ grid-row: 5;
+ grid-column: 2;
+ border-radius: 20px;
+ font-size: $body-size;
+ display: flex;
+ align-items: center;
+ margin-top: 5px;
+ gap: 5px;
+ justify-content: flex-end;
+ text-transform: underline;
+ }
+
+ &.dark {
+ background: $black;
+ border-color: $white;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ border-color: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
new file mode 100644
index 000000000..c0d357ad5
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
@@ -0,0 +1,90 @@
+import * as React from 'react';
+import { IRecommendation } from "./utils";
+import './Recommendation.scss';
+import { getType } from '../../utils';
+import { FaEyeSlash } from 'react-icons/fa';
+import { NewLightboxView } from '../../NewLightboxView';
+import { DocumentManager } from '../../../../util/DocumentManager';
+import { Doc } from '../../../../../fields/Doc';
+import { Docs } from '../../../../documents/Documents';
+
+export const Recommendation = (props: IRecommendation) => {
+ const {title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId} = props
+
+ return <div className={`recommendation-container ${loading && 'loading'} ${previewUrl && 'previewUrl'}`} onClick={() => {
+ let doc: Doc | null = null;
+ if (source == "Dash" && docId) {
+ const docView = DocumentManager.Instance.getDocumentViewById(docId)
+ if (docView) {
+ doc = docView.rootDoc;
+ }
+ } else if (data) {
+ console.log(data, type)
+ switch(type) {
+ case "YouTube":
+ console.log('create ', type, 'document')
+ doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript: transcript })
+ break;
+ case "Video":
+ console.log('create ', type, 'document')
+ doc = Docs.Create.VideoDocument(data, { title: title, _width: 400, _height: 315, transcript: transcript })
+ break;
+ case "Webpage":
+ console.log('create ', type, 'document')
+ doc = Docs.Create.WebDocument(data, { title: title, text: text })
+ break;
+ case "HTML":
+ console.log('create ', type, 'document')
+ doc = Docs.Create.WebDocument(data, { title: title, text: text })
+ break;
+ case "Text":
+ console.log('create ', type, 'document')
+ doc = Docs.Create.TextDocument(data, { title: title, text: text })
+ break;
+ case "PDF":
+ console.log('create ', type, 'document')
+ doc = Docs.Create.PdfDocument(data, { title: title, text: text })
+ break;
+ }
+ }
+ if (doc !== null) NewLightboxView.SetNewLightboxDoc(doc)
+ }}>
+ {loading ?
+ <div className={`image-container`}>
+ </div>
+ :
+ previewUrl ? <div className={`image-container`}>
+ {<img className={`image`} src={previewUrl}></img>}
+ </div>
+ : null
+ }
+ <div className={`title`}>{title}</div>
+ <div className={`info`}>
+ {!loading && <div className={`type-container`}>
+ <div className={`lb-label`}>Type</div><div className={`lb-type`}>{getType(type!)}</div>
+ </div>}
+ {!loading && <div className={`distance-container`}>
+ <div className={`lb-label`}>Distance</div><div className={`lb-distance`}>{distance}</div>
+ </div>}
+ </div>
+ <div className={`source`}>
+ {!loading && <div className={`source-container`}>
+ <div className={`lb-label`}>Source</div><div className={`lb-source`}>{source}</div>
+ </div>}
+ </div>
+ <div className={`explainer`}>
+ {!loading &&
+ <div>
+ You are seeing this recommendation because this document also explores
+ <div className={`concepts-container`}>
+ {related_concepts?.map((val) => {
+ return <div className={'concept'}>{val}</div>
+ })}
+ </div>
+ </div>}
+ </div>
+ <div className={`hide-rec`}>
+ {!loading && <><div>Hide Recommendation</div><div style={{fontSize: 15, paddingRight: 5}}><FaEyeSlash/></div></>}
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Recommendation/index.ts b/src/client/views/newlightbox/components/Recommendation/index.ts
new file mode 100644
index 000000000..12ebf9d6e
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/index.ts
@@ -0,0 +1,2 @@
+export * from './utils'
+export * from './Recommendation' \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Recommendation/utils.ts b/src/client/views/newlightbox/components/Recommendation/utils.ts
new file mode 100644
index 000000000..796ce0eb0
--- /dev/null
+++ b/src/client/views/newlightbox/components/Recommendation/utils.ts
@@ -0,0 +1,23 @@
+import { DocumentType } from "../../../../documents/DocumentTypes"
+
+export interface IRecommendation {
+ loading?: boolean
+ type?: DocumentType | string,
+ data?: string,
+ title?: string,
+ text?: string,
+ source?: string,
+ previewUrl?: string,
+ transcript?: {
+ text: string,
+ start: number,
+ duration: number
+ }[],
+ embedding?: {
+ x: number,
+ y: number
+ },
+ distance?: number,
+ related_concepts?: string[],
+ docId?: string
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
new file mode 100644
index 000000000..e541e3f3c
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
@@ -0,0 +1,82 @@
+@import '../../NewLightboxStyles.scss';
+
+.skeletonDoc-container {
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 40px);
+ margin: 20px;
+ gap: 20px;
+
+ .header {
+ width: calc(100% - 20px);
+ height: 80px;
+ background: $gray-l2;
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ display: grid;
+ grid-template-rows: 60% 40%;
+ padding: 10px;
+ grid-template-columns: auto auto auto auto;
+ border-radius: 20px;
+
+ .title {
+ grid-row: 1;
+ grid-column: 1 / 5;
+ display: flex;
+ width: fit-content;
+ height: 100%;
+ min-width: 500px;
+ font-size: $title-size;
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ border-radius: 20px;
+ }
+
+ .type {
+ display: flex;
+ padding: 3px 7px;
+ width: fit-content;
+ height: fit-content;
+ margin-top: 8px;
+ min-height: 15px;
+ min-width: 60px;
+ grid-row: 2;
+ grid-column: 1;
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ border-radius: 20px;
+ }
+
+ .buttons-container {
+ grid-row: 1 / 3;
+ grid-column: 5;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 10px;
+
+ .button {
+ width: 50px;
+ height: 50px;
+ border-radius: 100%;
+ animation: skeleton-loading-l3 1s linear infinite alternate;
+ }
+ }
+
+ }
+
+ .content {
+ width: 100%;
+ flex: 1;
+ -webkit-flex: 1; /* Chrome */
+ background: $gray-l2;
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ border-radius: 20px;
+ }
+
+ // &.dark {
+ // background: $black;
+ // }
+
+ // &.light,
+ // &.default {
+ // background: $white;
+ // }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx
new file mode 100644
index 000000000..50cee893f
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.tsx
@@ -0,0 +1,22 @@
+import './SkeletonDoc.scss';
+import { ISkeletonDoc } from "./utils";
+import * as React from 'react';
+
+export const SkeletonDoc = (props: ISkeletonDoc) => {
+ const { type, data } = props
+
+ return <div className={`skeletonDoc-container`}>
+ <div className={`header`}>
+ <div className={`title`}></div>
+ <div className={`type`}></div>
+ <div className={`tags`}></div>
+ <div className={`buttons-container`}>
+ <div className={`button`}></div>
+ <div className={`button`}></div>
+ </div>
+ </div>
+ <div className={`content`}>
+ {data}
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/index.ts b/src/client/views/newlightbox/components/SkeletonDoc/index.ts
new file mode 100644
index 000000000..396b7272b
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/index.ts
@@ -0,0 +1 @@
+export * from './SkeletonDoc' \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/utils.ts b/src/client/views/newlightbox/components/SkeletonDoc/utils.ts
new file mode 100644
index 000000000..81c32c328
--- /dev/null
+++ b/src/client/views/newlightbox/components/SkeletonDoc/utils.ts
@@ -0,0 +1,5 @@
+import { IRecommendation } from "../Recommendation";
+
+export interface ISkeletonDoc extends IRecommendation {
+
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/Template.scss b/src/client/views/newlightbox/components/Template/Template.scss
new file mode 100644
index 000000000..5b72ddaf9
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/Template.scss
@@ -0,0 +1,15 @@
+@import '../../NewLightboxStyles.scss';
+
+.template-container {
+ width: 100vw;
+ height: 100vh;
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/Template.tsx b/src/client/views/newlightbox/components/Template/Template.tsx
new file mode 100644
index 000000000..9c6f0f59c
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/Template.tsx
@@ -0,0 +1,10 @@
+import './Template.scss';
+import * as React from 'react';
+import { ITemplate } from "./utils";
+
+export const Template = (props: ITemplate) => {
+
+ return <div className={`template-container`}>
+
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/index.ts b/src/client/views/newlightbox/components/Template/index.ts
new file mode 100644
index 000000000..36b5f3f46
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/index.ts
@@ -0,0 +1 @@
+export * from './Template' \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/Template/utils.ts b/src/client/views/newlightbox/components/Template/utils.ts
new file mode 100644
index 000000000..965e653ec
--- /dev/null
+++ b/src/client/views/newlightbox/components/Template/utils.ts
@@ -0,0 +1,3 @@
+export interface ITemplate {
+
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/components/index.ts b/src/client/views/newlightbox/components/index.ts
new file mode 100644
index 000000000..3f9128690
--- /dev/null
+++ b/src/client/views/newlightbox/components/index.ts
@@ -0,0 +1,3 @@
+export * from './Template'
+export * from './Recommendation'
+export * from './SkeletonDoc' \ No newline at end of file
diff --git a/src/client/views/newlightbox/utils.ts b/src/client/views/newlightbox/utils.ts
new file mode 100644
index 000000000..6016abca4
--- /dev/null
+++ b/src/client/views/newlightbox/utils.ts
@@ -0,0 +1,121 @@
+import { DocumentType } from "../../documents/DocumentTypes";
+import { IRecommendation } from "./components";
+
+export interface IDocRequest {
+ id: string,
+ title: string,
+ text: string,
+ type: string
+}
+
+export const fetchRecommendations = async (src: string, query: string, docs?: IDocRequest[], dummy?: boolean) => {
+ console.log("[rec] making request")
+ if (dummy) {
+ return {
+ "recommendations": dummyRecs,
+ "keywords": dummyKeywords,
+ "num_recommendations": 4,
+ "max_x": 100,
+ "max_y": 100,
+ "min_x": 0,
+ "min_y": 0
+
+ };
+ }
+ const response = await fetch('http://127.0.0.1:8000/recommend', {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ "src": src,
+ "query": query,
+ "docs": docs
+ })
+ })
+ const data = await response.json();
+
+ return data;
+}
+
+export const fetchKeywords = async (text: string, n: number, dummy?: boolean) => {
+ console.log("[fetchKeywords]")
+ if (dummy) {
+ return {
+ "keywords": dummyKeywords
+ };
+ }
+ const response = await fetch('http://127.0.0.1:8000/keywords', {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ "text": text,
+ "n": n
+ })
+ })
+ const data = await response.json()
+ return data;
+}
+
+export const getType = (type: DocumentType | string) => {
+ switch(type) {
+ case DocumentType.AUDIO:
+ return "Audio"
+ case DocumentType.VID:
+ return "Video"
+ case DocumentType.PDF:
+ return "PDF"
+ case DocumentType.WEB:
+ return "Webpage"
+ case "YouTube":
+ return "Video"
+ case "HTML":
+ return "Webpage"
+ default:
+ return "Unknown: " + type
+ }
+}
+
+const dummyRecs = {
+ "a": {
+ title: 'Vannevar Bush - American Engineer',
+ previewUrl: 'https://cdn.britannica.com/98/23598-004-1E6A382E/Vannevar-Bush-Differential-Analyzer-1935.jpg',
+ type: 'web',
+ distance: 2.3,
+ source: 'www.britannica.com',
+ related_concepts: ['vannevar bush', 'knowledge'],
+ embedding: {
+ x: 0,
+ y: 0
+ }
+ },
+ "b": {
+ title: "From Memex to hypertext: Vannevar Bush and the mind's machine",
+ type: 'pdf',
+ distance: 5.4,
+ source: 'Google Scholar',
+ related_concepts: ['memex', 'vannevar bush', 'hypertext'],
+ },
+ "c": {
+ title: 'How the hyperlink changed everything | Small Thing Big Idea, a TED series',
+ previewUrl: 'https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/b17d043f-2642-4117-a913-52204505513f/MargaretGouldStewart_2018V-embed.jpg?u%5Br%5D=2&u%5Bs%5D=0.5&u%5Ba%5D=0.8&u%5Bt%5D=0.03&quality=82w=640',
+ type: 'youtube',
+ distance: 5.3,
+ source: 'www.youtube.com',
+ related_concepts: ['User Control', 'Explanations']
+ },
+ "d": {
+ title: 'Recommender Systems: Behind the Scenes of Machine Learning-Based Personalization',
+ previewUrl: 'https://sloanreview.mit.edu/wp-content/uploads/2018/10/MAG-Ransbotham-Ratings-Recommendations-1200X627-1200x627.jpg',
+ type: 'pdf',
+ distance: 9.3,
+ source: 'www.altexsoft.com',
+ related_concepts: ['User Control', 'Explanations']
+ }
+}
+
+const dummyKeywords = ['user control', 'vannevar bush', 'hypermedia', 'hypertext'] \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index f1627e1e1..b25540dd3 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -115,7 +115,10 @@
width: 100%;
height: 100%;
transition: inherit;
-
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
.sharingIndicator {
height: 30px;
width: 30px;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b6f1626f8..6b37dc419 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1232,7 +1232,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const renderDoc = this.renderDoc({
borderRadius: this.borderRounding,
outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
- border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined,
+ border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined,
boxShadow,
clipPath: borderPath?.clipPath,
});
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 5bba51ec8..e2fe0bcf1 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,6 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
+import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, OrientationType, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -33,6 +34,7 @@ import { OpenWhere } from '../DocumentView';
import { RichTextMenu } from '../formattedText/RichTextMenu';
import { WebBox } from '../WebBox';
import { FontIconBadge } from './FontIconBadge';
+import * as fa from 'react-icons/fa'
import './FontIconBox.scss';
export enum ButtonType {
@@ -309,19 +311,24 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
// Get items to place into the list
- const list = this.buttonList
+ const list: IListItemProps[] = this.buttonList
.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
.map(value => (
- <div
- className="list-item"
- key={value}
- style={{
- fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined,
- backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
- }}
- onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}>
- {value[0].toUpperCase() + value.slice(1)}
- </div>
+ {
+ text: value,
+ shortcut: '#',
+ icon: <fa.FaCaretUp/>
+ }
+ // <div
+ // className="list-item"
+ // key={value}
+ // style={{
+ // fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined,
+ // backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
+ // }}
+ // onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}>
+ // {value[0].toUpperCase() + value.slice(1)}
+ // </div>
));
const label =
@@ -332,6 +339,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
);
return (
+ <Dropdown type={Type.PRIM} dropdownType={DropdownType.CLICK} items={list} location={OrientationType.LEFT}/>
+ )
+
+ return (
<div
className={`menuButton ${this.type} ${active}`}
style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }}
@@ -401,6 +412,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
{this.label}
</div>
);
+
+ return (
+ <ColorPicker onChange={(e) => {
+ this.colorPickerClosed = !this.colorPickerClosed;
+ this.noTooltip = !this.colorPickerClosed;
+ setTimeout(() => Doc.UnBrushAllDocs());
+ e.stopPropagation();
+ }}/>
+ )
return (
<div
@@ -453,6 +473,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
{this.label}
</div>
);
+ console.log("switchToggle", switchToggle, this.rootDoc.title)
+ return (
+ <Toggle toggleType={ToggleType.BUTTON} type={Type.PRIM} toggleStatus={switchToggle} text={buttonText} color={color} icon={this.Icon(color)!} label={this.label}/>
+ )
if (switchToggle) {
return (
@@ -480,6 +504,11 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
@computed get defaultButton() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ return (
+ <IconButton icon={this.Icon(color)!} label={this.label}/>
+ )
+
return (
<div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
<div className="menuButton-wrap">
@@ -548,18 +577,20 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
break;
case ButtonType.ClickButton:
case ButtonType.ToolButton: button = (
- <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
- {this.Icon(color)}
- {label()}
- </div>
+ <IconButton color={color} icon={this.Icon(color)!} label={this.label}/>
+ // <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
+ // {this.Icon(color)}
+ // {label()}
+ // </div>
);
break;
case ButtonType.MenuButton: button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
- {this.Icon(color)}
- {label(true)}
- <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
- </div>
+ <IconButton size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/>
+ // <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
+ // {this.Icon(color)}
+ // {label(true)}
+ // <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
+ // </div>
);
break;
}
diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
deleted file mode 100644
index 74c3c563c..000000000
--- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { Component } from 'react';
-import { BoolCast, StrCast } from '../../../../../fields/Types';
-import { IButtonProps } from '../ButtonInterface';
-import { ColorState, SketchPicker } from 'react-color';
-import { ScriptField } from '../../../../../fields/ScriptField';
-import { Doc } from '../../../../../fields/Doc';
-import { FontIconBox } from '../FontIconBox';
-
-export class ColorDropdown extends Component<IButtonProps> {
- render() {
- const active: string = StrCast(this.props.rootDoc.dropDownOpen);
-
- const script: string = StrCast(this.props.rootDoc.script);
- const scriptCheck: string = script + '(undefined, true)';
- const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result;
-
- const stroke: boolean = false;
- // if (script === "setStrokeColor") {
- // stroke = true;
- // const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result;
- // const width = 20 + (checkWidth / 100) * 70;
- // const height = 20 + (checkWidth / 100) * 70;
- // strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />);
- // }
-
- const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb'];
-
- const colorBox = (func: (color: ColorState) => void) => <SketchPicker disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : '#FFFFFF'} presetColors={colorOptions} />;
- const label =
- !this.props.label || !FontIconBox.GetShowLabels() ? null : (
- <div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: 'absolute' }}>
- {this.props.label}
- </div>
- );
-
- const dropdownCaret = (
- <div className="menuButton-dropDown" style={{ borderBottomRightRadius: active ? 0 : undefined }}>
- <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" />
- </div>
- );
-
- const click = (value: ColorState) => {
- const hex: string = value.hex;
- const s = ScriptField.MakeScript(script + '("' + hex + '", false)');
- if (s) {
- s.script.run().result;
- }
- };
- return (
- <div
- className={`menuButton ${this.props.type} ${active}`}
- style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }}
- onClick={() => (this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen)}
- onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
- <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : '#FFFFFF' }} />
- {label}
- {/* {dropdownCaret} */}
- {this.props.rootDoc.dropDownOpen ? (
- <div>
- <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
- {colorBox(click)}
- </div>
- <div
- className="dropbox-background"
- onClick={e => {
- e.stopPropagation();
- this.props.rootDoc.dropDownOpen = false;
- }}
- />
- </div>
- ) : null}
- </div>
- );
- }
-}
diff --git a/src/client/views/nodes/button/colorDropdown/index.ts b/src/client/views/nodes/button/colorDropdown/index.ts
deleted file mode 100644
index 1147d6457..000000000
--- a/src/client/views/nodes/button/colorDropdown/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './ColorDropdown'; \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index e8865b918..5516cf205 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -9,16 +9,21 @@
background: none;
z-index: 1000;
padding: 0px;
+ overflow: scroll;
cursor: default;
.searchBox-bar {
width: 100%;
- height: 35px;
+ height: fit-content;
display: flex;
justify-content: center;
align-items: center;
background-color: none;
padding: 5px;
+ top: 0px;
+ position: sticky;
+ background: $light-gray;
+ border-bottom: $standard-border;
.searchBox-type {
display: block;
@@ -42,34 +47,66 @@
}
}
- .searchBox-results-container {
+ .section-header {
+
+ .section-title {
+ font-size: $body-text;
+ font-weight: 600;
+ }
+
+ .section-subtitle {
+ display: flex;
+ color: $light-gray;
+ }
+
+ padding: 5px 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ background: $medium-blue;
+ color: white;
+ }
+
+ .searchBox-recommendations-container {
display: flex;
flex-direction: column;
width: 100%;
- height: 100%;
+ height: fit-content;
justify-content: "center";
-
- .searchBox-results-count {
+
+ .searchBox-recommendations-view {
+ margin-top: 10px;
display: flex;
- color: gray;
- margin-left: 5px;
+ width: 100%;
+ height: fit-content;
+ flex-direction: column;
+ gap: 10px;
+ padding: 0px 10px;
+
+
}
+ }
+
+ .searchBox-results-container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: fit-content;
+ justify-content: "center";
- .searchBox-results-scroll-view {
- margin-top: 10px;
+ .searchBox-results-view {
display: inline-block;
width: 100%;
- height: calc(100% - 55px);
- overflow-y: scroll;
+ height: fit-content;
.searchBox-results-scroll-view-result {
display: inline-block;
vertical-align: middle;
width: 100%;
- height: 50px;
+ height: fit-content;
cursor: pointer;
font-size: 15px;
- padding: 11px;
+ padding: 10px;
&.searchBox-results-scroll-view-result-selected {
background: #999;
@@ -81,6 +118,8 @@
width: calc(100% - 45px);
text-align: left;
overflow: hidden;
+ max-height: 2.4em;
+ line-height: 1.2em;
text-overflow: ellipsis;
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index d13c09443..12c25ca09 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -15,6 +15,8 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
import { ViewBoxBaseComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import './SearchBox.scss';
+import { fetchRecommendations } from '../newlightbox/utils';
+import { IRecommendation, Recommendation } from '../newlightbox/components';
const DAMPENING_FACTOR = 0.9;
const MAX_ITERATIONS = 25;
@@ -43,6 +45,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
@observable _searchString = '';
@observable _docTypeString = 'all';
@observable _results: Map<Doc, string[]> = new Map<Doc, string[]>();
+ @observable _recommendations: IRecommendation[] = [];
@observable _pageRanks: Map<Doc, number> = new Map<Doc, number>();
@observable _linkedDocsOut: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
@observable _linkedDocsIn: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
@@ -394,6 +397,38 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
if (query) {
this.searchCollection(query);
+ const response = await fetchRecommendations('', query, [], true)
+ const recs = response.recommendations
+ const recommendations:IRecommendation[] = []
+ for (const key in recs) {
+ const title = recs[key].title;
+ console.log(title);
+ const url = recs[key].url
+ const type = recs[key].type
+ const text = recs[key].text
+ const transcript = recs[key].transcript
+ const previewUrl = recs[key].previewUrl
+ const embedding = recs[key].embedding
+ const distance = recs[key].distance
+ const source = recs[key].source
+ const related_concepts = recs[key].related_concepts
+ const docId = recs[key].doc_id
+ recommendations.push({
+ title: title,
+ data: url,
+ type: type,
+ text: text,
+ transcript: transcript,
+ previewUrl: previewUrl,
+ embedding: embedding,
+ distance: Math.round(distance * 100) / 100,
+ source: source,
+ related_concepts: related_concepts,
+ docId: docId
+ })
+ }
+ const setRecommendations = action(() => this._recommendations = recommendations)
+ setRecommendations()
}
};
@@ -488,6 +523,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
}
});
+ const recommendationsJSX: JSX.Element[] = this._recommendations.map((props) => (
+ <Recommendation {...props}/>
+ ))
+
return (
<div style={{ pointerEvents: 'all' }} className="searchBox-container">
<div className="searchBox-bar">
@@ -512,10 +551,20 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
ref={this._inputRef}
/>
</div>
- <div className="searchBox-results-container">
- <div className="searchBox-results-count">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
- <div className="searchBox-results-scroll-view">{resultsJSX}</div>
- </div>
+ {resultsJSX.length > 0 && <div className="searchBox-results-container">
+ <div className="section-header">
+ <div className="section-title">Results</div>
+ <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ </div>
+ <div className="searchBox-results-view">{resultsJSX}</div>
+ </div>}
+ {recommendationsJSX.length > 0 && <div className="searchBox-recommendations-container">
+ <div className="section-header">
+ <div className="section-title">Recommendations</div>
+ <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+ </div>
+ <div className="searchBox-recommendations-view">{recommendationsJSX}</div>
+ </div>}
</div>
);
}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 20cf563c1..1d9c25e3c 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,7 +1,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@mui/material';
-import { Button, FontSize, IconButton, Size } from 'browndash-components';
+import { Button, IconButton, FontSize, Size, Type } from 'browndash-components';
import { action, computed, observable, reaction } from 'mobx';
+import { Tooltip } from '@mui/material';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaBug, FaCamera, FaStamp } from 'react-icons/fa';
@@ -98,6 +98,7 @@ export class TopBar extends React.Component {
tooltip="Browsing mode for directly navigating to documents"
size={Size.SMALL}
color={'white'}
+ style={{fontWeight: 700, fontSize: '1rem'}}
onClick={(e: React.MouseEvent) => {
const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' });
@@ -113,13 +114,6 @@ export class TopBar extends React.Component {
dashView?.showContextMenu(e.clientX + 20, e.clientY + 30);
}}
/>
- <Button
- text={GetEffectiveAcl(Doc.GetProto(Doc.ActiveDashboard)) === AclAdmin ? 'Share' : 'View Original'}
- onClick={() => {
- SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
- }}
- size={Size.SMALL}
- />
{!Doc.noviceMode && (
<IconButton
tooltip="Work on a copy of the dashboard layout"
@@ -145,6 +139,14 @@ export class TopBar extends React.Component {
@computed get topbarRight() {
return (
<div className="topbar-right">
+ {Doc.ActiveDashboard && <Button
+ text={GetEffectiveAcl(Doc.GetProto(Doc.ActiveDashboard)) === AclAdmin ? 'Share' : 'View Original'}
+ type={Type.TERT}
+ onClick={() => {
+ SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
+ }}
+ size={Size.SMALL}
+ />}
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ServerStats.Instance.open} icon={<FaStamp />} />
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ReportManager.Instance.open} icon={<FaBug />} />
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 805da1d43..15088ddb2 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -34,7 +34,7 @@ const compiler = webpack(config);
export type RouteSetter = (server: RouteManager) => void;
//export let disconnect: Function;
-export let resolvedPorts: { server: number; socket: number } = { server: 1050, socket: 4321 };
+export let resolvedPorts: { server: number; socket: number } = { server: 3000, socket: 4321 };
export let resolvedServerUrl: string;
export default async function InitializeServer(routeSetter: RouteSetter) {