aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts4
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx38
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss39
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx309
-rw-r--r--src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss175
-rw-r--r--src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx111
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss8
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx2
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx16
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx2
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx3
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx27
13 files changed, 705 insertions, 31 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f7070a862..dd72051fa 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -688,8 +688,8 @@ export class CurrentUserUtils {
static schemaTools():Button[] {
return [
{title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
- {title: "1 Line",toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
- ];
+ {title: "1 Line", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
+ {title: "DataViz", toolTip: "Turn Schema Table into Data Visualization Doc", btnType: ButtonType.ClickButton, icon: "chart-bar", scripts:{ onClick: '{ datavizFromSchema()'} }, ];
}
static webTools() {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c71c72257..f2a1ca57e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -31,6 +31,7 @@ import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
import { ReportManager } from '../util/reportManager/ReportManager';
import { ComponentDecorations } from './ComponentDecorations';
+import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
import { ContextMenu } from './ContextMenu';
import { DashboardView } from './DashboardView';
import { DictationOverlay } from './DictationOverlay';
@@ -1071,6 +1072,7 @@ export class MainView extends ObservableReactComponent<{}> {
<OverlayView />
{/* {this.mapBoxHack} */}
<GPTPopup key="gptpopup" />
+ <SchemaCSVPopUp key="schemacsvpopup" />
<GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
{/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
</div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1f4688729..88cdd51ff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -52,6 +52,7 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
+import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
export type collectionFreeformViewProps = {
NativeWidth?: () => number;
@@ -1982,3 +1983,40 @@ ScriptingGlobals.add(function bringToFront() {
ScriptingGlobals.add(function sendToBack(doc: Doc) {
SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
});
+ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
+ SelectionManager.Views.forEach(view => {
+ if (!view.layoutDoc.schema_columnKeys){
+ view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date'])
+ }
+ const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key!="text");
+ if (!keys) return;
+
+ const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
+ let csvRows = [];
+ csvRows.push(keys.join(','));
+ for (let i=0; i < children.length; i++) {
+ let eachRow = [];
+ for (let j=0; j<keys.length; j++){
+ var cell = children[i][keys[j]];
+ if (cell && cell as string) cell = cell.toString().replace(/\,/g, '');
+ eachRow.push(cell);
+ }
+ csvRows.push(eachRow);
+ }
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+ const options = { x:0, y:-300, title: 'schemaTable', _width: 300, _height: 100, type: 'text/csv' };
+ const file = new File([blob], 'schemaTable', options);
+ const loading = Docs.Create.LoadingDocument(file, options);
+ loading.presentation_openInLightbox = true;
+ DocUtils.uploadFileToDoc(file, {}, loading);
+
+ if (view.ComponentView?.addDocument) {
+ // loading.dataViz_fromSchema = true;
+ loading.dataViz_asSchema = view.layoutDoc;
+ SchemaCSVPopUp.Instance.setView(view);
+ SchemaCSVPopUp.Instance.setTarget(view.layoutDoc);
+ SchemaCSVPopUp.Instance.setDataVizDoc(loading);
+ SchemaCSVPopUp.Instance.setVisible(true);
+ }
+ })
+});
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index 430446c06..a3132dc6e 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -1,4 +1,4 @@
-.dataviz {
+.dataViz-box {
overflow: auto;
height: 100%;
width: 100%;
@@ -7,9 +7,46 @@
display: flex;
flex-direction: row;
}
+
+ .dataviz-overlayButton-sidebar {
+ background: #121721;
+ height: 25px;
+ width: 25px;
+ right: 5px;
+ display: flex;
+ position: absolute;
+ align-items: center;
+ justify-content: center;
+ border-radius: 3px;
+ pointer-events: all;
+ z-index: 1; // so it appears on top of the document's title, if shown
+
+ // box-shadow: $standard-box-shadow;
+ // transition: 0.2s;
+
+ &:hover {
+ filter: brightness(0.85);
+ }
+ }
+
+ .dataviz-sidebar {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 100%;
+ }
.button-container {
pointer-events: unset;
}
+
+ .dataVizBox-annotationLayer{
+ position: absolute;
+ transform-origin: left top;
+ top: 0;
+ width: 100%;
+ pointer-events: none;
+ mix-blend-mode: multiply;
+ }
}
.start-message {
margin: 10px;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index e31c04c40..56c130e0f 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,12 +1,12 @@
-import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, ObservableMap } from 'mobx';
+import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
+import { action, computed, observable, ObservableMap, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, Field, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
-import { Cast, CsvCast, StrCast } from '../../../../fields/Types';
+import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
-import { Docs } from '../../../documents/Documents';
+import { DocUtils, Docs } from '../../../documents/Documents';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import { PinProps } from '../trails';
@@ -15,6 +15,22 @@ import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
import './DataVizBox.scss';
+import { UndoManager, undoable } from '../../../util/UndoManager';
+import { SidebarAnnos } from '../../SidebarAnnos';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Utils, emptyFunction, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { DocumentView } from '../DocumentView';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { TraceMobx } from '../../../../fields/util';
+import { MarqueeAnnotator } from '../../MarqueeAnnotator';
+import { InkTool } from '../../../../fields/InkField';
+import { AnchorMenu } from '../../pdf/AnchorMenu';
+import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
+import { ContextMenu } from '../../ContextMenu';
+import { gptImageCall } from '../../../apis/gpt/GPT';
+import { Networking } from '../../../Network';
+import { listSpec } from '../../../../fields/Schema';
export enum DataVizView {
TABLE = 'table',
@@ -25,6 +41,44 @@ export enum DataVizView {
@observer
export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+ private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _ffref = React.createRef<CollectionFreeFormView>();
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
+ @observable schemaDataVizChildren: any;
+ @observable _marqueeing: number[] | undefined;
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @computed get annotationLayer() {
+ TraceMobx();
+ return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
+ }
+ marqueeDown = (e: React.PointerEvent) => {
+ if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ setupMoveUpEvents(
+ this,
+ e,
+ action(e => {
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ //this._marqueeing = [e.pageX, e.pageY];
+ this._marqueeing = [e.clientX, e.clientY];
+ return true;
+ }),
+ returnFalse,
+ () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+ false
+ );
+ }
+ };
+ @action
+ finishMarquee = () => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this._marqueeing = undefined;
+ this._props.select(false);
+ };
+ savedAnnotations = () => this._savedAnnotations;
+
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(DataVizBox, fieldStr);
}
@@ -32,6 +86,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records
static dataset = new ObservableMap<string, { [key: string]: string }[]>();
private _vizRenderer: LineChart | Histogram | PieChart | undefined;
+ private _sidebarRef = React.createRef<SidebarAnnos>();
// all CSV records in the dataset (that aren't an empty row)
@computed.struct get records() {
@@ -74,10 +129,17 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return func() ?? false;
};
getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
+ const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
const anchor = !pinProps
? this.Document
: this._vizRenderer?.getAnchor(pinProps) ??
+ visibleAnchor ??
Docs.Create.ConfigDocument({
+ title: 'ImgAnchor:' + this.Document.title,
+ config_panX: NumCast(this.layoutDoc._freeform_panX),
+ config_panY: NumCast(this.layoutDoc._freeform_panY),
+ config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
+ annotationOn: this.Document,
// when we clear selection -> we should have it so chartBox getAnchor returns undefined
// this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
/*put in some options*/
@@ -93,10 +155,101 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
anchor[key] = this.layoutDoc[key];
}
});
+
this.addDocument(anchor);
+ //addAsAnnotation && this.addDocument(anchor);
return anchor;
};
+ createNoteAnnotation = () => {
+ const createFunc = undoable(
+ action(() => {
+ const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
+ }),
+ 'create note annotation'
+ );
+ if (!this.layoutDoc.layout_showSidebar) {
+ this.toggleSidebar();
+ setTimeout(createFunc);
+ } else createFunc();
+ };
+
+ @observable _showSidebar = false;
+ @observable _previewNativeWidth: Opt<number> = undefined;
+ @observable _previewWidth: Opt<number> = undefined;
+ @action
+ toggleSidebar = () => {
+ const prevWidth = this.sidebarWidth();
+ this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
+ this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
+ };
+ @computed get SidebarShown() {
+ return this.layoutDoc._layout_showSidebar ? true : false;
+ }
+ @computed get sidebarHandle() {
+ return (
+ <div
+ className="dataviz-overlayButton-sidebar"
+ key="sidebar"
+ title="Toggle Sidebar"
+ style={{
+ display: !this._props.isContentActive() ? 'none' : undefined,
+ top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
+ backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+ }}
+ onPointerDown={this.sidebarBtnDown}>
+ <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+ </div>
+ );
+ }
+ /**
+ * Toggle sidebar onclick the tiny comment button on the top right corner
+ * @param e
+ */
+ sidebarBtnDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) =>
+ runInAction(() => {
+ const localDelta = this._props
+ .ScreenToLocalTransform()
+ .scale(this._props.NativeDimScaling?.() || 1)
+ .transformDirection(delta[0], delta[1]);
+ const fullWidth = this._props.width;
+ const mapWidth = fullWidth! - this.sidebarWidth();
+ if (this.sidebarWidth() + localDelta[0] > 0) {
+ this.layoutDoc._layout_showSidebar = true;
+ this.layoutDoc._width = fullWidth! + localDelta[0];
+ this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth! + localDelta[0])).toString() + '%';
+ } else {
+ this.layoutDoc._layout_showSidebar = false;
+ this.layoutDoc._width = mapWidth;
+ this.layoutDoc._layout_sidebarWidthPercent = '0%';
+ }
+ return false;
+ }),
+ emptyFunction,
+ () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
+ );
+ };
+ getView = async (doc: Doc) => {
+ if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
+ return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ };
+ @computed get sidebarWidthPercent() {
+ return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+ }
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
+ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+ if (!this.SidebarShown) this.toggleSidebar();
+ return this.addDocument(doc, sidebarKey);
+ };
+ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
+
componentDidMount() {
this._props.setContentView?.(this);
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
@@ -116,13 +269,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
layoutDoc: this.layoutDoc,
records: this.records,
axes: this.axes,
+ //width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
width: (this._props.PanelWidth() / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
};
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this._props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} />;
case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)}
@@ -130,15 +284,81 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
} // prettier-ignore
}
+ @action
+ onPointerDown = (e: React.PointerEvent): void => {
+ if ((this.Document._freeform_scale || 1) !== 1) return;
+ if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ this._props.select(false);
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ this._marqueeing = [e.clientX, e.clientY];
+ const target = e.target as any;
+ if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
+ } else {
+ // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
+ setTimeout(
+ action(() => (this._marqueeing = undefined)),
+ 100
+ ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+
+ document.addEventListener('pointerup', this.onSelectEnd);
+ }
+ }
+ };
+
+ @action
+ onSelectEnd = (e: PointerEvent): void => {
+ this._props.select(false);
+ document.removeEventListener('pointerup', this.onSelectEnd);
+
+ const sel = window.getSelection();
+ if (sel) {
+ AnchorMenu.Instance.setSelectedText(sel.toString());
+ }
+
+ if (sel?.type === 'Range') {
+ AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
+ }
+
+ // Changing which document to add the annotation to (the currently selected PDF)
+ GPTPopup.Instance.setSidebarId('data_sidebar');
+ GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+ };
+
+ @action
+ updateSchemaViz = () => {
+ const getFrom = DocCast(this.layoutDoc.dataViz_asSchema)
+ const keys = Cast(getFrom.schema_columnKeys, listSpec('string'))?.filter(key => key!="text");
+ if (!keys) return;
+ const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
+ var current: {[key: string]: string}[] = []
+ for (let i=1; i<children.length; i++){
+ var row: {[key:string]: string} = {};
+ if (children[i]){
+ for (let j=0; j<keys.length; j++){
+ var cell = children[i][keys[j]];
+ if (cell && cell as string) cell = cell.toString().replace(/\,/g, '');
+ row[keys[j]] = StrCast(cell)
+ }
+ }
+ current.push(row)
+ }
+ DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current )
+ }
+
render() {
- const scale = this._props.NativeDimScaling?.() || 1;
+ if (this.layoutDoc && this.layoutDoc.dataViz_asSchema){
+ this.schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length
+ this.updateSchemaViz();
+ }
+ const scale = this._props.NativeDimScaling?.() || 1;
return !this.records.length ? (
// displays how to get data into the DataVizBox if its empty
<div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
) : (
<div
- className="dataViz"
+ className="dataViz-box"
+ onPointerDown={this.marqueeDown}
style={{
pointerEvents: this._props.isContentActive() === true ? 'all' : 'none',
width: `${100 / scale}%`,
@@ -147,23 +367,76 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
position: 'absolute',
}}
onWheel={e => e.stopPropagation()}
- ref={r =>
- r?.addEventListener(
- 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
- (e: WheelEvent) => {
- if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
- e.stopPropagation();
- },
- { passive: false }
- )
- }>
+ ref={this._mainCont}
+ >
<div className={'datatype-button'}>
<Toggle text={' TABLE '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
<Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
<Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
<Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
</div>
+
+ {/* <CollectionFreeFormView
+ ref={this._ffref}
+ {...this._props}
+ setContentView={emptyFunction}
+ renderDepth={this._props.renderDepth - 1}
+ fieldKey={this.annotationKey}
+ styleProvider={this._props.styleProvider}
+ isAnnotationOverlay={true}
+ annotationLayerHostsContent={false}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
+ select={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}>
+ {this.renderVizView}
+ </CollectionFreeFormView> */}
+
{this.renderVizView}
+ <div className="dataviz-sidebar"
+ style={{ width: `${this.sidebarWidthPercent}`,
+ backgroundColor: `${this.sidebarColor}` }}
+ onPointerDown={this.onPointerDown}>
+ <SidebarAnnos
+ ref={this._sidebarRef}
+ {...this._props}
+ fieldKey={this.fieldKey}
+ rootDoc={this.Document}
+ layoutDoc={this.layoutDoc}
+ dataDoc={this.dataDoc}
+ usePanelWidth={true}
+ showSidebar={this.SidebarShown}
+ nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ PanelWidth={this.sidebarWidth}
+ sidebarAddDocument={this.sidebarAddDocument}
+ moveDocument={this.moveDocument}
+ removeDocument={this.sidebarRemoveDocument}
+ />
+ </div>
+ {this.sidebarHandle}
+ {this.annotationLayer}
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ <MarqueeAnnotator
+ rootDoc={this.Document}
+ getPageFromScroll={undefined}
+ anchorMenuClick={this.anchorMenuClick}
+ scrollTop={0}
+ annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
+ addDocument={this.sidebarAddDocument}
+ docView={this.DocumentView!}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this.savedAnnotations}
+ selectionText={returnEmptyString}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ anchorMenuCrop={this.crop}
+ />
+ )}
</div>
);
}
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss
new file mode 100644
index 000000000..63a693918
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.scss
@@ -0,0 +1,175 @@
+$textgrey: #707070;
+$lighttextgrey: #a3a3a3;
+$greyborder: #d3d3d3;
+$lightgrey: #ececec;
+$button: #5b97ff;
+$highlightedText: #82e0ff;
+
+.summary-box {
+ position: fixed;
+ bottom: 10px;
+ right: 10px;
+ width: 250px;
+ min-height: 200px;
+ border-radius: 15px;
+ padding: 15px;
+ padding-bottom: 0;
+ z-index: 999;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ background-color: #ffffff;
+ box-shadow: 0 2px 5px #7474748d;
+ color: $textgrey;
+
+ .summary-heading {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid $greyborder;
+ padding-bottom: 5px;
+
+ .summary-text {
+ font-size: 12px;
+ font-weight: 500;
+ }
+ }
+
+ label {
+ color: $textgrey;
+ font-size: 12px;
+ font-weight: 400;
+ letter-spacing: 1px;
+ margin: 0;
+ padding-right: 5px;
+ }
+
+ a {
+ cursor: pointer;
+ }
+
+ .content-wrapper {
+ padding-top: 10px;
+ min-height: 50px;
+ max-height: 150px;
+ overflow-y: auto;
+ }
+
+ .btns-wrapper {
+ height: 50px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .summarizing {
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ button {
+ font-size: 9px;
+ padding: 10px;
+ color: #ffffff;
+ background-color: $button;
+ border-radius: 5px;
+ }
+
+ .text-btn {
+ &:hover {
+ background-color: $button;
+ }
+ }
+
+ .btn-secondary {
+ font-size: 8px;
+ padding: 10px 5px;
+ background-color: $lightgrey;
+ color: $textgrey;
+ &:hover {
+ background-color: $lightgrey;
+ }
+ }
+
+ .icon-btn {
+ background-color: #ffffff;
+ padding: 10px;
+ border-radius: 50%;
+ color: $button;
+ border: 1px solid $button;
+ }
+}
+
+.image-content-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ padding-bottom: 16px;
+
+ .img-wrapper {
+ position: relative;
+ cursor: pointer;
+
+ .img-container {
+ pointer-events: none;
+ position: relative;
+
+ img {
+ pointer-events: all;
+ position: relative;
+ }
+ }
+
+ .img-container::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ .btn-container {
+ position: absolute;
+ right: 8px;
+ bottom: 8px;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ &:hover {
+ .img-container::after {
+ opacity: 1;
+ }
+
+ .btn-container {
+ opacity: 1;
+ }
+ }
+ }
+}
+
+// Typist CSS
+.Typist .Cursor {
+ display: inline-block;
+}
+.Typist .Cursor--blinking {
+ opacity: 1;
+ animation: blink 1s linear infinite;
+}
+
+@keyframes blink {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
new file mode 100644
index 000000000..3cb5125da
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -0,0 +1,111 @@
+import * as React from 'react';
+import './SchemaCSVPopUp.scss';
+import { action, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { Button, IconButton, Type } from 'browndash-components';
+import { StrCast } from '../../../../fields/Types';
+import { MarqueeView } from '../../collections/collectionFreeForm/MarqueeView';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { DragManager } from '../../../util/DragManager';
+import { DocumentView } from '../DocumentView';
+import { CgClose } from 'react-icons/cg';
+
+interface SchemaCSVPopUpProps {}
+
+@observer
+export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+ static Instance: SchemaCSVPopUp;
+
+ @observable
+ public dataVizDoc: Doc | undefined = undefined;
+ @action
+ public setDataVizDoc = (doc: Doc) => {
+ this.dataVizDoc = doc;
+ };
+
+ @observable
+ public view: DocumentView | undefined = undefined;
+ @action
+ public setView = (docView: DocumentView) => {
+ this.view = docView;
+ };
+
+ @observable
+ public target: Doc | undefined = undefined;
+ @action
+ public setTarget = (doc: Doc) => {
+ this.target = doc;
+ };
+
+ @observable
+ public visible: boolean = false;
+ @action
+ public setVisible = (vis: boolean) => {
+ this.visible = vis;
+ };
+
+ constructor(props: SchemaCSVPopUpProps) {
+ super(props);
+ makeObservable(this);
+ SchemaCSVPopUp.Instance = this;
+ }
+
+ dataBox = () => {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('Schema Table as Data Visualization Doc')}
+ <div className="image-content-wrapper">
+ <div className="img-wrapper">
+ <div className="img-container" onPointerDown={e => this.drag(e)}>
+ <img
+ width={150} height={150} src={"/assets/dataVizBox.png"}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ };
+
+ heading = (headingText: string) => (
+ <div className="summary-heading">
+ <label className="summary-text">{headingText}</label>
+ <IconButton color={StrCast(Doc.UserDoc().userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />
+ </div>
+ );
+
+ drag = (e: React.PointerEvent) => {
+ const downX = e.clientX;
+ const downY = e.clientY;
+ setupMoveUpEvents(
+ {},
+ e,
+ e => {
+ const sourceAnchorCreator = () => this.dataVizDoc!;
+ const targetCreator = (annotationOn: Doc | undefined) => {
+ const embedding = Doc.MakeEmbedding(this.dataVizDoc!);
+ return embedding;
+ };
+ if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+ DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [],
+ new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+ dragComplete: e => {this.setVisible(false);},
+ });
+ return true;
+ }
+ return false;
+ },
+ emptyFunction,
+ action(e => {})
+ );
+ };
+
+ render() {
+ return (
+ <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
+ {this.dataBox()}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index c0c0f10a2..2f7dd0487 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -4,7 +4,7 @@
flex-direction: column;
align-items: center;
cursor: default;
- margin-top: 10px;
+ margin-top: 30px;
overflow-y: visible;
.graph {
@@ -15,8 +15,8 @@
font-size: larger;
display: flex;
flex-direction: row;
- margin-top: -10px;
- margin-bottom: -10px;
+ margin-top: -20px;
+ margin-bottom: -20px;
}
.asHistogram-checkBox {
// display: flex;
@@ -93,6 +93,7 @@
display: flex;
flex-direction: column;
cursor: default;
+ margin-top: 30px;
height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows
.tableBox-container {
overflow: scroll;
@@ -111,6 +112,7 @@
white-space: pre;
max-width: 150;
overflow: hidden;
+ margin-left: 2px;
}
}
}
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index d786e84ad..227c993c7 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -461,7 +461,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
if (this._histogramData.length > 0 || !this.parentViz) {
return this._props.axes.length >= 1 ? (
- <div className="chart-container">
+ <div className="chart-container" style={{width: this._props.width+this._props.margin.right}}>
<div className="graph-title">
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index abb95aa4c..4b0df0457 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,4 +1,4 @@
-import { EditableText, Size } from 'browndash-components';
+import { Button, EditableText, Size } from 'browndash-components';
import * as d3 from 'd3';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
@@ -24,6 +24,7 @@ export interface SelectedDataPoint extends DataPoint {
elem?: d3.Selection<d3.BaseType, unknown, SVGGElement, unknown>;
}
export interface LineChartProps {
+ vizBox: DataVizBox;
Document: Doc;
layoutDoc: Doc;
axes: string[];
@@ -358,7 +359,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
- <div className="chart-container">
+ <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}>
<div className="graph-title">
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
@@ -372,7 +373,16 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
/>
</div>
<div ref={this._lineChartRef} />
- {selectedPt != 'none' ? <div className={'selected-data'}> {`Selected: ${selectedPt}`}</div> : null}
+ {selectedPt != 'none' ?
+ <div className={'selected-data'}>
+ {`Selected: ${selectedPt}`}
+ <Button onClick={e=>{
+ console.log("test plzz")
+ this.props.vizBox.sidebarBtnDown;
+ this.props.vizBox.sidebarAddDocument;}
+ }></Button>
+ </div>
+ : null}
</div>
) : (
<span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 9e7265b26..e644870da 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -356,7 +356,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
if (this._pieChartData.length > 0 || !this.parentViz) {
return this._props.axes.length >= 1 ? (
- <div className="chart-container">
+ <div className="chart-container" style={{width: this._props.width+this._props.margin.right}}>
<div className="graph-title">
<EditableText
val={StrCast(this._props.layoutDoc[titleAccessor])}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index f77c138df..16e66d0c3 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -87,7 +87,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
}
@computed get startID() {
- return this.rowHeight ? Math.floor(this._scrollTop / this.rowHeight) : 0;
+ return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight)-1, 0) : 0;
}
@computed get endID() {
console.log('start = ' + this.startID + ' container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
@@ -169,6 +169,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
return (
<div
className="tableBox"
+ style={{width: this.props.width+this.props.margin.right}}
tabIndex={0}
onKeyDown={e => {
if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index cdd0cb95a..db21d9a3d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -127,7 +127,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
console.log(err);
return '';
}
- GPTPopup.Instance.setLoading(false);
+ this.setLoading(false);
};
/**
@@ -213,6 +213,31 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
);
};
+ data = () => {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('GENERATED IMAGE')}
+ <div className="image-content-wrapper">
+ {this.imgUrls.map(rawSrc => (
+ <div className="img-wrapper">
+ <div className="img-container">
+ <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+ </div>
+ <div className="btn-container">
+ <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+ </div>
+ </div>
+ ))}
+ </div>
+ {!this.loading && (
+ <>
+ <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+ </>
+ )}
+ </div>
+ );
+ };
+
summaryBox = () => (
<>
<div>