aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgeireann <geireann.lindfield@gmail.com>2023-06-06 14:36:21 -0400
committergeireann <geireann.lindfield@gmail.com>2023-06-06 14:36:21 -0400
commit3958654925e92b1046b3ed5d49160514b6e48258 (patch)
treedb02b6e74a03306be67b5e6071fa2272b1f7fc66 /src
parent0fc47fefcb72592bd34e238949db9e98a84b8a63 (diff)
added rec stuff and begun updating components
Diffstat (limited to 'src')
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss7
-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.scss90
-rw-r--r--src/client/views/newlightbox/Header/LightboxHeader.tsx67
-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.scss131
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.tsx192
-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.ts110
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx34
-rw-r--r--src/server/server_Initialization.ts2
40 files changed, 1826 insertions, 19 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 50f451c0a..bf7df2892 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';
@@ -1013,6 +1014,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/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/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..2872a383b
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.scss
@@ -0,0 +1,90 @@
+@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;
+ }
+ }
+
+ .lb-button2 {
+ padding: 3px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ align-items: center;
+ transition: 0.2s ease;
+ gap: 5px;
+ font-size: 15px;
+ height: fit-content;
+ border-radius: 3px;
+
+ &:hover {
+ background: $gray-l2;
+ transform: scale(1.01);
+ }
+ }
+
+ &.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..a542d2943
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -0,0 +1,67 @@
+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, Size, Type } from 'browndash-components';
+import { MdExplore, MdTravelExplore } from 'react-icons/md'
+import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'
+import { Doc } from '../../../../fields/Doc';
+
+
+export const NewLightboxHeader = (props: INewLightboxHeader) => {
+ const {height = 100, width} = props;
+ const [doc, setDoc] = React.useState<Doc | undefined>(NewLightboxView.LightboxDoc)
+ const [editing, setEditing] = React.useState<boolean>(false)
+ const [title, setTitle] = React.useState<JSX.Element | null>(
+ (null)
+ )
+ React.useEffect(() => {
+ let lbDoc = NewLightboxView.LightboxDoc
+ setDoc(lbDoc)
+ if (lbDoc) {
+ setTitle(
+ <EditableText
+ editing={editing}
+ text={StrCast(lbDoc.title)}
+ onEdit={(newText: string) => {
+ if(lbDoc) lbDoc.title = newText;
+ }}
+ setEditing={setEditing}
+ />)
+ }
+ }, [NewLightboxView.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'}}>
+ <div className={`lb-button2`} onClick={() => setSaved(!saved)}>
+ {saved ? <BsBookmarkFill/> : <BsBookmark/>}
+ </div>
+ <div className={`lb-button2`} onClick={() => setSaved(!saved)}>
+ {saved ? <BsBookmarkFill/> : <BsBookmark/>}
+ </div>
+ </div>
+ <div style={{gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+ <div className={`lb-button ${NewLightboxView.ExploreMode}`} onClick={() => {
+ console.log(NewLightboxView.ExploreMode)
+ NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)
+ }}>
+ <MdTravelExplore/>
+ t-SNE 2D Embeddings
+ </div>
+ </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..e59834353
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
@@ -0,0 +1,131 @@
+@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;
+ }
+
+ .remove {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: $text-color-lm;
+ padding: 1.5px;
+ border-radius: 2px;
+
+ &:hover {
+ background: $gray-l1;
+ }
+ }
+ }
+
+ }
+ }
+
+ &.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..674a501a7
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
@@ -0,0 +1,192 @@
+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';
+
+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('fetching keywords w/: ', text)
+ const response = await fetchKeywords(text, 5, true)
+ 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
+ let contextDocs: Doc[] = DocListCast(DocCast(NewLightboxView.LightboxDoc?.context).data)
+ let docId = NewLightboxView.LightboxDoc && NewLightboxView.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, dashDocs, 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
+ })
+ 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}
+ <div className={`remove`} onClick={() => {
+ let kw = keywordsLoc
+ kw.splice(ind)
+ NewLightboxView.SetKeywords(kw)
+ }}>{<GrClose/>}</div>
+ </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..29b83c4e0
--- /dev/null
+++ b/src/client/views/newlightbox/utils.ts
@@ -0,0 +1,110 @@
+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("making request with: ", query)
+ if (dummy) {
+ return dummyRecs;
+ }
+ 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("making request with: ", text)
+ if (dummy) {
+ return 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: IRecommendation[] = [
+ {
+ 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
+ }
+ },
+ {
+ 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'],
+ },
+ {
+ 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']
+ },
+ {
+ 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/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 5f4117a8d..b2223519e 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { Button, ColorPicker, IconButton, Size } from 'browndash-components';
+import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, OrientationType, Size, Type } from 'browndash-components';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -33,6 +33,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 +310,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 +338,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' }}
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) {