From e6d1dd82f5d472bddaf66f9210142249d6fa09aa Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 6 Jul 2022 20:21:04 -0400 Subject: added endpoint routes for csv and integrated chartjs --- src/client/views/nodes/DataVizBox/ChartBox.tsx | 165 +++++++++++++++++++++ src/client/views/nodes/DataVizBox/DataVizBox.tsx | 67 ++++++--- src/client/views/nodes/DataVizBox/HistogramBox.tsx | 3 + .../nodes/DataVizBox/HistogramBoxPrimitives.scss | 0 .../nodes/DataVizBox/HistogramBoxPrimitives.tsx | 16 ++ .../nodes/DataVizBox/HistogramLabelPrimitives.scss | 0 .../nodes/DataVizBox/HistogramLabelPrimitives.tsx | 13 ++ 7 files changed, 244 insertions(+), 20 deletions(-) create mode 100644 src/client/views/nodes/DataVizBox/ChartBox.tsx create mode 100644 src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss create mode 100644 src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx create mode 100644 src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss create mode 100644 src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx new file mode 100644 index 000000000..c5ec1716d --- /dev/null +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -0,0 +1,165 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import { Doc } from "../../../../fields/Doc"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, + InteractionItem, + } from 'chart.js'; + import { Bar, getDatasetAtEvent, getElementAtEvent } from 'react-chartjs-2'; +import { ChartJSOrUndefined } from "react-chartjs-2/dist/types"; +import { action, computed, observable } from "mobx"; + + +export interface ChartBoxProps { + rootDoc: Doc; + pairs: {x: number, y:number}[]; +} + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend + ); + +// export const options = { +// responsive: true, +// plugins: { +// legend: { +// position: 'top' as const, +// }, +// title: { +// display: true, +// text: 'Chart.js Bar Chart', +// }, +// }, +// }; + +// const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; + +// export const data = { +// labels, +// datasets: [ +// { +// label: 'Dataset 1', +// data: [100, 200, 300, 400, 500, 600, 700], +// backgroundColor: 'rgba(255, 99, 132, 0.5)', +// }, +// { +// label: 'Dataset 2', +// data: [700, 600, 500, 400, 300, 200, 100], +// backgroundColor: 'rgba(53, 162, 235, 0.5)', +// }, +// ], +// }; + +const primaryColor = 'rgba(53, 162, 235, 0.5)'; +const selectedColor = 'rgba(255, 99, 132, 0.5)'; + +@observer +export class ChartBox extends React.Component { + private _chartRef: any = React.createRef>(); + + // TODO: add alternate | undefined to all these variables + @observable private _prevColor: string = undefined; + @observable private _prevIndex: { dIndex: number, index: number} = undefined; + // @observable private _chartJsData: { + // labels: number[]; + // datasets: { + // label: string; + // data: number[]; + // backgroundColor: string; + // }[]; + // } = undefined; + @observable private _chartJsData: any = null; + + @computed get options() { + return { + responsive: true, + plugins: { + legend: { + position: 'top' as const, + }, + title: { + display: true, + text: 'Bar Chart', + }, + }, + } + } + + @action + generateChartJsData() { + const labels = this.props.pairs.map(p => p.x); + const dataset = { + label: 'Dataset 1', + data: this.props.pairs.map(p => p.y), + // backgroundColor: primaryColor + backgroundColor: this.props.pairs.map(p => primaryColor), + } + const data = { + labels, + datasets: [dataset] + }; + this._chartJsData = data; + // this._chartRef.current.update() + } + + @computed get chartJsData() { + const labels = this.props.pairs.map(p => p.x); + const dataset = { + label: 'Dataset 1', + data: this.props.pairs.map(p => p.y), + backgroundColor: this.props.pairs.map(p => primaryColor), + // backgroundColor: primaryColor, + } + const data = { + labels, + datasets: [dataset] + }; + return data; + } + + // componentDidMount() { + // this.generateChartJsData(); + // } + + @action + onClick = (e: any) => { + e.preventDefault(); + console.log(getDatasetAtEvent(this._chartRef.current, e)); + console.log(getElementAtEvent(this._chartRef.current, e)); + + if (this._prevIndex && this._prevColor) { + this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor; + } + + + const currSelected = getElementAtEvent(this._chartRef.current, e); + const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index }; + this._prevIndex = { dIndex: index.datasetIndex, index: index.index }; + this._prevColor = this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]; + this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor; + // TODO: nda - send data back to the backend so that we know how to render the chart + this._chartRef.current.update(); + console.log(this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]); + } + + render() { + if (this.props.pairs && !this._chartJsData) this.generateChartJsData(); + + if (this.props.pairs && this._chartJsData) { + return this.onClick(e)} /> + } else { + return
+ } + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 592723ee9..0c1063e0c 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,9 +1,12 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { StrCast } from "../../../../fields/Types"; +import { Cast, StrCast } from "../../../../fields/Types"; +import { CsvField } from "../../../../fields/URLField"; +import { Utils } from "../../../../Utils"; import { ViewBoxBaseComponent } from "../../DocComponent"; import { FieldViewProps, FieldView } from "../FieldView"; +import { ChartBox } from "./ChartBox"; import "./DataVizBox.scss"; import { HistogramBox } from "./HistogramBox"; import { TableBox } from "./TableBox"; @@ -16,7 +19,7 @@ enum DataVizView { @observer export class DataVizBox extends ViewBoxBaseComponent() { - @observable private pairs: {x: number, y:number}[] = [{x: 1, y:2}]; + @observable private pairs: {x: number, y:number}[] = []; // TODO: nda - make this use enum values instead // @observable private currView: DataVizView = DataVizView.TABLE; @@ -39,39 +42,63 @@ export class DataVizBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } @action - private createPairs() { - const xVals: number[] = [0, 1, 2, 3, 4, 5]; - // const yVals: number[] = [10, 20, 30, 40, 50, 60]; - const yVals: number[] = [1, 2, 3, 4, 5, 6]; - let pairs: { - x: number, - y:number - }[] = []; - if (xVals.length != yVals.length) return pairs; - for (let i = 0; i < xVals.length; i++) { - pairs.push({x: xVals[i], y: yVals[i]}); - } - this.pairs = pairs; - return pairs; + private async createPairs() { + // const xVals: number[] = [0, 1, 2, 3, 4, 5]; + // // const yVals: number[] = [10, 20, 30, 40, 50, 60]; + // const yVals: number[] = [1, 2, 3, 4, 5, 6]; + // let pairs: { + // x: number, + // y:number + // }[] = []; + // if (xVals.length != yVals.length) return pairs; + // for (let i = 0; i < xVals.length; i++) { + // pairs.push({x: xVals[i], y: yVals[i]}); + // } + // this.pairs = pairs; + // this.pairs = await this.fetchData(); } @computed get selectView() { switch(this.currView) { case "table": - return () + return (); case "histogram": - return () + return (); + // case "histogram": + // return () } } @computed get pairVals() { - return this.createPairs(); + return fetch("/csvData?uri=" + this.dataUrl?.url.href).then(res => res.json()); } + @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField) } + componentDidMount() { - this.createPairs(); + // this.createPairs(); + this.fetchData(); } + // async fetchData() { + // console.log(Cast(this.dataDoc[this.fieldKey], CsvField)); + // const uri = this.dataUrl?.url.href; + // console.log(uri); + // const res = await fetch("/csvData?uri=" + uri); + // return await res.json(); + // } + + fetchData() { + const uri = this.dataUrl?.url.href; + fetch("/csvData?uri=" + uri).then( + res => res.json().then( + action(res => { + this.pairs = res; + }) + )) + } + + // handle changing the view using a button @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx index 00dc2ef46..4488323fe 100644 --- a/src/client/views/nodes/DataVizBox/HistogramBox.tsx +++ b/src/client/views/nodes/DataVizBox/HistogramBox.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import * as React from "react"; import { Doc } from "../../../../fields/Doc"; import { NumCast } from "../../../../fields/Types"; +import { DragManager } from "../../../util/DragManager"; import "./HistogramBox.scss"; interface HistogramBoxProps { @@ -15,6 +16,8 @@ interface HistogramBoxProps { export class HistogramBox extends React.Component { + private _dropXDispoer?: DragManager.DragDropDisposer; + private _dropYDispoer?: DragManager.DragDropDisposer; private origin = {x: 0.1 * this.width, y: 0.9 * this.height}; diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx new file mode 100644 index 000000000..ac1b7699b --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBoxPrimitives.tsx @@ -0,0 +1,16 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import { HistogramBox } from "./HistogramBox"; + +export interface HistogramPrimitivesProps { + HistoBox: HistogramBox; +} + +@observer +export class HistogramBoxPrimitives extends React.Component { + render() { + return
+ +
+ } +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx new file mode 100644 index 000000000..d3440dea1 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramLabelPrimitives.tsx @@ -0,0 +1,13 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives"; +import "./HistogramLabelPrimitives.scss"; + +@observer +export class HistogramLabelPrimitives extends React.Component { + render() { + return
+ +
+ } +} -- cgit v1.2.3-70-g09d2 From 1ccbb22c29ea406dae824b9163ddc676263bb83a Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 6 Jul 2022 20:41:27 -0400 Subject: cleaned up code for chartbox --- src/client/views/nodes/DataVizBox/ChartBox.tsx | 85 +++++------------------- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 22 ++---- 2 files changed, 21 insertions(+), 86 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index c5ec1716d..56f61474b 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -21,6 +21,15 @@ export interface ChartBoxProps { pairs: {x: number, y:number}[]; } +export interface ChartJsData { + labels: number[]; + datasets: { + label: string; + data: number[]; + backgroundColor: string[]; + }[] +} + ChartJS.register( CategoryScale, LinearScale, @@ -30,37 +39,6 @@ ChartJS.register( Legend ); -// export const options = { -// responsive: true, -// plugins: { -// legend: { -// position: 'top' as const, -// }, -// title: { -// display: true, -// text: 'Chart.js Bar Chart', -// }, -// }, -// }; - -// const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; - -// export const data = { -// labels, -// datasets: [ -// { -// label: 'Dataset 1', -// data: [100, 200, 300, 400, 500, 600, 700], -// backgroundColor: 'rgba(255, 99, 132, 0.5)', -// }, -// { -// label: 'Dataset 2', -// data: [700, 600, 500, 400, 300, 200, 100], -// backgroundColor: 'rgba(53, 162, 235, 0.5)', -// }, -// ], -// }; - const primaryColor = 'rgba(53, 162, 235, 0.5)'; const selectedColor = 'rgba(255, 99, 132, 0.5)'; @@ -68,18 +46,9 @@ const selectedColor = 'rgba(255, 99, 132, 0.5)'; export class ChartBox extends React.Component { private _chartRef: any = React.createRef>(); - // TODO: add alternate | undefined to all these variables - @observable private _prevColor: string = undefined; - @observable private _prevIndex: { dIndex: number, index: number} = undefined; - // @observable private _chartJsData: { - // labels: number[]; - // datasets: { - // label: string; - // data: number[]; - // backgroundColor: string; - // }[]; - // } = undefined; - @observable private _chartJsData: any = null; + @observable private _prevColor: string | undefined= undefined; + @observable private _prevIndex: { dIndex: number, index: number} | undefined = undefined; + @observable private _chartJsData: ChartJsData | undefined= undefined; @computed get options() { return { @@ -102,7 +71,6 @@ export class ChartBox extends React.Component { const dataset = { label: 'Dataset 1', data: this.props.pairs.map(p => p.y), - // backgroundColor: primaryColor backgroundColor: this.props.pairs.map(p => primaryColor), } const data = { @@ -110,39 +78,21 @@ export class ChartBox extends React.Component { datasets: [dataset] }; this._chartJsData = data; - // this._chartRef.current.update() } - @computed get chartJsData() { - const labels = this.props.pairs.map(p => p.x); - const dataset = { - label: 'Dataset 1', - data: this.props.pairs.map(p => p.y), - backgroundColor: this.props.pairs.map(p => primaryColor), - // backgroundColor: primaryColor, - } - const data = { - labels, - datasets: [dataset] - }; - return data; + componentDidMount() { + this.generateChartJsData(); } - // componentDidMount() { - // this.generateChartJsData(); - // } - @action onClick = (e: any) => { e.preventDefault(); - console.log(getDatasetAtEvent(this._chartRef.current, e)); - console.log(getElementAtEvent(this._chartRef.current, e)); - + if (getDatasetAtEvent(this._chartRef.current, e).length == 0) return; + if (!this._chartJsData) return; if (this._prevIndex && this._prevColor) { this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor; } - const currSelected = getElementAtEvent(this._chartRef.current, e); const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index }; this._prevIndex = { dIndex: index.datasetIndex, index: index.index }; @@ -150,12 +100,9 @@ export class ChartBox extends React.Component { this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor; // TODO: nda - send data back to the backend so that we know how to render the chart this._chartRef.current.update(); - console.log(this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]); } render() { - if (this.props.pairs && !this._chartJsData) this.generateChartJsData(); - if (this.props.pairs && this._chartJsData) { return this.onClick(e)} /> } else { diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 0c1063e0c..c5ad3c84d 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -41,23 +41,6 @@ export class DataVizBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } - @action - private async createPairs() { - // const xVals: number[] = [0, 1, 2, 3, 4, 5]; - // // const yVals: number[] = [10, 20, 30, 40, 50, 60]; - // const yVals: number[] = [1, 2, 3, 4, 5, 6]; - // let pairs: { - // x: number, - // y:number - // }[] = []; - // if (xVals.length != yVals.length) return pairs; - // for (let i = 0; i < xVals.length; i++) { - // pairs.push({x: xVals[i], y: yVals[i]}); - // } - // this.pairs = pairs; - // this.pairs = await this.fetchData(); - } - @computed get selectView() { switch(this.currView) { case "table": @@ -107,6 +90,11 @@ export class DataVizBox extends ViewBoxBaseComponent() { } render() { + + if (this.pairs.length == 0) { + return
Loading...
+ } + return (
-- cgit v1.2.3-70-g09d2 From 5a9cd07ae924bf7e3aeb5b70ce42bfa0a2900ddf Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 6 Jul 2022 20:50:30 -0400 Subject: storing render details in field --- src/client/views/nodes/DataVizBox/ChartBox.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index 56f61474b..26ccd1c12 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -14,6 +14,7 @@ import { import { Bar, getDatasetAtEvent, getElementAtEvent } from 'react-chartjs-2'; import { ChartJSOrUndefined } from "react-chartjs-2/dist/types"; import { action, computed, observable } from "mobx"; +import { Cast, StrCast } from "../../../../fields/Types"; export interface ChartBoxProps { @@ -67,6 +68,13 @@ export class ChartBox extends React.Component { @action generateChartJsData() { + if (this.props.rootDoc._chartData) { + // parse the string into a json object + this._chartJsData = JSON.parse(StrCast(this.props.rootDoc._chartData)); + this._prevColor = StrCast(this.props.rootDoc._prevColor); + this._prevIndex = JSON.parse(StrCast(this.props.rootDoc._prevIndex)); + return; + } const labels = this.props.pairs.map(p => p.x); const dataset = { label: 'Dataset 1', @@ -98,8 +106,12 @@ export class ChartBox extends React.Component { this._prevIndex = { dIndex: index.datasetIndex, index: index.index }; this._prevColor = this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]; this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor; - // TODO: nda - send data back to the backend so that we know how to render the chart this._chartRef.current.update(); + // stringify this._chartJsData + const strData = JSON.stringify(this._chartJsData); + this.props.rootDoc._chartData = strData; + this.props.rootDoc._prevColor = this._prevColor; + this.props.rootDoc._prevIndex = JSON.stringify(this._prevIndex); } render() { -- cgit v1.2.3-70-g09d2 From 1299019fa7185e53620245a4bca3a4beb9c95b78 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 6 Jul 2022 21:08:20 -0400 Subject: added support for line graph --- src/client/views/nodes/DataVizBox/ChartBox.tsx | 45 ++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index 26ccd1c12..1a084cf55 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -10,8 +10,10 @@ import { Tooltip, Legend, InteractionItem, + PointElement, + LineElement, } from 'chart.js'; - import { Bar, getDatasetAtEvent, getElementAtEvent } from 'react-chartjs-2'; + import { Bar, getDatasetAtEvent, getElementAtEvent, Line } from 'react-chartjs-2'; import { ChartJSOrUndefined } from "react-chartjs-2/dist/types"; import { action, computed, observable } from "mobx"; import { Cast, StrCast } from "../../../../fields/Types"; @@ -37,7 +39,9 @@ ChartJS.register( BarElement, Title, Tooltip, - Legend + Legend, + PointElement, + LineElement ); const primaryColor = 'rgba(53, 162, 235, 0.5)'; @@ -51,6 +55,22 @@ export class ChartBox extends React.Component { @observable private _prevIndex: { dIndex: number, index: number} | undefined = undefined; @observable private _chartJsData: ChartJsData | undefined= undefined; + @computed get currView() { + if (this.props.rootDoc._dataVizView) { + return StrCast(this.props.rootDoc._currChartView); + } else { + return "table"; + } + } + + constructor(props: any) { + super(props); + if (!this.props.rootDoc._currChartView) { + // TODO: nda - this might not always want to default to "table" + this.props.rootDoc._currChartView = "bar"; + } + } + @computed get options() { return { responsive: true, @@ -92,6 +112,14 @@ export class ChartBox extends React.Component { this.generateChartJsData(); } + @action + onClickChangeChart = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + console.log(e.currentTarget.value); + this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); + } + @action onClick = (e: any) => { e.preventDefault(); @@ -116,7 +144,18 @@ export class ChartBox extends React.Component { render() { if (this.props.pairs && this._chartJsData) { - return this.onClick(e)} /> + return ( +
+
+ {this.props.rootDoc._currChartView == "line" ? + ( this.onClick(e)} />) : + ( this.onClick(e)} />) + } +
+ + +
+ ) } else { return
} -- cgit v1.2.3-70-g09d2 From 39cf91aadee5f448e1a7a0a07da9e769d6e242fa Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Fri, 8 Jul 2022 17:31:38 -0400 Subject: removed todo comment --- src/client/views/nodes/DataVizBox/ChartBox.tsx | 1 - 1 file changed, 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index 1a084cf55..07f754637 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -66,7 +66,6 @@ export class ChartBox extends React.Component { constructor(props: any) { super(props); if (!this.props.rootDoc._currChartView) { - // TODO: nda - this might not always want to default to "table" this.props.rootDoc._currChartView = "bar"; } } -- cgit v1.2.3-70-g09d2 From 0b58be7abc9e477190b105fe9c1ecfcc9dd58e69 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 12 Jul 2022 12:19:09 -0400 Subject: merged master --- package-lock.json | 91 +++++++++++++++++------- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 80 ++++++++++----------- 2 files changed, 101 insertions(+), 70 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index e0e83d4b8..71bac6958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2455,7 +2455,7 @@ "textarea-caret": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz", - "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg==" + "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8=" } } }, @@ -2554,7 +2554,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "agent-base": { "version": "6.0.2", @@ -3346,7 +3346,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "balanced-match": { "version": "1.0.2", @@ -3411,7 +3411,7 @@ "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" }, "base64-js": { "version": "1.5.1", @@ -4333,7 +4333,7 @@ "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { "version": "1.3.0", @@ -4343,7 +4343,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compress-brotli": { "version": "1.3.8", @@ -5051,7 +5051,7 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=" }, "cyclist": { "version": "1.0.1", @@ -5059,6 +5059,16 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6069,7 +6079,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "ws": { "version": "7.4.6", @@ -6208,6 +6218,28 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "dev": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6219,6 +6251,7 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { + "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -8416,7 +8449,7 @@ "fs-extra": { "version": "0.26.7", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", + "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^2.1.0", @@ -9054,14 +9087,14 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" }, "has-flag": { "version": "3.0.0", @@ -9557,7 +9590,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, "inflight": { "version": "1.0.6", @@ -10600,7 +10633,7 @@ "jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", "requires": { "graceful-fs": "^4.1.6" } @@ -11019,7 +11052,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.isplainobject": { "version": "4.0.6", @@ -11270,7 +11303,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", + "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", "requires": { "jquery": "^1.12.3" }, @@ -11278,7 +11311,7 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" + "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" } } }, @@ -12788,7 +12821,7 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { @@ -14287,7 +14320,7 @@ }, "minimist": { "version": "1.2.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minizlib": { @@ -14336,7 +14369,7 @@ "dependencies": { "minimist": { "version": "1.2.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" } } @@ -15709,7 +15742,7 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { @@ -19343,7 +19376,7 @@ "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==" }, "debug": { "version": "4.1.1", @@ -19356,7 +19389,7 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" } } }, @@ -20208,7 +20241,7 @@ "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" }, "to-fast-properties": { "version": "2.0.0", @@ -20720,6 +20753,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -21655,7 +21694,7 @@ "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", "dev": true, "requires": { "resolve-from": "^3.0.0" @@ -21664,7 +21703,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true }, "semver": { @@ -22205,7 +22244,7 @@ "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" }, "yn": { "version": "2.0.0", diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index c5ad3c84d..a4a4028f2 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,25 +1,22 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { CsvField } from "../../../../fields/URLField"; -import { Utils } from "../../../../Utils"; -import { ViewBoxBaseComponent } from "../../DocComponent"; -import { FieldViewProps, FieldView } from "../FieldView"; -import { ChartBox } from "./ChartBox"; -import "./DataVizBox.scss"; -import { HistogramBox } from "./HistogramBox"; -import { TableBox } from "./TableBox"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { CsvField } from '../../../../fields/URLField'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { FieldViewProps, FieldView } from '../FieldView'; +import { ChartBox } from './ChartBox'; +import './DataVizBox.scss'; +import { TableBox } from './TableBox'; enum DataVizView { - TABLE = "table", - HISTOGRAM= "histogram" + TABLE = 'table', + HISTOGRAM = 'histogram', } - @observer export class DataVizBox extends ViewBoxBaseComponent() { - @observable private pairs: {x: number, y:number}[] = []; + @observable private pairs: { x: number; y: number }[] = []; // TODO: nda - make this use enum values instead // @observable private currView: DataVizView = DataVizView.TABLE; @@ -27,7 +24,7 @@ export class DataVizBox extends ViewBoxBaseComponent() { if (this.rootDoc._dataVizView) { return StrCast(this.rootDoc._dataVizView); } else { - return "table"; + return 'table'; } } @@ -35,71 +32,66 @@ export class DataVizBox extends ViewBoxBaseComponent() { super(props); if (!this.rootDoc._dataVizView) { // TODO: nda - this might not always want to default to "table" - this.rootDoc._dataVizView = "table"; + this.rootDoc._dataVizView = 'table'; } } - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataVizBox, fieldKey); + } @computed get selectView() { - switch(this.currView) { - case "table": - return (); - case "histogram": - return (); + switch (this.currView) { + case 'table': + return ; + case 'histogram': + return ; // case "histogram": - // return () + // return () } } @computed get pairVals() { - return fetch("/csvData?uri=" + this.dataUrl?.url.href).then(res => res.json()); + return fetch('/csvData?uri=' + this.dataUrl?.url.href).then(res => res.json()); } - @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField) } + @computed get dataUrl() { + return Cast(this.dataDoc[this.fieldKey], CsvField); + } componentDidMount() { // this.createPairs(); this.fetchData(); } - // async fetchData() { - // console.log(Cast(this.dataDoc[this.fieldKey], CsvField)); - // const uri = this.dataUrl?.url.href; - // console.log(uri); - // const res = await fetch("/csvData?uri=" + uri); - // return await res.json(); - // } - fetchData() { const uri = this.dataUrl?.url.href; - fetch("/csvData?uri=" + uri).then( - res => res.json().then( + fetch('/csvData?uri=' + uri).then(res => + res.json().then( action(res => { this.pairs = res; }) - )) + ) + ); } - // handle changing the view using a button @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.currView == "table" ? "histogram" : "table"; + this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table'; } render() { - if (this.pairs.length == 0) { - return
Loading...
+ return
Loading...
; } return (
- + {this.selectView}
); } -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 90a4aace3a5701c0332e180b5bd1119dd38ec52c Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 12 Jul 2022 17:25:42 -0400 Subject: stuck on linking --- package-lock.json | 83 ++++++- package.json | 1 + src/client/views/nodes/DataViz.tsx | 20 -- src/client/views/nodes/DataVizBox/ChartBox.tsx | 296 ++++++++++++++++--------- 4 files changed, 276 insertions(+), 124 deletions(-) delete mode 100644 src/client/views/nodes/DataViz.tsx (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 7d34ec01f..731599c73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4997,6 +4997,11 @@ "postcss-value-parser": "^3.3.0" } }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, "css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -5280,6 +5285,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -7509,8 +7519,7 @@ "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "events": { "version": "3.3.0", @@ -7961,6 +7970,11 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-equals": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", + "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -17857,6 +17871,14 @@ "resolved": "https://registry.npmjs.org/react-resizable-rotatable-draggable/-/react-resizable-rotatable-draggable-0.2.0.tgz", "integrity": "sha512-F8TPx3z7/AcmRViySbYV3LpUWXFpHlGAmKmNcYMgPlS+h1eYFazRG3xYS8Z6e48hWY1EcCny/YNrwRNUrap8CQ==" }, + "react-resize-detector": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", + "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", + "requires": { + "lodash": "^4.17.21" + } + }, "react-reveal": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/react-reveal/-/react-reveal-1.2.2.tgz", @@ -17917,6 +17939,28 @@ } } }, + "react-smooth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", + "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", + "requires": { + "fast-equals": "^2.0.0", + "react-transition-group": "2.9.0" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, "react-table": { "version": "6.11.5", "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz", @@ -18091,6 +18135,32 @@ "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, + "recharts": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.12.tgz", + "integrity": "sha512-dAzEuc9AjM+IF0A33QzEdBEUnyGKJcGUPa0MYm0vd38P3WouQjrj2egBrCNInE7ZcQwN+z3MoT7Rw03u8nP9HA==", + "requires": { + "classnames": "^2.2.5", + "d3-interpolate": "^2.0.0", + "d3-scale": "^3.0.0", + "d3-shape": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^7.1.2", + "react-smooth": "^2.0.1", + "recharts-scale": "^0.4.4", + "reduce-css-calc": "^2.1.8" + } + }, + "recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -18126,6 +18196,15 @@ "strip-indent": "^1.0.1" } }, + "reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + } + }, "reduce-flatten": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", diff --git a/package.json b/package.json index 488375b03..1e43390d8 100644 --- a/package.json +++ b/package.json @@ -283,6 +283,7 @@ "react-table": "^6.11.5", "react-transition-group": "^4.4.2", "readline": "^1.3.0", + "recharts": "^2.1.12", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^4.3.0", diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx deleted file mode 100644 index df4c8f937..000000000 --- a/src/client/views/nodes/DataViz.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { ViewBoxBaseComponent } from '../DocComponent'; -import './DataViz.scss'; -import { FieldView, FieldViewProps } from './FieldView'; - -@observer -export class DataVizBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DataVizBox, fieldKey); - } - - render() { - return ( -
-
Hi
-
- ); - } -} diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index 07f754637..42bd8a7f6 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -1,27 +1,17 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc } from "../../../../fields/Doc"; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, - InteractionItem, - PointElement, - LineElement, - } from 'chart.js'; - import { Bar, getDatasetAtEvent, getElementAtEvent, Line } from 'react-chartjs-2'; -import { ChartJSOrUndefined } from "react-chartjs-2/dist/types"; -import { action, computed, observable } from "mobx"; -import { Cast, StrCast } from "../../../../fields/Types"; - +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc } from '../../../../fields/Doc'; +// import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, InteractionItem, PointElement, LineElement } from 'chart.js'; +// import { Bar, getDatasetAtEvent, getElementAtEvent, Line } from 'react-chartjs-2'; +import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types'; +import { action, computed, observable } from 'mobx'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { CategoricalChartState } from 'recharts/types/chart/generateCategoricalChart'; export interface ChartBoxProps { rootDoc: Doc; - pairs: {x: number, y:number}[]; + pairs: { x: number; y: number }[]; } export interface ChartJsData { @@ -30,85 +20,127 @@ export interface ChartJsData { label: string; data: number[]; backgroundColor: string[]; - }[] + }[]; } -ChartJS.register( - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, - PointElement, - LineElement - ); - +// ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, PointElement, LineElement); + const primaryColor = 'rgba(53, 162, 235, 0.5)'; const selectedColor = 'rgba(255, 99, 132, 0.5)'; +const data = [ + { + name: 'Page A', + uv: 4000, + pv: 2400, + amt: 2400, + }, + { + name: 'Page B', + uv: 3000, + pv: 1398, + amt: 2210, + }, + { + name: 'Page C', + uv: 2000, + pv: 9800, + amt: 2290, + }, + { + name: 'Page D', + uv: 2780, + pv: 3908, + amt: 2000, + }, + { + name: 'Page E', + uv: 1890, + pv: 4800, + amt: 2181, + }, + { + name: 'Page F', + uv: 2390, + pv: 3800, + amt: 2500, + }, + { + name: 'Page G', + uv: 3490, + pv: 4300, + amt: 2100, + }, +]; + +export interface RechartData { + name: string | number; + y: number; +} + @observer export class ChartBox extends React.Component { - private _chartRef: any = React.createRef>(); - - @observable private _prevColor: string | undefined= undefined; - @observable private _prevIndex: { dIndex: number, index: number} | undefined = undefined; - @observable private _chartJsData: ChartJsData | undefined= undefined; + @observable private _chartData: RechartData[] = []; @computed get currView() { if (this.props.rootDoc._dataVizView) { return StrCast(this.props.rootDoc._currChartView); } else { - return "table"; + return 'table'; } } constructor(props: any) { super(props); if (!this.props.rootDoc._currChartView) { - this.props.rootDoc._currChartView = "bar"; - } - } - - @computed get options() { - return { - responsive: true, - plugins: { - legend: { - position: 'top' as const, - }, - title: { - display: true, - text: 'Bar Chart', - }, - }, + this.props.rootDoc._currChartView = 'bar'; } } @action - generateChartJsData() { + generateChartData() { if (this.props.rootDoc._chartData) { - // parse the string into a json object - this._chartJsData = JSON.parse(StrCast(this.props.rootDoc._chartData)); - this._prevColor = StrCast(this.props.rootDoc._prevColor); - this._prevIndex = JSON.parse(StrCast(this.props.rootDoc._prevIndex)); + this._chartData = JSON.parse(StrCast(this.props.rootDoc._chartData)); return; } - const labels = this.props.pairs.map(p => p.x); - const dataset = { - label: 'Dataset 1', - data: this.props.pairs.map(p => p.y), - backgroundColor: this.props.pairs.map(p => primaryColor), - } - const data = { - labels, - datasets: [dataset] - }; - this._chartJsData = data; + + // generate the chart data according to the RechartData type from the pairs prop + const chartData: RechartData[] = []; + this.props.pairs.forEach(pair => { + chartData.push({ + name: pair.x, + y: pair.y, + }); + }); + + this._chartData = chartData; } - + + // @action + // generateChartJsData() { + // if (this.props.rootDoc._chartData) { + // // parse the string into a json object + // this._chartJsData = JSON.parse(StrCast(this.props.rootDoc._chartData)); + // this._prevColor = StrCast(this.props.rootDoc._prevColor); + // this._prevIndex = JSON.parse(StrCast(this.props.rootDoc._prevIndex)); + // return; + // } + // const labels = this.props.pairs.map(p => p.x); + // const dataset = { + // label: 'Dataset 1', + // data: this.props.pairs.map(p => p.y), + // backgroundColor: this.props.pairs.map(p => primaryColor), + // }; + // const data = { + // labels, + // datasets: [dataset], + // }; + // this._chartJsData = data; + // } + componentDidMount() { - this.generateChartJsData(); + // this.generateChartJsData(); + this.generateChartData(); } @action @@ -117,46 +149,106 @@ export class ChartBox extends React.Component { e.stopPropagation(); console.log(e.currentTarget.value); this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); - } + }; - @action - onClick = (e: any) => { - e.preventDefault(); - if (getDatasetAtEvent(this._chartRef.current, e).length == 0) return; - if (!this._chartJsData) return; - if (this._prevIndex && this._prevColor) { - this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor; + // @action + // onClick = (e: React.MouseEvent) => { + // e.preventDefault(); + // console.log(e); + + // if (getDatasetAtEvent(this._chartRef.current, e).length == 0) return; + // if (!this._chartJsData) return; + // if (this._prevIndex && this._prevColor) { + // this._chartJsData.datasets[this._prevIndex.dIndex].backgroundColor[this._prevIndex.index] = this._prevColor; + // } + + // const currSelected = getElementAtEvent(this._chartRef.current, e); + // // TODO - nda: the currSelected might not have the updated color variables so look into that + // this._currSelected = currSelected; + // const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index }; + // this._prevIndex = { dIndex: index.datasetIndex, index: index.index }; + // this._prevColor = this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]; + // this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor; + // this._chartRef.current.update(); + // // stringify this._chartJsData + // const strData = JSON.stringify(this._chartJsData); + // this.props.rootDoc._chartData = strData; + // this.props.rootDoc._prevColor = this._prevColor; + // this.props.rootDoc._prevIndex = JSON.stringify(this._prevIndex); + // }; + + onMouseMove = (e: CategoricalChartState) => { + console.log(e); + + // pops up at 526 when it should be at 263 + // at 100%scale it pops up at 526 but mouse shows 263 at 50% scale + if (e.chartX && e.chartY) { + e.chartX += 263; + e.chartY += 263; } + // if (e.chartX && e.chartY) { + // e.chartX -= 200; + // e.chartY -= 200; + // } + }; - const currSelected = getElementAtEvent(this._chartRef.current, e); - const index = { datasetIndex: currSelected[0].datasetIndex, index: currSelected[0].index }; - this._prevIndex = { dIndex: index.datasetIndex, index: index.index }; - this._prevColor = this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index]; - this._chartJsData.datasets[index.datasetIndex].backgroundColor[index.index] = selectedColor; - this._chartRef.current.update(); - // stringify this._chartJsData - const strData = JSON.stringify(this._chartJsData); - this.props.rootDoc._chartData = strData; - this.props.rootDoc._prevColor = this._prevColor; - this.props.rootDoc._prevIndex = JSON.stringify(this._prevIndex); + handleDotClick = (e: any) => { + console.log(e); + }; + + // handleTextClick = (e: any) => { + // console.log(e); + // } + + @computed get reLineChart() { + return ( + + console.log(e)} + onMouseMove={e => this.onMouseMove(e)}> + + console.log(e)} /> + + + + + {/* */} + + + ); } render() { - if (this.props.pairs && this._chartJsData) { + if (this.props.pairs && this._chartData.length > 0) { return (
- {this.props.rootDoc._currChartView == "line" ? - ( this.onClick(e)} />) : - ( this.onClick(e)} />) - } + {/*{this.props.rootDoc._currChartView == 'line' ? ( + this.onClick(e)} /> + ) : ( + this.onClick(e)} /> + )} */} + {this.reLineChart}
- - + +
- ) + ); } else { - return
+ return
; } } -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 6a117b8779d1e3c00fbe67cb38c7cde1fe5b0552 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 13 Jul 2022 17:01:21 -0400 Subject: got basic d3js graph to work --- package-lock.json | 536 ++++++++++++++++++++++++ package.json | 8 +- src/client/views/nodes/DataVizBox/ChartBox.tsx | 151 +++---- src/client/views/nodes/DataVizBox/LineChart.tsx | 122 ++++++ 4 files changed, 720 insertions(+), 97 deletions(-) create mode 100644 src/client/views/nodes/DataVizBox/LineChart.tsx (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 731599c73..0a340b73a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1173,6 +1173,50 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, + "@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true + }, "@types/d3-axis": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-2.1.3.tgz", @@ -1181,11 +1225,138 @@ "@types/d3-selection": "^2" } }, + "@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, "@types/d3-color": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz", "integrity": "sha512-+0EtEjBfKEDtH9Rk3u3kLOUXM5F+iZK+WvASPb0MhIZl8J8NUvGeZRwKCXl+P3HkYx5TdU4YtcibpqHkSR9n7w==" }, + "@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "requires": { + "@types/d3-dsv": "*" + } + }, + "@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", + "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, "@types/d3-scale": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", @@ -1194,16 +1365,62 @@ "@types/d3-time": "^2" } }, + "@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, "@types/d3-selection": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-2.0.1.tgz", "integrity": "sha512-3mhtPnGE+c71rl/T5HMy+ykg7migAZ4T6gzU0HxpgBFKcasBrSnwRbYV1/UZR6o5fkpySxhWxAhd7yhjj8jL7g==" }, + "@types/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", + "dev": true, + "requires": { + "@types/d3-path": "*" + } + }, "@types/d3-time": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz", "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg==" }, + "@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "@types/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "requires": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "@types/dom-speech-recognition": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.1.tgz", @@ -5079,6 +5296,170 @@ "type": "^1.0.1" } }, + "d3": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", + "integrity": "sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "dependencies": { + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + } + } + }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -5092,11 +5473,76 @@ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.1.0.tgz", "integrity": "sha512-z/G2TQMyuf0X3qP+Mh+2PimoJD41VOCjViJzT0BHeL/+JQAofkiWZbWxlwFGb1N8EN+Cl/CW+MUKbVzr1689Cw==" }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "dependencies": { + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + } + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, "d3-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, + "d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "requires": { + "d3-array": "^3.2.0" + }, + "dependencies": { + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + } + } + }, + "d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "requires": { + "delaunator": "5" + } + }, "d3-dispatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", @@ -5111,16 +5557,64 @@ "d3-selection": "2" } }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, "d3-ease": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz", "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, "d3-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, + "d3-geo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", + "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, "d3-interpolate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", @@ -5134,6 +5628,21 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, "d3-scale": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", @@ -5146,6 +5655,15 @@ "d3-time-format": "2 - 3" } }, + "d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, "d3-selection": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", @@ -5493,6 +6011,14 @@ } } }, + "delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "requires": { + "robust-predicates": "^3.0.0" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -18614,6 +19140,11 @@ "glob": "^7.1.3" } }, + "robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, "rope-sequence": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz", @@ -18650,6 +19181,11 @@ } } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", diff --git a/package.json b/package.json index 1e43390d8..4a61f09a9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@types/connect-flash": "0.0.34", "@types/cookie-parser": "^1.4.2", "@types/cookie-session": "^2.0.44", + "@types/d3": "^7.4.0", "@types/dotenv": "^6.1.1", "@types/exif": "^0.6.3", "@types/express": "^4.17.13", @@ -66,17 +67,17 @@ "@types/prosemirror-view": "^1.23.1", "@types/rc-switch": "^1.9.2", "@types/react": "^18.0.15", - "@types/react-icons": "^3.0.0", - "@types/react-reconciler": "^0.26.4", - "@types/react-transition-group": "^4.4.5", "@types/react-autosuggest": "^9.3.14", "@types/react-color": "^2.17.6", "@types/react-datepicker": "^3.1.8", "@types/react-dom": "^18.0.6", "@types/react-grid-layout": "^1.3.2", + "@types/react-icons": "^3.0.0", "@types/react-measure": "^2.0.8", + "@types/react-reconciler": "^0.26.4", "@types/react-select": "^3.1.2", "@types/react-table": "^6.8.9", + "@types/react-transition-group": "^4.4.5", "@types/request": "^2.48.8", "@types/request-promise": "^4.1.48", "@types/rimraf": "^2.0.5", @@ -180,6 +181,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.0.0", "cors": "^2.8.5", + "d3": "^7.6.1", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", "exif": "^0.6.0", diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx index 42bd8a7f6..36d23d30e 100644 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx @@ -6,81 +6,37 @@ import { Doc } from '../../../../fields/Doc'; import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types'; import { action, computed, observable } from 'mobx'; import { Cast, StrCast } from '../../../../fields/Types'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +// import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { CategoricalChartState } from 'recharts/types/chart/generateCategoricalChart'; +import { LineChart } from './LineChart'; export interface ChartBoxProps { rootDoc: Doc; pairs: { x: number; y: number }[]; } -export interface ChartJsData { - labels: number[]; - datasets: { - label: string; - data: number[]; - backgroundColor: string[]; - }[]; -} - -// ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, PointElement, LineElement); - const primaryColor = 'rgba(53, 162, 235, 0.5)'; const selectedColor = 'rgba(255, 99, 132, 0.5)'; -const data = [ - { - name: 'Page A', - uv: 4000, - pv: 2400, - amt: 2400, - }, - { - name: 'Page B', - uv: 3000, - pv: 1398, - amt: 2210, - }, - { - name: 'Page C', - uv: 2000, - pv: 9800, - amt: 2290, - }, - { - name: 'Page D', - uv: 2780, - pv: 3908, - amt: 2000, - }, - { - name: 'Page E', - uv: 1890, - pv: 4800, - amt: 2181, - }, - { - name: 'Page F', - uv: 2390, - pv: 3800, - amt: 2500, - }, - { - name: 'Page G', - uv: 3490, - pv: 4300, - amt: 2100, - }, -]; - export interface RechartData { name: string | number; y: number; } +export interface DataPoints { + x: number; + y: number; +} + +export interface ChartData { + xLabel: string; + yLabel: string; + data: DataPoints[]; +} + @observer export class ChartBox extends React.Component { - @observable private _chartData: RechartData[] = []; + @observable private _chartData: ChartData | undefined = undefined; @computed get currView() { if (this.props.rootDoc._dataVizView) { @@ -104,16 +60,22 @@ export class ChartBox extends React.Component { return; } - // generate the chart data according to the RechartData type from the pairs prop - const chartData: RechartData[] = []; - this.props.pairs.forEach(pair => { - chartData.push({ - name: pair.x, - y: pair.y, + const data: ChartData = { + xLabel: '', + yLabel: '', + data: [], + }; + + if (this.props.pairs && this.props.pairs.length > 0) { + data.xLabel = 'x'; + data.yLabel = 'y'; + this.props.pairs.forEach(pair => { + data.data.push({ x: pair.x, y: pair.y }); }); - }); + } - this._chartData = chartData; + this._chartData = data; + this.props.rootDoc._chartData = JSON.stringify(data); } // @action @@ -200,35 +162,35 @@ export class ChartBox extends React.Component { // console.log(e); // } - @computed get reLineChart() { - return ( - - console.log(e)} - onMouseMove={e => this.onMouseMove(e)}> - - console.log(e)} /> - - - - - {/* */} - - - ); - } + // @computed get reLineChart() { + // return ( + // // + // console.log(e)} + // onMouseMove={e => this.onMouseMove(e)}> + // + // console.log(e)} /> + // + // + // + // + // {/* */} + // + // // + // ); + // } render() { - if (this.props.pairs && this._chartData.length > 0) { + if (this.props.pairs && this._chartData) { return (
@@ -237,7 +199,8 @@ export class ChartBox extends React.Component { ) : ( this.onClick(e)} /> )} */} - {this.reLineChart} + {/* {this.reLineChart} */} +
-
+
{formatTime(this._hoverTime - this.clipStart)}
{this._thumbnail && }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 0a51aea4e..0f4053127 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -36,7 +36,6 @@ display: flex; flex-direction: row; justify-content: center; - width: 100px; .row-button { width: 20px; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 7e903ca92..20d809232 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,15 +1,14 @@ import React = require('react'); +import { action, computed, observable, ObservableMap, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; +import { listSpec } from '../../../../fields/Schema'; +import { Cast } from '../../../../fields/Types'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; -import { SchemaRowBox } from './SchemaRowBox'; -import { action, computed, observable } from 'mobx'; -import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; -import { listSpec } from '../../../../fields/Schema'; import { SchemaColumnHeader } from './SchemaColumnHeader'; -import { List } from '../../../../fields/List'; -import { dropActionType } from '../../../util/DragManager'; +import { SchemaRowBox } from './SchemaRowBox'; export enum ColumnType { Number, @@ -23,11 +22,10 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'text', 'data', @observer export class CollectionSchemaView extends CollectionSubView() { - isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined); + private _lastSelectedRow: number | undefined; - @computed get layoutDoc() { - return Doc.Layout(this.props.Document); - } + @observable _rowMenuWidth: number = 60; + @observable _selectedDocs: ObservableSet = new ObservableSet(); @computed get columnKeys() { return Cast(this.props.Document.columnKeys, listSpec('string'), defaultColumnKeys); @@ -37,7 +35,7 @@ export class CollectionSchemaView extends CollectionSubView() { return Cast( this.props.Document.columnWidths, listSpec('number'), - this.columnKeys.map(() => (this.props.PanelWidth() - 100) / this.columnKeys.length) + this.columnKeys.map(() => (this.props.PanelWidth() - this._rowMenuWidth) / this.columnKeys.length) ); } @@ -49,6 +47,30 @@ export class CollectionSchemaView extends CollectionSubView() { return true; }; + @action + selectRow = (doc: Doc, ctrl?: boolean, shift?: boolean) => { + if (shift && this._lastSelectedRow !== undefined) { + const currSelectedRow = this.childDocs.indexOf(doc); + const startRow = Math.min(this._lastSelectedRow, currSelectedRow); + const endRow = Math.max(this._lastSelectedRow, currSelectedRow); + for (let i = startRow; i <= endRow; i++) { + const currDoc = this.childDocs[i]; + if (!this._selectedDocs.has(currDoc)) this._selectedDocs.add(currDoc); + } + } else if (ctrl) { + if (!this._selectedDocs.has(doc)) { + this._selectedDocs.add(doc); + this._lastSelectedRow = this.childDocs.indexOf(doc); + } else { + this._selectedDocs.delete(doc); + } + } else { + this._selectedDocs.clear(); + this._selectedDocs.add(doc); + this._lastSelectedRow = this.childDocs.indexOf(doc); + } + }; + render() { return (
@@ -63,17 +85,13 @@ export class CollectionSchemaView extends CollectionSubView() { ))}
@@ -82,6 +100,3 @@ export class CollectionSchemaView extends CollectionSubView() { ); } } -function DocListCast(childDocs: Doc[]): any { - throw new Error('Function not implemented.'); -} diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 50e2502dc..33f4e0bfc 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -1,25 +1,42 @@ import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, ObservableMap, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; -import './CollectionSchemaView.scss'; -import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent } from '../../DocComponent'; +import { Doc } from '../../../../fields/Doc'; +import { undoBatch } from '../../../util/UndoManager'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { Colors } from '../../global/globalEnums'; import { FieldViewProps } from '../../nodes/FieldView'; +import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { undoBatch } from '../../../util/UndoManager'; export interface SchemaRowBoxProps extends FieldViewProps { columnKeys: string[]; columnWidths: number[]; + rowMenuWidth: number; + selectedRows: ObservableSet; + selectRow: (doc: Doc, cmd?: boolean, shift?: boolean) => void; } @observer -export class SchemaRowBox extends ViewBoxAnnotatableComponent() { +export class SchemaRowBox extends ViewBoxBaseComponent() { + isSelected = () => this.props.selectedRows.has(this.props.Document); + + @action + onRowPointerDown = (e: React.PointerEvent) => { + e.stopPropagation(); + this.props.selectRow(this.props.Document, e.ctrlKey || e.metaKey, e.shiftKey); + } + render() { return ( -
-
-
this.props.removeDocument?.(this.props.Document))}> - +
+
+
{e.stopPropagation(); this.props.removeDocument?.(this.props.Document)})}> + +
+
{e.stopPropagation(); this.props.addDocTab(this.props.Document, 'add:right')}}> +
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 8437736ae..7bbd7c055 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -18,6 +18,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import './AudioBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; +import { SelectionManager } from '../../util/SelectionManager'; /** * AudioBox -- cgit v1.2.3-70-g09d2 From 9d2af1180f0dd5af5ab86b922cd8b0cdfcf4ea09 Mon Sep 17 00:00:00 2001 From: mehekj Date: Sat, 28 Jan 2023 22:38:41 -0500 Subject: checkpoint: schemarow as documentview functioning --- package-lock.json | 39 - src/client/documents/Documents.ts | 4 + .../collectionSchema/CollectionSchemaView.scss | 6 + .../collectionSchema/CollectionSchemaView.tsx | 129 +- .../collections/collectionSchema/SchemaRowBox.tsx | 80 +- .../OldCollectionSchemaCells.tsx | 1366 +++++++++---------- .../OldCollectionSchemaMovableColumn.tsx | 250 ++-- .../OldCollectionSchemaMovableRow.tsx | 274 ++-- .../OldCollectionSchemaView.tsx | 1298 +++++++++--------- .../old_collectionSchema/OldSchemaTable.tsx | 1388 ++++++++++---------- src/client/views/nodes/DocumentContentsView.tsx | 2 + 11 files changed, 2451 insertions(+), 2385 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 26e7a41d7..7f93ef3c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5303,16 +5303,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6480,28 +6470,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6513,7 +6481,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -21699,12 +21666,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1fd07d61d..58e318879 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1150,6 +1150,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); } + export function SchemaRowDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.SCHEMAROW), undefined, { ...(options || {}) }); + } + export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, viewType: CollectionViewType.Docking, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 4fa5d80e2..e0d0101a2 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -126,10 +126,16 @@ } } +.schema-row-wrapper { + max-height: 70px; + overflow: hidden; +} + .schema-header-row, .schema-row { display: flex; flex-direction: row; + height: 100%; max-height: 70px; overflow: auto; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index f7c68c803..c9f934aec 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,14 +1,14 @@ import React = require('react'); import { action, computed, observable, ObservableMap, ObservableSet, trace, untracked } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; +import { DataSym, Doc, DocListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; -import { Cast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -21,6 +21,9 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; +import { DocFocusOptions, DocumentView, ViewAdjustment } from '../../nodes/DocumentView'; +import { DefaultStyleProvider } from '../../StyleProvider'; +import { Transform } from '../../../util/Transform'; export enum ColumnType { Number, @@ -40,7 +43,7 @@ export class CollectionSchemaView extends CollectionSubView() { private _closestDropIndex: number = 0; private _minColWidth: number = 150; - @observable _rowMenuWidth: number = 100; + public static _rowMenuWidth: number = 100; @observable _selectedDocs: ObservableSet = new ObservableSet(); @observable _rowEles: ObservableMap = new ObservableMap(); @observable _isDragging: boolean = false; @@ -69,16 +72,15 @@ export class CollectionSchemaView extends CollectionSubView() { let widths = Cast( this.layoutDoc.columnWidths, listSpec('number'), - this.columnKeys.map(() => (this.props.PanelWidth() - this._rowMenuWidth) / this.columnKeys.length) + this.columnKeys.map(() => (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length) ); const totalWidth = widths.reduce((sum, width) => sum + width, 0); - if (totalWidth !== this.props.PanelWidth() - this._rowMenuWidth) { + if (totalWidth !== this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth) { widths = widths.map(w => { const proportion = w / totalWidth; - return proportion * (this.props.PanelWidth() - this._rowMenuWidth); + return proportion * (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth); }); - // this.layoutDoc.columnWidths = new List(widths); } return widths; @@ -110,8 +112,8 @@ export class CollectionSchemaView extends CollectionSubView() { let currWidths = [...this.storedColumnWidths]; const newColWidth = this.props.PanelWidth() / (currWidths.length + 1); currWidths = currWidths.map(w => { - const proportion = w / (this.props.PanelWidth() - this._rowMenuWidth); - return proportion * (this.props.PanelWidth() - this._rowMenuWidth - newColWidth); + const proportion = w / (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth); + return proportion * (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth - newColWidth); }); currWidths.splice(index + 1, 0, newColWidth); this.layoutDoc.columnWidths = new List(currWidths); @@ -133,8 +135,8 @@ export class CollectionSchemaView extends CollectionSubView() { let currWidths = [...this.storedColumnWidths]; const removedColWidth = currWidths[index]; currWidths = currWidths.map(w => { - const proportion = w / (this.props.PanelWidth() - this._rowMenuWidth - removedColWidth); - return proportion * (this.props.PanelWidth() - this._rowMenuWidth); + const proportion = w / (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth - removedColWidth); + return proportion * (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth); }); currWidths.splice(index, 1); this.layoutDoc.columnWidths = new List(currWidths); @@ -142,8 +144,6 @@ export class CollectionSchemaView extends CollectionSubView() { let currKeys = [...this.columnKeys]; currKeys.splice(index, 1); this.layoutDoc.columnKeys = new List(currKeys); - console.log([...this.storedColumnWidths]); - console.log([...this.columnKeys]); }; @action @@ -399,6 +399,34 @@ export class CollectionSchemaView extends CollectionSubView() { ContextMenu.Instance.displayMenu(x, y, undefined, true); }; + focusDocument = (doc: Doc, options: DocFocusOptions) => { + Doc.BrushDoc(doc); + + let focusSpeed = 0; + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + if (found) { + const top = found.getBoundingClientRect().top; + const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); + if (Math.floor(localTop[1]) !== 0) { + smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + } + } + const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; + this.props.focus(this.rootDoc, { + ...options, + afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), + }); + }; + + isChildContentActive = () => + this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; + + getDocTransform(doc: Doc, dref?: DocumentView) { + const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); + } + render() { return (
-
+
{this.columnKeys.map((key, index) => { return (
- {this.childDocs.map((doc: Doc, index: number) => ( - - ))} + {this.childDocs.map((doc: Doc, index: number) => { + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; + let dref: Opt; + + return ( +
+ (dref = r || undefined)} + LayoutTemplate={this.props.childLayoutTemplate} + LayoutTemplateString={SchemaRowBox.LayoutString(this.props.fieldKey)} + renderDepth={this.props.renderDepth + 1} + Document={doc} + DataDoc={dataDoc} + ContainingCollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.Document} + PanelWidth={this.props.PanelWidth} + PanelHeight={() => 70} + styleProvider={DefaultStyleProvider} + focus={this.focusDocument} + docFilters={this.childDocFilters} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + rootSelected={this.rootSelected} + ScreenToLocalTransform={() => this.getDocTransform(doc, dref)} + bringToFront={emptyFunction} + isContentActive={this.isChildContentActive} + hideDecorations={true} + hideTitle={true} + hideDocumentButtonBar={true} + /> +
+ ); + + // + })}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 5c1f32565..f790e9dbf 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -1,8 +1,8 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, ObservableMap, ObservableSet } from 'mobx'; +import { action, computed, ObservableMap, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; import { undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; @@ -11,6 +11,10 @@ import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; import { DragManager } from '../../../util/DragManager'; +import { OpenWhere } from '../../nodes/DocumentView'; +import { Cast } from '../../../../fields/Types'; +import { listSpec } from '../../../../fields/Schema'; +import { CollectionSchemaView } from './CollectionSchemaView'; export interface SchemaRowBoxProps extends FieldViewProps { rowIndex: number; @@ -26,40 +30,61 @@ export interface SchemaRowBoxProps extends FieldViewProps { } @observer -export class SchemaRowBox extends ViewBoxBaseComponent() { +export class SchemaRowBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SchemaRowBox, fieldKey); } private _ref: HTMLDivElement | null = null; - isSelected = () => this.props.selectedDocs.has(this.props.Document); bounds = () => this._ref?.getBoundingClientRect(); + @computed get columnKeys() { + return StrListCast(this.props.ContainingCollectionDoc?.columnKeys); + } + + @computed get storedColumnWidths() { + let widths = Cast( + this.props.ContainingCollectionDoc?.columnWidths, + listSpec('number'), + this.columnKeys.map(() => (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length) + ); + + const totalWidth = widths.reduce((sum, width) => sum + width, 0); + if (totalWidth !== this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth) { + widths = widths.map(w => { + const proportion = w / totalWidth; + return proportion * (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth); + }); + } + + return widths; + } + @action onRowPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); - setupMoveUpEvents( - this, - e, - e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex), - emptyFunction, - e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex) - ); + // setupMoveUpEvents( + // this, + // e, + // e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex), + // emptyFunction, + // e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex) + // ); }; onPointerEnter = (e: any) => { - if (!this.props.dragging) return; + // if (!this.props.dragging) return; document.removeEventListener('pointermove', this.onPointerMove); document.addEventListener('pointermove', this.onPointerMove); }; onPointerMove = (e: any) => { - if (!this.props.dragging) return; + // if (!this.props.dragging) return; let dragIsRow: boolean = true; DragManager.docsBeingDragged.forEach(doc => { - dragIsRow = this.props.selectedDocs.has(doc); + // dragIsRow = this.props.selectedDocs.has(doc); }); if (this._ref && dragIsRow) { const rect = this._ref.getBoundingClientRect(); @@ -69,11 +94,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { if (y <= halfLine) { this._ref.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; this._ref.style.borderBottom = '0px'; - this.props.dropIndex(this.props.rowIndex); + // this.props.dropIndex(this.props.rowIndex); } else if (y > halfLine) { this._ref.style.borderTop = '0px'; this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; - this.props.dropIndex(this.props.rowIndex + 1); + // this.props.dropIndex(this.props.rowIndex + 1); } } }; @@ -90,15 +115,22 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { return (
{ - row && this.props.addRowRef(this.props.Document, row); + // row && this.props.addRowRef(this.props.Document, row); this._ref = row; }}> -
+
{ @@ -111,14 +143,14 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { className="schema-row-button" onPointerDown={e => { e.stopPropagation(); - this.props.addDocTab(this.props.Document, 'add:right'); + this.props.addDocTab(this.props.Document, OpenWhere.addRight); }}>
- {this.props.columnKeys.map((key, index) => ( - + {this.columnKeys.map((key, index) => ( + ))}
diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx index 97a6c5c18..5953f85ad 100644 --- a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx +++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaCells.tsx @@ -1,683 +1,683 @@ -import React = require('react'); -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { extname } from 'path'; -import DatePicker from 'react-datepicker'; -import { CellInfo } from 'react-table'; -import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; -import { Id } from '../../../../fields/FieldSymbols'; -import { List } from '../../../../fields/List'; -import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { ComputedField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types'; -import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, Utils } from '../../../../Utils'; -import { Docs } from '../../../documents/Documents'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager } from '../../../util/DragManager'; -import { KeyCodes } from '../../../util/KeyCodes'; -import { CompileScript } from '../../../util/Scripting'; -import { SearchUtil } from '../../../util/SearchUtil'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { undoBatch } from '../../../util/UndoManager'; -import '../../../views/DocumentDecorations.scss'; -import { EditableView } from '../../EditableView'; -import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss'; -import { DocumentIconContainer } from '../../nodes/DocumentIcon'; -import { OverlayView } from '../../OverlayView'; -import { CollectionView } from '../CollectionView'; -import './CollectionSchemaView.scss'; -import { OpenWhere } from '../../nodes/DocumentView'; -import { PinProps } from '../../nodes/trails'; - -// intialize cell properties -export interface CellProps { - row: number; - col: number; - rowProps: CellInfo; - // currently unused - CollectionView: Opt; - // currently unused - ContainingCollection: Opt; - Document: Doc; - // column name - fieldKey: string; - // currently unused - renderDepth: number; - // called when a button is pressed on the node itself - addDocTab: (document: Doc, where: OpenWhere) => boolean; - pinToPres: (document: Doc, pinProps: PinProps) => void; - moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - isFocused: boolean; - changeFocusedCellByIndex: (row: number, col: number) => void; - // set whether the cell is in the isEditing mode - setIsEditing: (isEditing: boolean) => void; - isEditable: boolean; - setPreviewDoc: (doc: Doc) => void; - setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean; - getField: (row: number, col?: number) => void; - // currnetly unused - showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void; -} - -@observer -export class CollectionSchemaCell extends React.Component { - // return a field key that is corrected for whether it COMMENT - public static resolvedFieldKey(column: string, rowDoc: Doc) { - const fieldKey = column; - if (fieldKey.startsWith('*')) { - const rootKey = fieldKey.substring(1); - const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))]; - const matchedKeys = allKeys.filter(key => key.includes(rootKey)); - if (matchedKeys.length) return matchedKeys[0]; - } - return fieldKey; - } - @observable protected _isEditing: boolean = false; - protected _focusRef = React.createRef(); - protected _rowDoc = this.props.rowProps.original; - // Gets the serialized data in proto form of the base proto that this document's proto inherits from - protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original); - // methods for dragging and dropping - protected _dropDisposer?: DragManager.DragDropDisposer; - @observable contents: string = ''; - - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - } - - @action - onKeyDown = (e: KeyboardEvent): void => { - // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it - if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) { - document.removeEventListener('keydown', this.onKeyDown); - this._isEditing = true; - this.props.setIsEditing(true); - } - }; - - @action - isEditingCallback = (isEditing: boolean): void => { - // a general method that takes a boolean that determines whether the cell should be in - // is-editing mode - // remove the event listener if it's there - document.removeEventListener('keydown', this.onKeyDown); - // it's not already in is-editing mode, re-add the event listener - isEditing && document.addEventListener('keydown', this.onKeyDown); - this._isEditing = isEditing; - this.props.setIsEditing(isEditing); - this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - }; - - @action - onPointerDown = async (e: React.PointerEvent): Promise => { - // pan to the cell - this.onItemDown(e); - // focus on it - this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - this.props.setPreviewDoc(this.props.rowProps.original); - - let url: string; - if ((url = StrCast(this.props.rowProps.row.href))) { - // opens up the the doc in a new window, blurring the old one - try { - new URL(url); - const temp = window.open(url)!; - temp.blur(); - window.focus(); - } catch {} - } - - const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null); - doc && this.props.setPreviewDoc(doc); - }; - - @undoBatch - applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => { - // apply a specified change to the cell - const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) }); - if (!res.success) return false; - // change what is rendered to this new changed cell content - doc[this.renderFieldKey] = res.result; - return true; - // return whether the change was successful - }; - - private drop = (e: Event, de: DragManager.DropEvent) => { - // if the drag has data at its completion - if (de.complete.docDragData) { - // if only one doc was dragged - if (de.complete.docDragData.draggedDocuments.length === 1) { - // update the renderFieldKey - this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0]; - } else { - // create schema document reflecting the new column arrangement - const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {}); - this._rowDataDoc[this.renderFieldKey] = coll; - } - e.stopPropagation(); - } - }; - - protected dropRef = (ele: HTMLElement | null) => { - // if the drop disposer is not undefined, run its function - this._dropDisposer?.(); - // if ele is not null, give ele a non-undefined drop disposer - ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); - }; - - returnHighlights(contents: string, positions?: number[]) { - if (positions) { - const results = []; - StrCast(this.props.Document._searchString); - const length = StrCast(this.props.Document._searchString).length; - const color = contents ? 'black' : 'grey'; - - results.push( - - {contents?.slice(0, positions[0])} - - ); - positions.forEach((num, cur) => { - results.push( - - {contents?.slice(num, num + length)} - - ); - let end = 0; - cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]); - results.push( - - {contents?.slice(num + length, end)} - - ); - }); - return results; - } - return {contents ? contents?.valueOf() : 'undefined'}; - } - - @computed get renderFieldKey() { - // gets the resolved field key of this cell - return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); - } - - onItemDown = async (e: React.PointerEvent) => { - // if the document is a document used to change UI for search results in schema view - if (this.props.Document._searchDoc) { - const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); - const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); - // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc)); - } - }; - - renderCellWithType(type: string | undefined) { - const dragRef: React.RefObject = React.createRef(); - - // the column - const fieldKey = this.renderFieldKey; - // the exact cell - const field = this._rowDoc[fieldKey]; - - const onPointerEnter = (e: React.PointerEvent): void => { - // e.buttons === 1 means the left moue pointer is down - if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) { - dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over'; - } - }; - const onPointerLeave = (e: React.PointerEvent): void => { - // change the class name to indicate that the cell is no longer being dragged - dragRef.current!.className = 'collectionSchemaView-cellContainer'; - }; - - let contents = Field.toString(field as Field); - // display 2 hyphens instead of a blank box for empty cells - contents = contents === '' ? '--' : contents; - - // classname reflects the tatus of the cell - let className = 'collectionSchemaView-cellWrapper'; - if (this._isEditing) className += ' editing'; - if (this.props.isFocused && this.props.isEditable) className += ' focused'; - if (this.props.isFocused && !this.props.isEditable) className += ' inactive'; - - const positions = []; - if (StrCast(this.props.Document._searchString).toLowerCase() !== '') { - // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents - let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase(); - const search = StrCast(this.props.Document._searchString).toLowerCase(); - let start = term.indexOf(search); - let tally = 0; - // if search is found in term - if (start !== -1) { - positions.push(start); - } - // if search is found in term, continue finding all instances of search in term - while (start < contents?.length && start !== -1) { - term = term.slice(start + search.length + 1); - tally += start + search.length + 1; - start = term.indexOf(search); - positions.push(tally + start); - } - // remove the last position - if (positions.length > 1) { - positions.pop(); - } - } - const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined'; - return ( -
(this._isEditing = true))} - onPointerEnter={onPointerEnter} - onPointerLeave={onPointerLeave}> -
-
- {!this.props.Document._searchDoc ? ( - { - const cfield = ComputedField.WithoutComputed(() => FieldValue(field)); - const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; - const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1]; - return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : ''; - }} - SetValue={action((value: string) => { - // sets what is displayed after the user makes an input - let retVal = false; - if (value.startsWith(':=') || value.startsWith('=:=')) { - // decides how to compute a value when given either of the above strings - const script = value.substring(value.startsWith('=:=') ? 3 : 2); - retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col); - } else { - // check if the input is a number - let inputIsNum = true; - for (const s of value) { - if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) { - inputIsNum = false; - } - } - // check if the input is a boolean - const inputIsBool: boolean = value === 'false' || value === 'true'; - // what to do in the case - if (!inputIsNum && !inputIsBool && !value.startsWith('=')) { - // if it's not a number, it's a string, and should be processed as such - // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically - // after each edit - let valueSansQuotes = value; - if (this._isEditing) { - const vsqLength = valueSansQuotes.length; - // get rid of outer quotes - valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength); - } - let inputAsString = '"'; - // escape any quotes in the string - for (const i of valueSansQuotes) { - if (i === '"') { - inputAsString += '\\"'; - } else { - inputAsString += i; - } - } - // add a closing quote - inputAsString += '"'; - //two options here: we can strip off outer quotes or we can figure out what's going on with the script - const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); - const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length; - // change it if a change is made, otherwise, just compile using the old cell conetnts - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - // handle numbers and expressions - } else if (inputIsNum || value.startsWith('=')) { - //TODO: make accept numbers - const inputscript = value.substring(value.startsWith('=') ? 1 : 0); - // if commas are not stripped, the parser only considers the numbers after the last comma - let inputSansCommas = ''; - for (const s of inputscript) { - if (!(s === ',')) { - inputSansCommas += s; - } - } - const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); - const changeMade = value.length - 2 !== value.length; - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - // handle booleans - } else if (inputIsBool) { - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); - const changeMade = value.length - 2 !== value.length; - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - } - } - if (retVal) { - this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' - this.props.setIsEditing(false); - } - return retVal; - })} - OnFillDown={async (value: string) => { - // computes all of the value preceded by := - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); - script.compiled && - DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) => - value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run) - ); - }} - /> - ) : ( - this.returnHighlights(contents, positions) - )} -
-
-
- ); - } - - render() { - return this.renderCellWithType(undefined); - } -} - -@observer -export class CollectionSchemaNumberCell extends CollectionSchemaCell { - render() { - return this.renderCellWithType('number'); - } -} - -@observer -export class CollectionSchemaBooleanCell extends CollectionSchemaCell { - render() { - return this.renderCellWithType('boolean'); - } -} - -@observer -export class CollectionSchemaStringCell extends CollectionSchemaCell { - render() { - return this.renderCellWithType('string'); - } -} - -@observer -export class CollectionSchemaDateCell extends CollectionSchemaCell { - @computed get _date(): Opt { - // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined. - return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; - } - - @action - handleChange = (date: any) => { - // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } }); - // if (script.compiled) { - // this.applyToDoc(this._document, this.props.row, this.props.col, script.run); - // } else { - // ^ DateCast is always undefined for some reason, but that is what the field should be set to - this._rowDoc[this.renderFieldKey] = new DateField(date as Date); - //} - }; - - render() { - return !this.props.isFocused ? ( - {this._date ? Field.toString(this._date as Field) : '--'} - ) : ( - this.handleChange(date)} onChange={date => this.handleChange(date)} /> - ); - } -} - -@observer -export class CollectionSchemaDocCell extends CollectionSchemaCell { - _overlayDisposer?: () => void; - - @computed get _doc() { - return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); - } - - @action - onSetValue = (value: string) => { - this._doc && (Doc.GetProto(this._doc).title = value); - - const script = CompileScript(value, { - addReturn: true, - typecheck: true, - transformer: DocumentIconContainer.getTransformer(), - }); - // compile the script - const results = script.compiled && script.run(); - // if the script was compiled and run - if (results && results.success) { - this._rowDoc[this.renderFieldKey] = results.result; - return true; - } - return false; - }; - - componentWillUnmount() { - this.onBlur(); - } - - onBlur = () => { - this._overlayDisposer?.(); - }; - onFocus = () => { - this.onBlur(); - this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); - }; - - @action - isEditingCallback = (isEditing: boolean): void => { - // the isEditingCallback from a general CollectionSchemaCell - document.removeEventListener('keydown', this.onKeyDown); - isEditing && document.addEventListener('keydown', this.onKeyDown); - this._isEditing = isEditing; - this.props.setIsEditing(isEditing); - this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - }; - - render() { - // if there's a doc, render it - return !this._doc ? ( - this.renderCellWithType('document') - ) : ( -
-
- StrCast(this._doc?.title)} - SetValue={action((value: string) => { - this.onSetValue(value); - return true; - })} - /> -
-
this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton"> - -
-
- ); - } -} - -@observer -export class CollectionSchemaImageCell extends CollectionSchemaCell { - choosePath(url: URL) { - if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href - if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver - if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question - - const ext = extname(url.href); - return url.href.replace(ext, '_o' + ext); - } - - render() { - const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc - const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images - const altpaths = alts - .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) - .filter(url => url) - .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents - const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; - // If there is a path, follow it; otherwise, follow a link to a default image icon - const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; - - const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio - let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px - const height = Math.min(75, width / aspect); // get a height either proportional to that or 75 px - width = height * aspect; // increase the width of the image if necessary to maintain proportionality - - const reference = React.createRef(); - return ( -
-
- -
-
- ); - } -} - -@observer -export class CollectionSchemaListCell extends CollectionSchemaCell { - _overlayDisposer?: () => void; - - @computed get _field() { - return this._rowDoc[this.renderFieldKey]; - } - @computed get _optionsList() { - return this._field as List; - } - @observable private _opened = false; // whether the list is opened - @observable private _text = 'select an item'; - @observable private _selectedNum = 0; // the index of the list item selected - - @action - onSetValue = (value: string) => { - // change if it's a document - this._optionsList[this._selectedNum] = this._text = value; - - (this._field as List).splice(this._selectedNum, 1, value); - }; - - @action - onSelected = (element: string, index: number) => { - // if an item is selected, the private variables should update to reflect this - this._text = element; - this._selectedNum = index; - }; - - onFocus = () => { - this._overlayDisposer?.(); - this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); - }; - - render() { - const link = false; - const reference = React.createRef(); - - // if the list is not opened, don't display it; otherwise, do. - if (this._optionsList?.length) { - const options = !this._opened ? null : ( -
- {this._optionsList.map((element, index) => { - const val = Field.toString(element); - return ( -
this.onSelected(StrCast(element), index)}> - {val} -
- ); - })} -
- ); - - const plainText =
{this._text}
; - const textarea = ( -
- this._text} - SetValue={action((value: string) => { - // add special for params - this.onSetValue(value); - return true; - })} - /> -
- ); - - //☰ - return ( -
-
-
- -
{link ? plainText : textarea}
-
- {options} -
-
- ); - } - return this.renderCellWithType('list'); - } -} - -@observer -export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { - @computed get _isChecked() { - return BoolCast(this._rowDoc[this.renderFieldKey]); - } - - render() { - const reference = React.createRef(); - return ( -
- (this._rowDoc[this.renderFieldKey] = e.target.checked)} /> -
- ); - } -} - -@observer -export class CollectionSchemaButtons extends CollectionSchemaCell { - // the navigation buttons for schema view when it is used for search. - render() { - return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? ( - <> - ) : ( -
- - -
- ); - } -} +// import React = require('react'); +// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +// import { action, computed, observable } from 'mobx'; +// import { observer } from 'mobx-react'; +// import { extname } from 'path'; +// import DatePicker from 'react-datepicker'; +// import { CellInfo } from 'react-table'; +// import { DateField } from '../../../../fields/DateField'; +// import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +// import { Id } from '../../../../fields/FieldSymbols'; +// import { List } from '../../../../fields/List'; +// import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +// import { ComputedField } from '../../../../fields/ScriptField'; +// import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types'; +// import { ImageField } from '../../../../fields/URLField'; +// import { emptyFunction, Utils } from '../../../../Utils'; +// import { Docs } from '../../../documents/Documents'; +// import { DocumentType } from '../../../documents/DocumentTypes'; +// import { DocumentManager } from '../../../util/DocumentManager'; +// import { DragManager } from '../../../util/DragManager'; +// import { KeyCodes } from '../../../util/KeyCodes'; +// import { CompileScript } from '../../../util/Scripting'; +// import { SearchUtil } from '../../../util/SearchUtil'; +// import { SnappingManager } from '../../../util/SnappingManager'; +// import { undoBatch } from '../../../util/UndoManager'; +// import '../../../views/DocumentDecorations.scss'; +// import { EditableView } from '../../EditableView'; +// import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss'; +// import { DocumentIconContainer } from '../../nodes/DocumentIcon'; +// import { OverlayView } from '../../OverlayView'; +// import { CollectionView } from '../CollectionView'; +// import './CollectionSchemaView.scss'; +// import { OpenWhere } from '../../nodes/DocumentView'; +// import { PinProps } from '../../nodes/trails'; + +// // intialize cell properties +// export interface CellProps { +// row: number; +// col: number; +// rowProps: CellInfo; +// // currently unused +// CollectionView: Opt; +// // currently unused +// ContainingCollection: Opt; +// Document: Doc; +// // column name +// fieldKey: string; +// // currently unused +// renderDepth: number; +// // called when a button is pressed on the node itself +// addDocTab: (document: Doc, where: OpenWhere) => boolean; +// pinToPres: (document: Doc, pinProps: PinProps) => void; +// moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; +// isFocused: boolean; +// changeFocusedCellByIndex: (row: number, col: number) => void; +// // set whether the cell is in the isEditing mode +// setIsEditing: (isEditing: boolean) => void; +// isEditable: boolean; +// setPreviewDoc: (doc: Doc) => void; +// setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean; +// getField: (row: number, col?: number) => void; +// // currnetly unused +// showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void; +// } + +// @observer +// export class CollectionSchemaCell extends React.Component { +// // return a field key that is corrected for whether it COMMENT +// public static resolvedFieldKey(column: string, rowDoc: Doc) { +// const fieldKey = column; +// if (fieldKey.startsWith('*')) { +// const rootKey = fieldKey.substring(1); +// const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))]; +// const matchedKeys = allKeys.filter(key => key.includes(rootKey)); +// if (matchedKeys.length) return matchedKeys[0]; +// } +// return fieldKey; +// } +// @observable protected _isEditing: boolean = false; +// protected _focusRef = React.createRef(); +// protected _rowDoc = this.props.rowProps.original; +// // Gets the serialized data in proto form of the base proto that this document's proto inherits from +// protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original); +// // methods for dragging and dropping +// protected _dropDisposer?: DragManager.DragDropDisposer; +// @observable contents: string = ''; + +// componentDidMount() { +// document.addEventListener('keydown', this.onKeyDown); +// } +// componentWillUnmount() { +// document.removeEventListener('keydown', this.onKeyDown); +// } + +// @action +// onKeyDown = (e: KeyboardEvent): void => { +// // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it +// if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) { +// document.removeEventListener('keydown', this.onKeyDown); +// this._isEditing = true; +// this.props.setIsEditing(true); +// } +// }; + +// @action +// isEditingCallback = (isEditing: boolean): void => { +// // a general method that takes a boolean that determines whether the cell should be in +// // is-editing mode +// // remove the event listener if it's there +// document.removeEventListener('keydown', this.onKeyDown); +// // it's not already in is-editing mode, re-add the event listener +// isEditing && document.addEventListener('keydown', this.onKeyDown); +// this._isEditing = isEditing; +// this.props.setIsEditing(isEditing); +// this.props.changeFocusedCellByIndex(this.props.row, this.props.col); +// }; + +// @action +// onPointerDown = async (e: React.PointerEvent): Promise => { +// // pan to the cell +// this.onItemDown(e); +// // focus on it +// this.props.changeFocusedCellByIndex(this.props.row, this.props.col); +// this.props.setPreviewDoc(this.props.rowProps.original); + +// let url: string; +// if ((url = StrCast(this.props.rowProps.row.href))) { +// // opens up the the doc in a new window, blurring the old one +// try { +// new URL(url); +// const temp = window.open(url)!; +// temp.blur(); +// window.focus(); +// } catch {} +// } + +// const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null); +// doc && this.props.setPreviewDoc(doc); +// }; + +// @undoBatch +// applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => { +// // apply a specified change to the cell +// const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) }); +// if (!res.success) return false; +// // change what is rendered to this new changed cell content +// doc[this.renderFieldKey] = res.result; +// return true; +// // return whether the change was successful +// }; + +// private drop = (e: Event, de: DragManager.DropEvent) => { +// // if the drag has data at its completion +// if (de.complete.docDragData) { +// // if only one doc was dragged +// if (de.complete.docDragData.draggedDocuments.length === 1) { +// // update the renderFieldKey +// this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0]; +// } else { +// // create schema document reflecting the new column arrangement +// const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {}); +// this._rowDataDoc[this.renderFieldKey] = coll; +// } +// e.stopPropagation(); +// } +// }; + +// protected dropRef = (ele: HTMLElement | null) => { +// // if the drop disposer is not undefined, run its function +// this._dropDisposer?.(); +// // if ele is not null, give ele a non-undefined drop disposer +// ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); +// }; + +// returnHighlights(contents: string, positions?: number[]) { +// if (positions) { +// const results = []; +// StrCast(this.props.Document._searchString); +// const length = StrCast(this.props.Document._searchString).length; +// const color = contents ? 'black' : 'grey'; + +// results.push( +// +// {contents?.slice(0, positions[0])} +// +// ); +// positions.forEach((num, cur) => { +// results.push( +// +// {contents?.slice(num, num + length)} +// +// ); +// let end = 0; +// cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]); +// results.push( +// +// {contents?.slice(num + length, end)} +// +// ); +// }); +// return results; +// } +// return {contents ? contents?.valueOf() : 'undefined'}; +// } + +// @computed get renderFieldKey() { +// // gets the resolved field key of this cell +// return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); +// } + +// onItemDown = async (e: React.PointerEvent) => { +// // if the document is a document used to change UI for search results in schema view +// if (this.props.Document._searchDoc) { +// const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); +// const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); +// // Jump to the this document +// DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc)); +// } +// }; + +// renderCellWithType(type: string | undefined) { +// const dragRef: React.RefObject = React.createRef(); + +// // the column +// const fieldKey = this.renderFieldKey; +// // the exact cell +// const field = this._rowDoc[fieldKey]; + +// const onPointerEnter = (e: React.PointerEvent): void => { +// // e.buttons === 1 means the left moue pointer is down +// if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) { +// dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over'; +// } +// }; +// const onPointerLeave = (e: React.PointerEvent): void => { +// // change the class name to indicate that the cell is no longer being dragged +// dragRef.current!.className = 'collectionSchemaView-cellContainer'; +// }; + +// let contents = Field.toString(field as Field); +// // display 2 hyphens instead of a blank box for empty cells +// contents = contents === '' ? '--' : contents; + +// // classname reflects the tatus of the cell +// let className = 'collectionSchemaView-cellWrapper'; +// if (this._isEditing) className += ' editing'; +// if (this.props.isFocused && this.props.isEditable) className += ' focused'; +// if (this.props.isFocused && !this.props.isEditable) className += ' inactive'; + +// const positions = []; +// if (StrCast(this.props.Document._searchString).toLowerCase() !== '') { +// // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents +// let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase(); +// const search = StrCast(this.props.Document._searchString).toLowerCase(); +// let start = term.indexOf(search); +// let tally = 0; +// // if search is found in term +// if (start !== -1) { +// positions.push(start); +// } +// // if search is found in term, continue finding all instances of search in term +// while (start < contents?.length && start !== -1) { +// term = term.slice(start + search.length + 1); +// tally += start + search.length + 1; +// start = term.indexOf(search); +// positions.push(tally + start); +// } +// // remove the last position +// if (positions.length > 1) { +// positions.pop(); +// } +// } +// const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined'; +// return ( +//
(this._isEditing = true))} +// onPointerEnter={onPointerEnter} +// onPointerLeave={onPointerLeave}> +//
+//
+// {!this.props.Document._searchDoc ? ( +// { +// const cfield = ComputedField.WithoutComputed(() => FieldValue(field)); +// const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; +// const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1]; +// return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : ''; +// }} +// SetValue={action((value: string) => { +// // sets what is displayed after the user makes an input +// let retVal = false; +// if (value.startsWith(':=') || value.startsWith('=:=')) { +// // decides how to compute a value when given either of the above strings +// const script = value.substring(value.startsWith('=:=') ? 3 : 2); +// retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col); +// } else { +// // check if the input is a number +// let inputIsNum = true; +// for (const s of value) { +// if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) { +// inputIsNum = false; +// } +// } +// // check if the input is a boolean +// const inputIsBool: boolean = value === 'false' || value === 'true'; +// // what to do in the case +// if (!inputIsNum && !inputIsBool && !value.startsWith('=')) { +// // if it's not a number, it's a string, and should be processed as such +// // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically +// // after each edit +// let valueSansQuotes = value; +// if (this._isEditing) { +// const vsqLength = valueSansQuotes.length; +// // get rid of outer quotes +// valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength); +// } +// let inputAsString = '"'; +// // escape any quotes in the string +// for (const i of valueSansQuotes) { +// if (i === '"') { +// inputAsString += '\\"'; +// } else { +// inputAsString += i; +// } +// } +// // add a closing quote +// inputAsString += '"'; +// //two options here: we can strip off outer quotes or we can figure out what's going on with the script +// const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); +// const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length; +// // change it if a change is made, otherwise, just compile using the old cell conetnts +// script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); +// // handle numbers and expressions +// } else if (inputIsNum || value.startsWith('=')) { +// //TODO: make accept numbers +// const inputscript = value.substring(value.startsWith('=') ? 1 : 0); +// // if commas are not stripped, the parser only considers the numbers after the last comma +// let inputSansCommas = ''; +// for (const s of inputscript) { +// if (!(s === ',')) { +// inputSansCommas += s; +// } +// } +// const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); +// const changeMade = value.length - 2 !== value.length; +// script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); +// // handle booleans +// } else if (inputIsBool) { +// const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); +// const changeMade = value.length - 2 !== value.length; +// script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); +// } +// } +// if (retVal) { +// this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' +// this.props.setIsEditing(false); +// } +// return retVal; +// })} +// OnFillDown={async (value: string) => { +// // computes all of the value preceded by := +// const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } }); +// script.compiled && +// DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) => +// value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run) +// ); +// }} +// /> +// ) : ( +// this.returnHighlights(contents, positions) +// )} +//
+//
+//
+// ); +// } + +// render() { +// return this.renderCellWithType(undefined); +// } +// } + +// @observer +// export class CollectionSchemaNumberCell extends CollectionSchemaCell { +// render() { +// return this.renderCellWithType('number'); +// } +// } + +// @observer +// export class CollectionSchemaBooleanCell extends CollectionSchemaCell { +// render() { +// return this.renderCellWithType('boolean'); +// } +// } + +// @observer +// export class CollectionSchemaStringCell extends CollectionSchemaCell { +// render() { +// return this.renderCellWithType('string'); +// } +// } + +// @observer +// export class CollectionSchemaDateCell extends CollectionSchemaCell { +// @computed get _date(): Opt { +// // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined. +// return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; +// } + +// @action +// handleChange = (date: any) => { +// // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } }); +// // if (script.compiled) { +// // this.applyToDoc(this._document, this.props.row, this.props.col, script.run); +// // } else { +// // ^ DateCast is always undefined for some reason, but that is what the field should be set to +// this._rowDoc[this.renderFieldKey] = new DateField(date as Date); +// //} +// }; + +// render() { +// return !this.props.isFocused ? ( +// {this._date ? Field.toString(this._date as Field) : '--'} +// ) : ( +// this.handleChange(date)} onChange={date => this.handleChange(date)} /> +// ); +// } +// } + +// @observer +// export class CollectionSchemaDocCell extends CollectionSchemaCell { +// _overlayDisposer?: () => void; + +// @computed get _doc() { +// return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); +// } + +// @action +// onSetValue = (value: string) => { +// this._doc && (Doc.GetProto(this._doc).title = value); + +// const script = CompileScript(value, { +// addReturn: true, +// typecheck: true, +// transformer: DocumentIconContainer.getTransformer(), +// }); +// // compile the script +// const results = script.compiled && script.run(); +// // if the script was compiled and run +// if (results && results.success) { +// this._rowDoc[this.renderFieldKey] = results.result; +// return true; +// } +// return false; +// }; + +// componentWillUnmount() { +// this.onBlur(); +// } + +// onBlur = () => { +// this._overlayDisposer?.(); +// }; +// onFocus = () => { +// this.onBlur(); +// this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); +// }; + +// @action +// isEditingCallback = (isEditing: boolean): void => { +// // the isEditingCallback from a general CollectionSchemaCell +// document.removeEventListener('keydown', this.onKeyDown); +// isEditing && document.addEventListener('keydown', this.onKeyDown); +// this._isEditing = isEditing; +// this.props.setIsEditing(isEditing); +// this.props.changeFocusedCellByIndex(this.props.row, this.props.col); +// }; + +// render() { +// // if there's a doc, render it +// return !this._doc ? ( +// this.renderCellWithType('document') +// ) : ( +//
+//
+// StrCast(this._doc?.title)} +// SetValue={action((value: string) => { +// this.onSetValue(value); +// return true; +// })} +// /> +//
+//
this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton"> +// +//
+//
+// ); +// } +// } + +// @observer +// export class CollectionSchemaImageCell extends CollectionSchemaCell { +// choosePath(url: URL) { +// if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href +// if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver +// if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question + +// const ext = extname(url.href); +// return url.href.replace(ext, '_o' + ext); +// } + +// render() { +// const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc +// const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images +// const altpaths = alts +// .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) +// .filter(url => url) +// .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents +// const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; +// // If there is a path, follow it; otherwise, follow a link to a default image icon +// const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; + +// const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio +// let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px +// const height = Math.min(75, width / aspect); // get a height either proportional to that or 75 px +// width = height * aspect; // increase the width of the image if necessary to maintain proportionality + +// const reference = React.createRef(); +// return ( +//
+//
+// +//
+//
+// ); +// } +// } + +// @observer +// export class CollectionSchemaListCell extends CollectionSchemaCell { +// _overlayDisposer?: () => void; + +// @computed get _field() { +// return this._rowDoc[this.renderFieldKey]; +// } +// @computed get _optionsList() { +// return this._field as List; +// } +// @observable private _opened = false; // whether the list is opened +// @observable private _text = 'select an item'; +// @observable private _selectedNum = 0; // the index of the list item selected + +// @action +// onSetValue = (value: string) => { +// // change if it's a document +// this._optionsList[this._selectedNum] = this._text = value; + +// (this._field as List).splice(this._selectedNum, 1, value); +// }; + +// @action +// onSelected = (element: string, index: number) => { +// // if an item is selected, the private variables should update to reflect this +// this._text = element; +// this._selectedNum = index; +// }; + +// onFocus = () => { +// this._overlayDisposer?.(); +// this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); +// }; + +// render() { +// const link = false; +// const reference = React.createRef(); + +// // if the list is not opened, don't display it; otherwise, do. +// if (this._optionsList?.length) { +// const options = !this._opened ? null : ( +//
+// {this._optionsList.map((element, index) => { +// const val = Field.toString(element); +// return ( +//
this.onSelected(StrCast(element), index)}> +// {val} +//
+// ); +// })} +//
+// ); + +// const plainText =
{this._text}
; +// const textarea = ( +//
+// this._text} +// SetValue={action((value: string) => { +// // add special for params +// this.onSetValue(value); +// return true; +// })} +// /> +//
+// ); + +// //☰ +// return ( +//
+//
+//
+// +//
{link ? plainText : textarea}
+//
+// {options} +//
+//
+// ); +// } +// return this.renderCellWithType('list'); +// } +// } + +// @observer +// export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { +// @computed get _isChecked() { +// return BoolCast(this._rowDoc[this.renderFieldKey]); +// } + +// render() { +// const reference = React.createRef(); +// return ( +//
+// (this._rowDoc[this.renderFieldKey] = e.target.checked)} /> +//
+// ); +// } +// } + +// @observer +// export class CollectionSchemaButtons extends CollectionSchemaCell { +// // the navigation buttons for schema view when it is used for search. +// render() { +// return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? ( +// <> +// ) : ( +//
+// +// +//
+// ); +// } +// } diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx index 28d2e6ab1..c2182ae0c 100644 --- a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx +++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableColumn.tsx @@ -1,138 +1,138 @@ -import React = require('react'); -import { action } from 'mobx'; -import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { DragManager } from '../../../util/DragManager'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { Transform } from '../../../util/Transform'; -import './CollectionSchemaView.scss'; +// import React = require('react'); +// import { action } from 'mobx'; +// import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +// import { DragManager } from '../../../util/DragManager'; +// import { SnappingManager } from '../../../util/SnappingManager'; +// import { Transform } from '../../../util/Transform'; +// import './CollectionSchemaView.scss'; -export interface MovableColumnProps { - columnRenderer: React.ReactNode; - columnValue: SchemaHeaderField; - allColumns: SchemaHeaderField[]; - reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void; - ScreenToLocalTransform: () => Transform; -} -export class MovableColumn extends React.Component { - // The header of the column - private _header?: React.RefObject = React.createRef(); - // The container of the function that is responsible for moving the column over to a new plac - private _colDropDisposer?: DragManager.DragDropDisposer; - // initial column position - private _startDragPosition: { x: number; y: number } = { x: 0, y: 0 }; - // sensitivity to being dragged, in pixels - private _sensitivity: number = 16; - // Column reference ID - private _dragRef: React.RefObject = React.createRef(); +// export interface MovableColumnProps { +// columnRenderer: React.ReactNode; +// columnValue: SchemaHeaderField; +// allColumns: SchemaHeaderField[]; +// reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void; +// ScreenToLocalTransform: () => Transform; +// } +// export class MovableColumn extends React.Component { +// // The header of the column +// private _header?: React.RefObject = React.createRef(); +// // The container of the function that is responsible for moving the column over to a new plac +// private _colDropDisposer?: DragManager.DragDropDisposer; +// // initial column position +// private _startDragPosition: { x: number; y: number } = { x: 0, y: 0 }; +// // sensitivity to being dragged, in pixels +// private _sensitivity: number = 16; +// // Column reference ID +// private _dragRef: React.RefObject = React.createRef(); - onPointerEnter = (e: React.PointerEvent): void => { - // if the column is left-clicked and it is being dragged - if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = 'collectionSchema-col-wrapper'; - document.addEventListener('pointermove', this.onDragMove, true); - } - }; +// onPointerEnter = (e: React.PointerEvent): void => { +// // if the column is left-clicked and it is being dragged +// if (e.buttons === 1 && SnappingManager.GetIsDragging()) { +// this._header!.current!.className = 'collectionSchema-col-wrapper'; +// document.addEventListener('pointermove', this.onDragMove, true); +// } +// }; - onPointerLeave = (e: React.PointerEvent): void => { - this._header!.current!.className = 'collectionSchema-col-wrapper'; - document.removeEventListener('pointermove', this.onDragMove, true); - !e.buttons && document.removeEventListener('pointermove', this.onPointerMove); - }; +// onPointerLeave = (e: React.PointerEvent): void => { +// this._header!.current!.className = 'collectionSchema-col-wrapper'; +// document.removeEventListener('pointermove', this.onDragMove, true); +// !e.buttons && document.removeEventListener('pointermove', this.onPointerMove); +// }; - onDragMove = (e: PointerEvent): void => { - // only take into account the horizonal direction when a column is dragged - const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); - const rect = this._header!.current!.getBoundingClientRect(); - // Now store the point at the top center of the column when it was in its original position - const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top); - // to be compared with its new horizontal position - const before = x[0] < bounds[0]; - this._header!.current!.className = 'collectionSchema-col-wrapper'; - if (before) this._header!.current!.className += ' col-before'; - if (!before) this._header!.current!.className += ' col-after'; - e.stopPropagation(); - }; +// onDragMove = (e: PointerEvent): void => { +// // only take into account the horizonal direction when a column is dragged +// const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); +// const rect = this._header!.current!.getBoundingClientRect(); +// // Now store the point at the top center of the column when it was in its original position +// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top); +// // to be compared with its new horizontal position +// const before = x[0] < bounds[0]; +// this._header!.current!.className = 'collectionSchema-col-wrapper'; +// if (before) this._header!.current!.className += ' col-before'; +// if (!before) this._header!.current!.className += ' col-after'; +// e.stopPropagation(); +// }; - createColDropTarget = (ele: HTMLDivElement) => { - this._colDropDisposer?.(); - if (ele) { - this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this)); - } - }; +// createColDropTarget = (ele: HTMLDivElement) => { +// this._colDropDisposer?.(); +// if (ele) { +// this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this)); +// } +// }; - colDrop = (e: Event, de: DragManager.DropEvent) => { - document.removeEventListener('pointermove', this.onDragMove, true); - // we only care about whether the column is shifted to the side - const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); - // get the dimensions of the smallest rectangle that bounds the header - const rect = this._header!.current!.getBoundingClientRect(); - const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top); - // get whether the column was dragged before or after where it is now - const before = x[0] < bounds[0]; - const colDragData = de.complete.columnDragData; - // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables - if (colDragData) { - e.stopPropagation(); - this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns); - return true; - } - return false; - }; +// colDrop = (e: Event, de: DragManager.DropEvent) => { +// document.removeEventListener('pointermove', this.onDragMove, true); +// // we only care about whether the column is shifted to the side +// const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); +// // get the dimensions of the smallest rectangle that bounds the header +// const rect = this._header!.current!.getBoundingClientRect(); +// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + (rect.right - rect.left) / 2, rect.top); +// // get whether the column was dragged before or after where it is now +// const before = x[0] < bounds[0]; +// const colDragData = de.complete.columnDragData; +// // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables +// if (colDragData) { +// e.stopPropagation(); +// this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns); +// return true; +// } +// return false; +// }; - onPointerMove = (e: PointerEvent) => { - const onRowMove = (e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); +// onPointerMove = (e: PointerEvent) => { +// const onRowMove = (e: PointerEvent) => { +// e.stopPropagation(); +// e.preventDefault(); - document.removeEventListener('pointermove', onRowMove); - document.removeEventListener('pointerup', onRowUp); - const dragData = new DragManager.ColumnDragData(this.props.columnValue); - DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y); - }; - const onRowUp = (): void => { - document.removeEventListener('pointermove', onRowMove); - document.removeEventListener('pointerup', onRowUp); - }; - // if the left mouse button is the one being held - if (e.buttons === 1) { - const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); - // If the movemnt of the drag exceeds the sensitivity value - if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { - document.removeEventListener('pointermove', this.onPointerMove); - e.stopPropagation(); +// document.removeEventListener('pointermove', onRowMove); +// document.removeEventListener('pointerup', onRowUp); +// const dragData = new DragManager.ColumnDragData(this.props.columnValue); +// DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y); +// }; +// const onRowUp = (): void => { +// document.removeEventListener('pointermove', onRowMove); +// document.removeEventListener('pointerup', onRowUp); +// }; +// // if the left mouse button is the one being held +// if (e.buttons === 1) { +// const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); +// // If the movemnt of the drag exceeds the sensitivity value +// if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { +// document.removeEventListener('pointermove', this.onPointerMove); +// e.stopPropagation(); - document.addEventListener('pointermove', onRowMove); - document.addEventListener('pointerup', onRowUp); - } - } - }; +// document.addEventListener('pointermove', onRowMove); +// document.addEventListener('pointerup', onRowUp); +// } +// } +// }; - onPointerUp = (e: React.PointerEvent) => { - document.removeEventListener('pointermove', this.onPointerMove); - }; +// onPointerUp = (e: React.PointerEvent) => { +// document.removeEventListener('pointermove', this.onPointerMove); +// }; - @action - onPointerDown = (e: React.PointerEvent, ref: React.RefObject) => { - this._dragRef = ref; - const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY); - // If the cell thing dragged is not being edited - if (!(e.target as any)?.tagName.includes('INPUT')) { - this._startDragPosition = { x: dx, y: dy }; - document.addEventListener('pointermove', this.onPointerMove); - } - }; +// @action +// onPointerDown = (e: React.PointerEvent, ref: React.RefObject) => { +// this._dragRef = ref; +// const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY); +// // If the cell thing dragged is not being edited +// if (!(e.target as any)?.tagName.includes('INPUT')) { +// this._startDragPosition = { x: dx, y: dy }; +// document.addEventListener('pointermove', this.onPointerMove); +// } +// }; - render() { - const reference = React.createRef(); +// render() { +// const reference = React.createRef(); - return ( -
-
-
this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}> - {this.props.columnRenderer} -
-
-
- ); - } -} +// return ( +//
+//
+//
this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}> +// {this.props.columnRenderer} +//
+//
+//
+// ); +// } +// } diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx index 3cb2df7d3..2b39df201 100644 --- a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx +++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaMovableRow.tsx @@ -1,152 +1,152 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action } from 'mobx'; -import * as React from 'react'; -import { ReactTableDefaults, RowInfo } from 'react-table'; -import { Doc } from '../../../../fields/Doc'; -import { Cast, FieldValue, StrCast } from '../../../../fields/Types'; -import { DocumentManager } from '../../../util/DocumentManager'; -import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { Transform } from '../../../util/Transform'; -import { undoBatch } from '../../../util/UndoManager'; -import { ContextMenu } from '../../ContextMenu'; -import { OpenWhere } from '../../nodes/DocumentView'; -import './CollectionSchemaView.scss'; +// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +// import { action } from 'mobx'; +// import * as React from 'react'; +// import { ReactTableDefaults, RowInfo } from 'react-table'; +// import { Doc } from '../../../../fields/Doc'; +// import { Cast, FieldValue, StrCast } from '../../../../fields/Types'; +// import { DocumentManager } from '../../../util/DocumentManager'; +// import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager'; +// import { SnappingManager } from '../../../util/SnappingManager'; +// import { Transform } from '../../../util/Transform'; +// import { undoBatch } from '../../../util/UndoManager'; +// import { ContextMenu } from '../../ContextMenu'; +// import { OpenWhere } from '../../nodes/DocumentView'; +// import './CollectionSchemaView.scss'; -export interface MovableRowProps { - rowInfo: RowInfo; - ScreenToLocalTransform: () => Transform; - addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; - removeDoc: (doc: Doc | Doc[]) => boolean; - rowFocused: boolean; - textWrapRow: (doc: Doc) => void; - rowWrapped: boolean; - dropAction: string; - addDocTab: any; -} +// export interface MovableRowProps { +// rowInfo: RowInfo; +// ScreenToLocalTransform: () => Transform; +// addDoc: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; +// removeDoc: (doc: Doc | Doc[]) => boolean; +// rowFocused: boolean; +// textWrapRow: (doc: Doc) => void; +// rowWrapped: boolean; +// dropAction: string; +// addDocTab: any; +// } -export class MovableRow extends React.Component> { - private _header?: React.RefObject = React.createRef(); - private _rowDropDisposer?: DragManager.DragDropDisposer; +// export class MovableRow extends React.Component> { +// private _header?: React.RefObject = React.createRef(); +// private _rowDropDisposer?: DragManager.DragDropDisposer; - // Event listeners are only necessary when the user is hovering over the table - // Create one when the mouse starts hovering... - onPointerEnter = (e: React.PointerEvent): void => { - if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = 'collectionSchema-row-wrapper'; - document.addEventListener('pointermove', this.onDragMove, true); - } - }; - // ... and delete it when the mouse leaves - onPointerLeave = (e: React.PointerEvent): void => { - this._header!.current!.className = 'collectionSchema-row-wrapper'; - document.removeEventListener('pointermove', this.onDragMove, true); - }; - // The method for the event listener, reorders columns when dragged to their new locations. - onDragMove = (e: PointerEvent): void => { - const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); - const rect = this._header!.current!.getBoundingClientRect(); - const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); - const before = x[1] < bounds[1]; - this._header!.current!.className = 'collectionSchema-row-wrapper'; - if (before) this._header!.current!.className += ' row-above'; - if (!before) this._header!.current!.className += ' row-below'; - e.stopPropagation(); - }; - componentWillUnmount() { - this._rowDropDisposer?.(); - } - // - createRowDropTarget = (ele: HTMLDivElement) => { - this._rowDropDisposer?.(); - if (ele) { - this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); - } - }; - // Controls what hppens when a row is dragged and dropped - rowDrop = (e: Event, de: DragManager.DropEvent) => { - this.onPointerLeave(e as any); - const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc)); - if (!rowDoc) return false; +// // Event listeners are only necessary when the user is hovering over the table +// // Create one when the mouse starts hovering... +// onPointerEnter = (e: React.PointerEvent): void => { +// if (e.buttons === 1 && SnappingManager.GetIsDragging()) { +// this._header!.current!.className = 'collectionSchema-row-wrapper'; +// document.addEventListener('pointermove', this.onDragMove, true); +// } +// }; +// // ... and delete it when the mouse leaves +// onPointerLeave = (e: React.PointerEvent): void => { +// this._header!.current!.className = 'collectionSchema-row-wrapper'; +// document.removeEventListener('pointermove', this.onDragMove, true); +// }; +// // The method for the event listener, reorders columns when dragged to their new locations. +// onDragMove = (e: PointerEvent): void => { +// const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); +// const rect = this._header!.current!.getBoundingClientRect(); +// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); +// const before = x[1] < bounds[1]; +// this._header!.current!.className = 'collectionSchema-row-wrapper'; +// if (before) this._header!.current!.className += ' row-above'; +// if (!before) this._header!.current!.className += ' row-below'; +// e.stopPropagation(); +// }; +// componentWillUnmount() { +// this._rowDropDisposer?.(); +// } +// // +// createRowDropTarget = (ele: HTMLDivElement) => { +// this._rowDropDisposer?.(); +// if (ele) { +// this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); +// } +// }; +// // Controls what hppens when a row is dragged and dropped +// rowDrop = (e: Event, de: DragManager.DropEvent) => { +// this.onPointerLeave(e as any); +// const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc)); +// if (!rowDoc) return false; - const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); - const rect = this._header!.current!.getBoundingClientRect(); - const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); - const before = x[1] < bounds[1]; +// const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); +// const rect = this._header!.current!.getBoundingClientRect(); +// const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); +// const before = x[1] < bounds[1]; - const docDragData = de.complete.docDragData; - if (docDragData) { - e.stopPropagation(); - if (docDragData.draggedDocuments[0] === rowDoc) return true; - const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before); - const movedDocs = docDragData.draggedDocuments; - return docDragData.dropAction || docDragData.userDropAction - ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) - : docDragData.moveDocument - ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) - : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); - } - return false; - }; +// const docDragData = de.complete.docDragData; +// if (docDragData) { +// e.stopPropagation(); +// if (docDragData.draggedDocuments[0] === rowDoc) return true; +// const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before); +// const movedDocs = docDragData.draggedDocuments; +// return docDragData.dropAction || docDragData.userDropAction +// ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) +// : docDragData.moveDocument +// ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) +// : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); +// } +// return false; +// }; - onRowContextMenu = (e: React.MouseEvent): void => { - const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row'; - ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' }); - }; +// onRowContextMenu = (e: React.MouseEvent): void => { +// const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row'; +// ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' }); +// }; - @undoBatch - @action - move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => { - const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection); - return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); - }; +// @undoBatch +// @action +// move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => { +// const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection); +// return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); +// }; - @action - onKeyDown = (e: React.KeyboardEvent) => { - console.log('yes'); - if (e.key === 'Backspace' || e.key === 'Delete') { - undoBatch(() => this.props.removeDoc(this.props.rowInfo.original)); - } - }; +// @action +// onKeyDown = (e: React.KeyboardEvent) => { +// console.log('yes'); +// if (e.key === 'Backspace' || e.key === 'Delete') { +// undoBatch(() => this.props.removeDoc(this.props.rowInfo.original)); +// } +// }; - render() { - const { children = null, rowInfo } = this.props; +// render() { +// const { children = null, rowInfo } = this.props; - if (!rowInfo) { - return {children}; - } +// if (!rowInfo) { +// return {children}; +// } - const { original } = rowInfo; - const doc = FieldValue(Cast(original, Doc)); +// const { original } = rowInfo; +// const doc = FieldValue(Cast(original, Doc)); - if (!doc) return null; +// if (!doc) return null; - const reference = React.createRef(); - const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType); +// const reference = React.createRef(); +// const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType); - let className = 'collectionSchema-row'; - if (this.props.rowFocused) className += ' row-focused'; - if (this.props.rowWrapped) className += ' row-wrapped'; +// let className = 'collectionSchema-row'; +// if (this.props.rowFocused) className += ' row-focused'; +// if (this.props.rowWrapped) className += ' row-wrapped'; - return ( -
-
- -
-
this.props.removeDoc(this.props.rowInfo.original))}> - -
-
- -
-
this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}> - -
-
- {children} -
-
-
- ); - } -} +// return ( +//
+//
+// +//
+//
this.props.removeDoc(this.props.rowInfo.original))}> +// +//
+//
+// +//
+//
this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}> +// +//
+//
+// {children} +//
+//
+//
+// ); +// } +// } diff --git a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx index 260db4b88..f3f09cbf0 100644 --- a/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx +++ b/src/client/views/collections/old_collectionSchema/OldCollectionSchemaView.tsx @@ -1,649 +1,649 @@ -import React = require('react'); -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, untracked } from 'mobx'; -import { observer } from 'mobx-react'; -import Measure from 'react-measure'; -// import { Resize } from 'react-table'; -import { Doc, Opt } from '../../../../fields/Doc'; -import { List } from '../../../../fields/List'; -import { listSpec } from '../../../../fields/Schema'; -import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { Cast, NumCast } from '../../../../fields/Types'; -import { TraceMobx } from '../../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; -import { DocUtils } from '../../../documents/Documents'; -import { SelectionManager } from '../../../util/SelectionManager'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { Transform } from '../../../util/Transform'; -import { undoBatch } from '../../../util/UndoManager'; -import { ContextMenu } from '../../ContextMenu'; -import { ContextMenuProps } from '../../ContextMenuItem'; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { DocumentView } from '../../nodes/DocumentView'; -import { DefaultStyleProvider } from '../../StyleProvider'; -import { CollectionSubView } from '../CollectionSubView'; -import './CollectionSchemaView.scss'; -// import { SchemaTable } from './SchemaTable'; -// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 - -export enum ColumnType { - Any, - Number, - String, - Boolean, - Doc, - Image, - List, - Date, -} -// this map should be used for keys that should have a const type of value -const columnTypes: Map = new Map([ - ['title', ColumnType.String], - ['x', ColumnType.Number], - ['y', ColumnType.Number], - ['_width', ColumnType.Number], - ['_height', ColumnType.Number], - ['_nativeWidth', ColumnType.Number], - ['_nativeHeight', ColumnType.Number], - ['isPrototype', ColumnType.Boolean], - ['_curPage', ColumnType.Number], - ['_currentTimecode', ColumnType.Number], - ['zIndex', ColumnType.Number], -]); - -@observer -export class CollectionSchemaView extends CollectionSubView() { - private _previewCont?: HTMLDivElement; - - @observable _previewDoc: Doc | undefined = undefined; - @observable _focusedTable: Doc = this.props.Document; - @observable _col: any = ''; - @observable _menuWidth = 0; - @observable _headerOpen = false; - @observable _headerIsEditing = false; - @observable _menuHeight = 0; - @observable _pointerX = 0; - @observable _pointerY = 0; - @observable _openTypes: boolean = false; - - @computed get previewWidth() { - return () => NumCast(this.props.Document.schemaPreviewWidth); - } - @computed get previewHeight() { - return () => this.props.PanelHeight() - 2 * this.borderWidth; - } - @computed get tableWidth() { - return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); - } - @computed get borderWidth() { - return Number(COLLECTION_BORDER_WIDTH); - } - @computed get scale() { - return this.props.ScreenToLocalTransform().Scale; - } - @computed get columns() { - return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); - } - set columns(columns: SchemaHeaderField[]) { - this.props.Document._schemaHeaders = new List(columns); - } - - @computed get menuCoordinates() { - let searchx = 0; - let searchy = 0; - if (this.props.Document._searchDoc) { - const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0]; - if (el !== undefined) { - const rect = el.getBoundingClientRect(); - searchx = rect.x; - searchy = rect.y; - } - } - const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; - const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy; - return this.props.ScreenToLocalTransform().transformPoint(x, y); - } - - get documentKeys() { - const docs = this.childDocs; - const keys: { [key: string]: boolean } = {}; - // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. - // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be - // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. - // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu - // is displayed (unlikely) it won't show up until something else changes. - //TODO Types - untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))))); - - this.columns.forEach(key => (keys[key.heading] = true)); - return Array.from(Object.keys(keys)); - } - - @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing); - - @undoBatch - setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => { - this._openTypes = false; - if (columnTypes.get(columnField.heading)) return; - - const columns = this.columns; - const index = columns.indexOf(columnField); - if (index > -1) { - columnField.setType(NumCast(type)); - columns[index] = columnField; - this.columns = columns; - } - }); - - @undoBatch - setColumnColor = (columnField: SchemaHeaderField, color: string): void => { - const columns = this.columns; - const index = columns.indexOf(columnField); - if (index > -1) { - columnField.setColor(color); - columns[index] = columnField; - this.columns = columns; // need to set the columns to trigger rerender - } - }; - - @undoBatch - @action - setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => { - const columns = this.columns; - columns.forEach(col => col.setDesc(undefined)); - - const index = columns.findIndex(c => c.heading === columnField.heading); - const column = columns[index]; - column.setDesc(descending); - columns[index] = column; - this.columns = columns; - }; - - renderTypes = (col: any) => { - if (columnTypes.get(col.heading)) return null; - - const type = col.type; - - const anyType = ( -
this.setColumnType(col, ColumnType.Any)}> - - Any -
- ); - - const numType = ( -
this.setColumnType(col, ColumnType.Number)}> - - Number -
- ); - - const textType = ( -
this.setColumnType(col, ColumnType.String)}> - - Text -
- ); - - const boolType = ( -
this.setColumnType(col, ColumnType.Boolean)}> - - Checkbox -
- ); - - const listType = ( -
this.setColumnType(col, ColumnType.List)}> - - List -
- ); - - const docType = ( -
this.setColumnType(col, ColumnType.Doc)}> - - Document -
- ); - - const imageType = ( -
this.setColumnType(col, ColumnType.Image)}> - - Image -
- ); - - const dateType = ( -
this.setColumnType(col, ColumnType.Date)}> - - Date -
- ); - - const allColumnTypes = ( -
- {anyType} - {numType} - {textType} - {boolType} - {listType} - {docType} - {imageType} - {dateType} -
- ); - - const justColType = - type === ColumnType.Any - ? anyType - : type === ColumnType.Number - ? numType - : type === ColumnType.String - ? textType - : type === ColumnType.Boolean - ? boolType - : type === ColumnType.List - ? listType - : type === ColumnType.Doc - ? docType - : type === ColumnType.Date - ? dateType - : imageType; - - return ( -
(this._openTypes = !this._openTypes))}> -
- - -
- {this._openTypes ? allColumnTypes : justColType} -
- ); - }; - - renderSorting = (col: any) => { - const sort = col.desc; - return ( -
- -
-
this.setColumnSort(col, true)}> - - Sort descending -
-
this.setColumnSort(col, false)}> - - Sort ascending -
-
this.setColumnSort(col, undefined)}> - - Clear sorting -
-
-
- ); - }; - - renderColors = (col: any) => { - const selected = col.color; - - const pink = PastelSchemaPalette.get('pink2'); - const purple = PastelSchemaPalette.get('purple2'); - const blue = PastelSchemaPalette.get('bluegreen1'); - const yellow = PastelSchemaPalette.get('yellow4'); - const red = PastelSchemaPalette.get('red2'); - const gray = '#f1efeb'; - - return ( -
- -
-
this.setColumnColor(col, pink!)}>
-
this.setColumnColor(col, purple!)}>
-
this.setColumnColor(col, blue!)}>
-
this.setColumnColor(col, yellow!)}>
-
this.setColumnColor(col, red!)}>
-
this.setColumnColor(col, gray)}>
-
-
- ); - }; - - @undoBatch - @action - changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => { - const columns = this.columns; - if (columns === undefined) { - this.columns = new List([new SchemaHeaderField(newKey, 'f1efeb')]); - } else { - if (addNew) { - columns.push(new SchemaHeaderField(newKey, 'f1efeb')); - this.columns = columns; - } else { - const index = columns.map(c => c.heading).indexOf(oldKey); - if (index > -1) { - const column = columns[index]; - column.setHeading(newKey); - columns[index] = column; - this.columns = columns; - if (filter) { - Doc.setDocFilter(this.props.Document, newKey, filter, 'match'); - } else { - this.props.Document._docFilters = undefined; - } - } - } - } - }; - - @action - openHeader = (col: any, screenx: number, screeny: number) => { - this._col = col; - this._headerOpen = true; - this._pointerX = screenx; - this._pointerY = screeny; - }; - - @action - closeHeader = () => { - this._headerOpen = false; - }; - - @undoBatch - @action - deleteColumn = (key: string) => { - const columns = this.columns; - if (columns === undefined) { - this.columns = new List([]); - } else { - const index = columns.map(c => c.heading).indexOf(key); - if (index > -1) { - columns.splice(index, 1); - this.columns = columns; - } - } - this.closeHeader(); - }; - - getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth); - }; - - @action - onHeaderClick = (e: React.PointerEvent) => { - e.stopPropagation(); - }; - - @action - onWheel(e: React.WheelEvent) { - const scale = this.props.ScreenToLocalTransform().Scale; - this.props.isContentActive(true) && e.stopPropagation(); - } - - @computed get renderMenuContent() { - TraceMobx(); - return ( -
- {this.renderTypes(this._col)} - {this.renderColors(this._col)} -
- -
-
- ); - } - - private createTarget = (ele: HTMLDivElement) => { - this._previewCont = ele; - super.CreateDropTarget(ele); - }; - - isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; - - @action setFocused = (doc: Doc) => (this._focusedTable = doc); - - @action setPreviewDoc = (doc: Opt) => { - SelectionManager.SelectSchemaViewDoc(doc); - this._previewDoc = doc; - }; - - //toggles preview side-panel of schema - @action - toggleExpander = () => { - this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0; - }; - - onDividerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander); - }; - @action - onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { - const nativeWidth = this._previewCont!.getBoundingClientRect(); - const minWidth = 40; - const maxWidth = 1000; - const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; - const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth; - this.props.Document.schemaPreviewWidth = width; - return false; - }; - - onPointerDown = (e: React.PointerEvent): void => { - if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (this.props.isSelected(true)) e.stopPropagation(); - else this.props.select(false); - } - }; - - @computed - get previewDocument(): Doc | undefined { - return this._previewDoc; - } - - @computed - get dividerDragger() { - return this.previewWidth() === 0 ? null : ( -
-
-
- ); - } - - @computed - get previewPanel() { - return ( -
- {!this.previewDocument ? null : ( - - )} -
- ); - } - - @computed - get schemaTable() { - return ( - - ); - } - - @computed - public get schemaToolbar() { - return ( -
-
-
- - Show Preview -
-
-
- ); - } - - onSpecificMenu = (e: React.MouseEvent) => { - if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) { - const cm = ContextMenu.Instance; - const options = cm.findByDescription('Options...'); - const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; - optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' }); - !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); - cm.displayMenu(e.clientX, e.clientY); - (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this. - e.stopPropagation(); - } - }; - - @action - onTableClick = (e: React.MouseEvent): void => { - if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) { - this.setPreviewDoc(undefined); - } else { - e.stopPropagation(); - } - this.setFocused(this.props.Document); - this.closeHeader(); - }; - - onResizedChange = (newResized: Resize[], event: any) => { - const columns = this.columns; - newResized.forEach(resized => { - const index = columns.findIndex(c => c.heading === resized.id); - const column = columns[index]; - column.setWidth(resized.value); - columns[index] = column; - }); - this.columns = columns; - }; - - @action - setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns); - - @undoBatch - reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => { - const columns = [...columnsValues]; - const oldIndex = columns.indexOf(toMove); - const relIndex = columns.indexOf(relativeTo); - const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex; - - if (oldIndex === newIndex) return; - - columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]); - this.columns = columns; - }; - - onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation(); - - render() { - TraceMobx(); - if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0); - const menuContent = this.renderMenuContent; - const menu = ( -
this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}> - { - const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); - this._menuWidth = dim[0]; - this._menuHeight = dim[1]; - })}> - {({ measureRef }) =>
{menuContent}
} -
-
- ); - return ( -
-
this.props.isContentActive(true) && e.stopPropagation()} - onDrop={e => this.onExternalDrop(e, {})} - ref={this.createTarget}> - {this.schemaTable} -
- {this.dividerDragger} - {!this.previewWidth() ? null : this.previewPanel} - {this._headerOpen && this.props.isContentActive() ? menu : null} -
- ); - TraceMobx(); - return
HELLO
; - } -} +// import React = require('react'); +// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +// import { action, computed, observable, untracked } from 'mobx'; +// import { observer } from 'mobx-react'; +// import Measure from 'react-measure'; +// // import { Resize } from 'react-table'; +// import { Doc, Opt } from '../../../../fields/Doc'; +// import { List } from '../../../../fields/List'; +// import { listSpec } from '../../../../fields/Schema'; +// import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +// import { Cast, NumCast } from '../../../../fields/Types'; +// import { TraceMobx } from '../../../../fields/util'; +// import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +// import { DocUtils } from '../../../documents/Documents'; +// import { SelectionManager } from '../../../util/SelectionManager'; +// import { SnappingManager } from '../../../util/SnappingManager'; +// import { Transform } from '../../../util/Transform'; +// import { undoBatch } from '../../../util/UndoManager'; +// import { ContextMenu } from '../../ContextMenu'; +// import { ContextMenuProps } from '../../ContextMenuItem'; +// import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; +// import { DocumentView } from '../../nodes/DocumentView'; +// import { DefaultStyleProvider } from '../../StyleProvider'; +// import { CollectionSubView } from '../CollectionSubView'; +// import './CollectionSchemaView.scss'; +// // import { SchemaTable } from './SchemaTable'; +// // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 + +// export enum ColumnType { +// Any, +// Number, +// String, +// Boolean, +// Doc, +// Image, +// List, +// Date, +// } +// // this map should be used for keys that should have a const type of value +// const columnTypes: Map = new Map([ +// ['title', ColumnType.String], +// ['x', ColumnType.Number], +// ['y', ColumnType.Number], +// ['_width', ColumnType.Number], +// ['_height', ColumnType.Number], +// ['_nativeWidth', ColumnType.Number], +// ['_nativeHeight', ColumnType.Number], +// ['isPrototype', ColumnType.Boolean], +// ['_curPage', ColumnType.Number], +// ['_currentTimecode', ColumnType.Number], +// ['zIndex', ColumnType.Number], +// ]); + +// @observer +// export class CollectionSchemaView extends CollectionSubView() { +// private _previewCont?: HTMLDivElement; + +// @observable _previewDoc: Doc | undefined = undefined; +// @observable _focusedTable: Doc = this.props.Document; +// @observable _col: any = ''; +// @observable _menuWidth = 0; +// @observable _headerOpen = false; +// @observable _headerIsEditing = false; +// @observable _menuHeight = 0; +// @observable _pointerX = 0; +// @observable _pointerY = 0; +// @observable _openTypes: boolean = false; + +// @computed get previewWidth() { +// return () => NumCast(this.props.Document.schemaPreviewWidth); +// } +// @computed get previewHeight() { +// return () => this.props.PanelHeight() - 2 * this.borderWidth; +// } +// @computed get tableWidth() { +// return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); +// } +// @computed get borderWidth() { +// return Number(COLLECTION_BORDER_WIDTH); +// } +// @computed get scale() { +// return this.props.ScreenToLocalTransform().Scale; +// } +// @computed get columns() { +// return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); +// } +// set columns(columns: SchemaHeaderField[]) { +// this.props.Document._schemaHeaders = new List(columns); +// } + +// @computed get menuCoordinates() { +// let searchx = 0; +// let searchy = 0; +// if (this.props.Document._searchDoc) { +// const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0]; +// if (el !== undefined) { +// const rect = el.getBoundingClientRect(); +// searchx = rect.x; +// searchy = rect.y; +// } +// } +// const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; +// const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy; +// return this.props.ScreenToLocalTransform().transformPoint(x, y); +// } + +// get documentKeys() { +// const docs = this.childDocs; +// const keys: { [key: string]: boolean } = {}; +// // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. +// // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be +// // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. +// // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu +// // is displayed (unlikely) it won't show up until something else changes. +// //TODO Types +// untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))))); + +// this.columns.forEach(key => (keys[key.heading] = true)); +// return Array.from(Object.keys(keys)); +// } + +// @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing); + +// @undoBatch +// setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => { +// this._openTypes = false; +// if (columnTypes.get(columnField.heading)) return; + +// const columns = this.columns; +// const index = columns.indexOf(columnField); +// if (index > -1) { +// columnField.setType(NumCast(type)); +// columns[index] = columnField; +// this.columns = columns; +// } +// }); + +// @undoBatch +// setColumnColor = (columnField: SchemaHeaderField, color: string): void => { +// const columns = this.columns; +// const index = columns.indexOf(columnField); +// if (index > -1) { +// columnField.setColor(color); +// columns[index] = columnField; +// this.columns = columns; // need to set the columns to trigger rerender +// } +// }; + +// @undoBatch +// @action +// setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => { +// const columns = this.columns; +// columns.forEach(col => col.setDesc(undefined)); + +// const index = columns.findIndex(c => c.heading === columnField.heading); +// const column = columns[index]; +// column.setDesc(descending); +// columns[index] = column; +// this.columns = columns; +// }; + +// renderTypes = (col: any) => { +// if (columnTypes.get(col.heading)) return null; + +// const type = col.type; + +// const anyType = ( +//
this.setColumnType(col, ColumnType.Any)}> +// +// Any +//
+// ); + +// const numType = ( +//
this.setColumnType(col, ColumnType.Number)}> +// +// Number +//
+// ); + +// const textType = ( +//
this.setColumnType(col, ColumnType.String)}> +// +// Text +//
+// ); + +// const boolType = ( +//
this.setColumnType(col, ColumnType.Boolean)}> +// +// Checkbox +//
+// ); + +// const listType = ( +//
this.setColumnType(col, ColumnType.List)}> +// +// List +//
+// ); + +// const docType = ( +//
this.setColumnType(col, ColumnType.Doc)}> +// +// Document +//
+// ); + +// const imageType = ( +//
this.setColumnType(col, ColumnType.Image)}> +// +// Image +//
+// ); + +// const dateType = ( +//
this.setColumnType(col, ColumnType.Date)}> +// +// Date +//
+// ); + +// const allColumnTypes = ( +//
+// {anyType} +// {numType} +// {textType} +// {boolType} +// {listType} +// {docType} +// {imageType} +// {dateType} +//
+// ); + +// const justColType = +// type === ColumnType.Any +// ? anyType +// : type === ColumnType.Number +// ? numType +// : type === ColumnType.String +// ? textType +// : type === ColumnType.Boolean +// ? boolType +// : type === ColumnType.List +// ? listType +// : type === ColumnType.Doc +// ? docType +// : type === ColumnType.Date +// ? dateType +// : imageType; + +// return ( +//
(this._openTypes = !this._openTypes))}> +//
+// +// +//
+// {this._openTypes ? allColumnTypes : justColType} +//
+// ); +// }; + +// renderSorting = (col: any) => { +// const sort = col.desc; +// return ( +//
+// +//
+//
this.setColumnSort(col, true)}> +// +// Sort descending +//
+//
this.setColumnSort(col, false)}> +// +// Sort ascending +//
+//
this.setColumnSort(col, undefined)}> +// +// Clear sorting +//
+//
+//
+// ); +// }; + +// renderColors = (col: any) => { +// const selected = col.color; + +// const pink = PastelSchemaPalette.get('pink2'); +// const purple = PastelSchemaPalette.get('purple2'); +// const blue = PastelSchemaPalette.get('bluegreen1'); +// const yellow = PastelSchemaPalette.get('yellow4'); +// const red = PastelSchemaPalette.get('red2'); +// const gray = '#f1efeb'; + +// return ( +//
+// +//
+//
this.setColumnColor(col, pink!)}>
+//
this.setColumnColor(col, purple!)}>
+//
this.setColumnColor(col, blue!)}>
+//
this.setColumnColor(col, yellow!)}>
+//
this.setColumnColor(col, red!)}>
+//
this.setColumnColor(col, gray)}>
+//
+//
+// ); +// }; + +// @undoBatch +// @action +// changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => { +// const columns = this.columns; +// if (columns === undefined) { +// this.columns = new List([new SchemaHeaderField(newKey, 'f1efeb')]); +// } else { +// if (addNew) { +// columns.push(new SchemaHeaderField(newKey, 'f1efeb')); +// this.columns = columns; +// } else { +// const index = columns.map(c => c.heading).indexOf(oldKey); +// if (index > -1) { +// const column = columns[index]; +// column.setHeading(newKey); +// columns[index] = column; +// this.columns = columns; +// if (filter) { +// Doc.setDocFilter(this.props.Document, newKey, filter, 'match'); +// } else { +// this.props.Document._docFilters = undefined; +// } +// } +// } +// } +// }; + +// @action +// openHeader = (col: any, screenx: number, screeny: number) => { +// this._col = col; +// this._headerOpen = true; +// this._pointerX = screenx; +// this._pointerY = screeny; +// }; + +// @action +// closeHeader = () => { +// this._headerOpen = false; +// }; + +// @undoBatch +// @action +// deleteColumn = (key: string) => { +// const columns = this.columns; +// if (columns === undefined) { +// this.columns = new List([]); +// } else { +// const index = columns.map(c => c.heading).indexOf(key); +// if (index > -1) { +// columns.splice(index, 1); +// this.columns = columns; +// } +// } +// this.closeHeader(); +// }; + +// getPreviewTransform = (): Transform => { +// return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth); +// }; + +// @action +// onHeaderClick = (e: React.PointerEvent) => { +// e.stopPropagation(); +// }; + +// @action +// onWheel(e: React.WheelEvent) { +// const scale = this.props.ScreenToLocalTransform().Scale; +// this.props.isContentActive(true) && e.stopPropagation(); +// } + +// @computed get renderMenuContent() { +// TraceMobx(); +// return ( +//
+// {this.renderTypes(this._col)} +// {this.renderColors(this._col)} +//
+// +//
+//
+// ); +// } + +// private createTarget = (ele: HTMLDivElement) => { +// this._previewCont = ele; +// super.CreateDropTarget(ele); +// }; + +// isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; + +// @action setFocused = (doc: Doc) => (this._focusedTable = doc); + +// @action setPreviewDoc = (doc: Opt) => { +// SelectionManager.SelectSchemaViewDoc(doc); +// this._previewDoc = doc; +// }; + +// //toggles preview side-panel of schema +// @action +// toggleExpander = () => { +// this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0; +// }; + +// onDividerDown = (e: React.PointerEvent) => { +// setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander); +// }; +// @action +// onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { +// const nativeWidth = this._previewCont!.getBoundingClientRect(); +// const minWidth = 40; +// const maxWidth = 1000; +// const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; +// const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth; +// this.props.Document.schemaPreviewWidth = width; +// return false; +// }; + +// onPointerDown = (e: React.PointerEvent): void => { +// if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { +// if (this.props.isSelected(true)) e.stopPropagation(); +// else this.props.select(false); +// } +// }; + +// @computed +// get previewDocument(): Doc | undefined { +// return this._previewDoc; +// } + +// @computed +// get dividerDragger() { +// return this.previewWidth() === 0 ? null : ( +//
+//
+//
+// ); +// } + +// @computed +// get previewPanel() { +// return ( +//
+// {!this.previewDocument ? null : ( +// +// )} +//
+// ); +// } + +// @computed +// get schemaTable() { +// return ( +// +// ); +// } + +// @computed +// public get schemaToolbar() { +// return ( +//
+//
+//
+// +// Show Preview +//
+//
+//
+// ); +// } + +// onSpecificMenu = (e: React.MouseEvent) => { +// if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) { +// const cm = ContextMenu.Instance; +// const options = cm.findByDescription('Options...'); +// const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; +// optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' }); +// !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); +// cm.displayMenu(e.clientX, e.clientY); +// (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this. +// e.stopPropagation(); +// } +// }; + +// @action +// onTableClick = (e: React.MouseEvent): void => { +// if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) { +// this.setPreviewDoc(undefined); +// } else { +// e.stopPropagation(); +// } +// this.setFocused(this.props.Document); +// this.closeHeader(); +// }; + +// onResizedChange = (newResized: Resize[], event: any) => { +// const columns = this.columns; +// newResized.forEach(resized => { +// const index = columns.findIndex(c => c.heading === resized.id); +// const column = columns[index]; +// column.setWidth(resized.value); +// columns[index] = column; +// }); +// this.columns = columns; +// }; + +// @action +// setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns); + +// @undoBatch +// reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => { +// const columns = [...columnsValues]; +// const oldIndex = columns.indexOf(toMove); +// const relIndex = columns.indexOf(relativeTo); +// const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex; + +// if (oldIndex === newIndex) return; + +// columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]); +// this.columns = columns; +// }; + +// onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation(); + +// render() { +// TraceMobx(); +// if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0); +// const menuContent = this.renderMenuContent; +// const menu = ( +//
this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}> +// { +// const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); +// this._menuWidth = dim[0]; +// this._menuHeight = dim[1]; +// })}> +// {({ measureRef }) =>
{menuContent}
} +//
+//
+// ); +// return ( +//
+//
this.props.isContentActive(true) && e.stopPropagation()} +// onDrop={e => this.onExternalDrop(e, {})} +// ref={this.createTarget}> +// {this.schemaTable} +//
+// {this.dividerDragger} +// {!this.previewWidth() ? null : this.previewPanel} +// {this._headerOpen && this.props.isContentActive() ? menu : null} +//
+// ); +// TraceMobx(); +// return
HELLO
; +// } +// } diff --git a/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx index 1b4fcf0a4..bc33f3be5 100644 --- a/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx +++ b/src/client/views/collections/old_collectionSchema/OldSchemaTable.tsx @@ -1,694 +1,694 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table'; -import { DateField } from '../../../../fields/DateField'; -import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; -import { Id } from '../../../../fields/FieldSymbols'; -import { List } from '../../../../fields/List'; -import { listSpec } from '../../../../fields/Schema'; -import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { ComputedField } from '../../../../fields/ScriptField'; -import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; -import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl } from '../../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils'; -import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { CompileScript, Transformer, ts } from '../../../util/Scripting'; -import { Transform } from '../../../util/Transform'; -import { undoBatch } from '../../../util/UndoManager'; -import '../../../views/DocumentDecorations.scss'; -import { ContextMenu } from '../../ContextMenu'; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; -import { PinProps } from '../../nodes/trails'; -import { DefaultStyleProvider } from '../../StyleProvider'; -import { CollectionView } from '../CollectionView'; -import { - CellProps, - CollectionSchemaButtons, - CollectionSchemaCell, - CollectionSchemaCheckboxCell, - CollectionSchemaDateCell, - CollectionSchemaDocCell, - CollectionSchemaImageCell, - CollectionSchemaListCell, - CollectionSchemaNumberCell, - CollectionSchemaStringCell, -} from './CollectionSchemaCells'; -import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders'; -import { MovableColumn } from './OldCollectionSchemaMovableColumn'; -import { MovableRow } from './CollectionSchemaMovableRow'; -import './CollectionSchemaView.scss'; - -enum ColumnType { - Any, - Number, - String, - Boolean, - Doc, - Image, - List, - Date, -} - -// this map should be used for keys that should have a const type of value -const columnTypes: Map = new Map([ - ['title', ColumnType.String], - ['x', ColumnType.Number], - ['y', ColumnType.Number], - ['_width', ColumnType.Number], - ['_height', ColumnType.Number], - ['_nativeWidth', ColumnType.Number], - ['_nativeHeight', ColumnType.Number], - ['isPrototype', ColumnType.Boolean], - ['_curPage', ColumnType.Number], - ['_currentTimecode', ColumnType.Number], - ['zIndex', ColumnType.Number], -]); - -export interface SchemaTableProps { - Document: Doc; // child doc - dataDoc?: Doc; - PanelHeight: () => number; - PanelWidth: () => number; - childDocs?: Doc[]; - CollectionView: Opt; - ContainingCollectionView: Opt; - ContainingCollectionDoc: Opt; - fieldKey: string; - renderDepth: number; - deleteDocument?: (document: Doc | Doc[]) => boolean; - addDocument?: (document: Doc | Doc[]) => boolean; - moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - ScreenToLocalTransform: () => Transform; - active: (outsideReaction: boolean | undefined) => boolean | undefined; - onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; - addDocTab: (document: Doc, where: OpenWhere) => boolean; - pinToPres: (document: Doc, pinProps: PinProps) => void; - isSelected: (outsideReaction?: boolean) => boolean; - isFocused: (document: Doc, outsideReaction: boolean) => boolean; - setFocused: (document: Doc) => void; - setPreviewDoc: (document: Opt) => void; - columns: SchemaHeaderField[]; - documentKeys: any[]; - headerIsEditing: boolean; - openHeader: (column: any, screenx: number, screeny: number) => void; - onClick: (e: React.MouseEvent) => void; - onPointerDown: (e: React.PointerEvent) => void; - onResizedChange: (newResized: Resize[], event: any) => void; - setColumns: (columns: SchemaHeaderField[]) => void; - reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => void; - changeColumns: (oldKey: string, newKey: string, addNew: boolean) => void; - setHeaderIsEditing: (isEditing: boolean) => void; - changeColumnSort: (columnField: SchemaHeaderField, descending: boolean | undefined) => void; -} - -@observer -export class SchemaTable extends React.Component { - @observable _cellIsEditing: boolean = false; - @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 }; - @observable _openCollections: Set = new Set(); - - @observable _showDoc: Doc | undefined; - @observable _showDataDoc: any = ''; - @observable _showDocPos: number[] = []; - - @observable _showTitleDropdown: boolean = false; - - @computed get previewWidth() { - return () => NumCast(this.props.Document.schemaPreviewWidth); - } - @computed get previewHeight() { - return () => this.props.PanelHeight() - 2 * this.borderWidth; - } - @computed get tableWidth() { - return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); - } - - @computed get childDocs() { - if (this.props.childDocs) return this.props.childDocs; - - const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - return DocListCast(doc[this.props.fieldKey]); - } - set childDocs(docs: Doc[]) { - const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - doc[this.props.fieldKey] = new List(docs); - } - - @computed get textWrappedRows() { - return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); - } - set textWrappedRows(textWrappedRows: string[]) { - this.props.Document.textwrappedSchemaRows = new List(textWrappedRows); - } - - @computed get resized(): { id: string; value: number }[] { - return this.props.columns.reduce((resized, shf) => { - shf.width > -1 && resized.push({ id: shf.heading, value: shf.width }); - return resized; - }, [] as { id: string; value: number }[]); - } - @computed get sorted(): SortingRule[] { - return this.props.columns.reduce((sorted, shf) => { - shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc }); - return sorted; - }, [] as SortingRule[]); - } - - @action - changeSorting = (col: any) => { - this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true); - }; - - @action - changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown); - - @computed get borderWidth() { - return Number(COLLECTION_BORDER_WIDTH); - } - @computed get tableColumns(): Column[] { - const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); - const columns: Column[] = []; - const tableIsFocused = this.props.isFocused(this.props.Document, false); - const focusedRow = this._focusedCell.row; - const focusedCol = this._focusedCell.col; - const isEditable = !this.props.headerIsEditing; - - columns.push({ - expander: true, - Header: '', - width: 58, - Expander: rowInfo => { - return rowInfo.original.type !== DocumentType.COL ? null : ( -
this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}> - -
- ); - }, - }); - columns.push( - ...this.props.columns.map(col => { - const icon: IconProp = - this.getColumnType(col) === ColumnType.Number - ? 'hashtag' - : this.getColumnType(col) === ColumnType.String - ? 'font' - : this.getColumnType(col) === ColumnType.Boolean - ? 'check-square' - : this.getColumnType(col) === ColumnType.Doc - ? 'file' - : this.getColumnType(col) === ColumnType.Image - ? 'image' - : this.getColumnType(col) === ColumnType.List - ? 'list-ul' - : this.getColumnType(col) === ColumnType.Date - ? 'calendar' - : 'align-justify'; - - const keysDropdown = ( - c.heading)} - canAddNew={true} - addNew={false} - onSelect={this.props.changeColumns} - setIsEditing={this.props.setHeaderIsEditing} - docs={this.props.childDocs} - Document={this.props.Document} - dataDoc={this.props.dataDoc} - fieldKey={this.props.fieldKey} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - ContainingCollectionView={this.props.ContainingCollectionView} - active={this.props.active} - openHeader={this.props.openHeader} - icon={icon} - col={col} - // try commenting this out - width={'100%'} - /> - ); - - const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up'; - const header = ( -
- {keysDropdown} -
this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}> - -
- {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined :
+ new
} */} -
- ); - - return { - Header: , - accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0), - id: col.heading, - Cell: (rowProps: CellInfo) => { - const rowIndex = rowProps.index; - const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); - const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; - - const props: CellProps = { - row: rowIndex, - col: columnIndex, - rowProps: rowProps, - isFocused: isFocused, - changeFocusedCellByIndex: this.changeFocusedCellByIndex, - CollectionView: this.props.CollectionView, - ContainingCollection: this.props.ContainingCollectionView, - Document: this.props.Document, - fieldKey: this.props.fieldKey, - renderDepth: this.props.renderDepth, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - moveDocument: this.props.moveDocument, - setIsEditing: this.setCellIsEditing, - isEditable: isEditable, - setPreviewDoc: this.props.setPreviewDoc, - setComputed: this.setComputed, - getField: this.getField, - showDoc: this.showDoc, - }; - - switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { - case ColumnType.Number: - return ; - case ColumnType.String: - return ; - case ColumnType.Boolean: - return ; - case ColumnType.Doc: - return ; - case ColumnType.Image: - return ; - case ColumnType.List: - return ; - case ColumnType.Date: - return ; - default: - return ; - } - }, - minWidth: 200, - }; - }) - ); - columns.push({ - Header: , - accessor: (doc: Doc) => 0, - id: 'add', - Cell: (rowProps: CellInfo) => { - const rowIndex = rowProps.index; - const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); - const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; - return ( - - ); - }, - width: 28, - resizable: false, - }); - return columns; - } - - constructor(props: SchemaTableProps) { - super(props); - if (this.props.Document._schemaHeaders === undefined) { - this.props.Document._schemaHeaders = new List([ - new SchemaHeaderField('title', '#f1efeb'), - new SchemaHeaderField('author', '#f1efeb'), - new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date), - new SchemaHeaderField('text', '#f1efeb', ColumnType.String), - new SchemaHeaderField('type', '#f1efeb'), - new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc), - ]); - } - } - - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - } - - tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => { - const tableDoc = this.props.Document[DataSym]; - const effectiveAcl = GetEffectiveAcl(tableDoc); - - if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) { - doc.context = this.props.Document; - tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())); - return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); - } - return false; - }; - - private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { - return !rowInfo - ? {} - : { - ScreenToLocalTransform: this.props.ScreenToLocalTransform, - addDoc: this.tableAddDoc, - removeDoc: this.props.deleteDocument, - rowInfo, - rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), - textWrapRow: this.toggleTextWrapRow, - rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, - dropAction: StrCast(this.props.Document.childDropAction), - addDocTab: this.props.addDocTab, - }; - }; - - private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => { - if (!rowInfo || column) return {}; - - const row = rowInfo.index; - //@ts-ignore - const col = this.columns.map(c => c.heading).indexOf(column!.id); - const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); - // TODO: editing border doesn't work :( - return { - style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' }, - }; - }; - - @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing); - - @action - onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) { - // && this.props.isSelected(true)) { - const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : ''; - this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); - - if (direction) { - const pdoc = FieldValue(this.childDocs[this._focusedCell.row]); - pdoc && this.props.setPreviewDoc(pdoc); - e.stopPropagation(); - } - } else if (e.keyCode === 27) { - this.props.setPreviewDoc(undefined); - e.stopPropagation(); // stopPropagation for left/right arrows - } - }; - - changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => { - switch (direction) { - case 'tab': - return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 }; - case 'right': - return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 }; - case 'left': - return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 }; - case 'up': - return { row: curRow === 0 ? curRow : curRow - 1, col: curCol }; - case 'down': - return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol }; - } - return this._focusedCell; - }; - - @action - changeFocusedCellByIndex = (row: number, col: number): void => { - if (this._focusedCell.row !== row || this._focusedCell.col !== col) { - this._focusedCell = { row: row, col: col }; - } - this.props.setFocused(this.props.Document); - }; - - @undoBatch - createRow = action(() => { - this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 })); - this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col }; - }); - - @undoBatch - @action - createColumn = () => { - const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`; - for (let index = 0; index < 100; index++) { - if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) { - this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb')); - break; - } - } - }; - - @action - getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => { - if (doc && field && column.type === ColumnType.Any) { - const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)]; - if (val instanceof ImageField) return ColumnType.Image; - if (val instanceof Doc) return ColumnType.Doc; - if (val instanceof DateField) return ColumnType.Date; - if (val instanceof List) return ColumnType.List; - } - if (column.type && column.type !== 0) { - return column.type; - } - if (columnTypes.get(column.heading)) { - return (column.type = columnTypes.get(column.heading)!); - } - return (column.type = ColumnType.Any); - }; - - @undoBatch - @action - toggleTextwrap = async () => { - const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); - if (textwrappedRows.length) { - this.props.Document.textwrappedSchemaRows = new List([]); - } else { - const docs = DocListCast(this.props.Document[this.props.fieldKey]); - const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); - this.props.Document.textwrappedSchemaRows = new List(allRows); - } - }; - - @action - toggleTextWrapRow = (doc: Doc): void => { - const textWrapped = this.textWrappedRows; - const index = textWrapped.findIndex(id => doc[Id] === id); - - index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]); - - this.textWrappedRows = textWrapped; - }; - - @computed - get reactTable() { - const children = this.childDocs; - const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false); - const expanded: { [name: string]: any } = {}; - Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true)); - const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :(((( - - return ( - - row.original.type !== DocumentType.COL ? null : ( -
- -
- ) - } - /> - ); - } - - onContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' }); - }; - - getField = (row: number, col?: number) => { - const docs = this.childDocs; - - row = row % docs.length; - while (row < 0) row += docs.length; - const columns = this.props.columns; - const doc = docs[row]; - if (col === undefined) { - return doc; - } - if (col >= 0 && col < columns.length) { - const column = this.props.columns[col].heading; - return doc[column]; - } - return undefined; - }; - - createTransformer = (row: number, col: number): Transformer => { - const self = this; - const captures: { [name: string]: Field } = {}; - - const transformer: ts.TransformerFactory = context => { - return root => { - function visit(node: ts.Node) { - node = ts.visitEachChild(node, visit, context); - if (ts.isIdentifier(node)) { - const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; - const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; - if (isntPropAccess && isntPropAssign) { - if (node.text === '$r') { - return ts.createNumericLiteral(row.toString()); - } else if (node.text === '$c') { - return ts.createNumericLiteral(col.toString()); - } else if (node.text === '$') { - if (ts.isCallExpression(node.parent)) { - // captures.doc = self.props.Document; - // captures.key = self.props.fieldKey; - } - } - } - } - - return node; - } - return ts.visitNode(root, visit); - }; - }; - - // const getVars = () => { - // return { capturedVariables: captures }; - // }; - - return { transformer /*getVars*/ }; - }; - - setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => { - script = `const $ = (row:number, col?:number) => { - const rval = (doc as any)[key][row + ${row}]; - return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading]; - } - return ${script}`; - const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) }); - if (compiled.compiled) { - doc[field] = new ComputedField(compiled); - return true; - } - return false; - }; - - @action - showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => { - this._showDoc = doc; - if (dataDoc && screenX && screenY) { - this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY); - } - }; - - onOpenClick = () => { - this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight); - }; - - getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth); - }; - - render() { - const preview = ''; - return ( -
this.props.active(true) && e.stopPropagation()} - onDrop={e => this.props.onDrop(e, {})} - onContextMenu={this.onContextMenu}> - {this.reactTable} - {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : ( -
- + new -
- )} - {!this._showDoc ? null : ( -
- 150} - PanelHeight={() => 150} - ScreenToLocalTransform={this.getPreviewTransform} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} - moveDocument={this.props.moveDocument} - whenChildContentsActiveChanged={emptyFunction} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - bringToFront={returnFalse}> -
- )} -
- ); - } -} +// import { IconProp } from '@fortawesome/fontawesome-svg-core'; +// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +// import { action, computed, observable } from 'mobx'; +// import { observer } from 'mobx-react'; +// import * as React from 'react'; +// import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table'; +// import { DateField } from '../../../../fields/DateField'; +// import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +// import { Id } from '../../../../fields/FieldSymbols'; +// import { List } from '../../../../fields/List'; +// import { listSpec } from '../../../../fields/Schema'; +// import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +// import { ComputedField } from '../../../../fields/ScriptField'; +// import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; +// import { ImageField } from '../../../../fields/URLField'; +// import { GetEffectiveAcl } from '../../../../fields/util'; +// import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils'; +// import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; +// import { DocumentType } from '../../../documents/DocumentTypes'; +// import { CompileScript, Transformer, ts } from '../../../util/Scripting'; +// import { Transform } from '../../../util/Transform'; +// import { undoBatch } from '../../../util/UndoManager'; +// import '../../../views/DocumentDecorations.scss'; +// import { ContextMenu } from '../../ContextMenu'; +// import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; +// import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; +// import { PinProps } from '../../nodes/trails'; +// import { DefaultStyleProvider } from '../../StyleProvider'; +// import { CollectionView } from '../CollectionView'; +// import { +// CellProps, +// CollectionSchemaButtons, +// CollectionSchemaCell, +// CollectionSchemaCheckboxCell, +// CollectionSchemaDateCell, +// CollectionSchemaDocCell, +// CollectionSchemaImageCell, +// CollectionSchemaListCell, +// CollectionSchemaNumberCell, +// CollectionSchemaStringCell, +// } from './CollectionSchemaCells'; +// import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders'; +// import { MovableColumn } from './OldCollectionSchemaMovableColumn'; +// import { MovableRow } from './CollectionSchemaMovableRow'; +// import './CollectionSchemaView.scss'; + +// enum ColumnType { +// Any, +// Number, +// String, +// Boolean, +// Doc, +// Image, +// List, +// Date, +// } + +// // this map should be used for keys that should have a const type of value +// const columnTypes: Map = new Map([ +// ['title', ColumnType.String], +// ['x', ColumnType.Number], +// ['y', ColumnType.Number], +// ['_width', ColumnType.Number], +// ['_height', ColumnType.Number], +// ['_nativeWidth', ColumnType.Number], +// ['_nativeHeight', ColumnType.Number], +// ['isPrototype', ColumnType.Boolean], +// ['_curPage', ColumnType.Number], +// ['_currentTimecode', ColumnType.Number], +// ['zIndex', ColumnType.Number], +// ]); + +// export interface SchemaTableProps { +// Document: Doc; // child doc +// dataDoc?: Doc; +// PanelHeight: () => number; +// PanelWidth: () => number; +// childDocs?: Doc[]; +// CollectionView: Opt; +// ContainingCollectionView: Opt; +// ContainingCollectionDoc: Opt; +// fieldKey: string; +// renderDepth: number; +// deleteDocument?: (document: Doc | Doc[]) => boolean; +// addDocument?: (document: Doc | Doc[]) => boolean; +// moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; +// ScreenToLocalTransform: () => Transform; +// active: (outsideReaction: boolean | undefined) => boolean | undefined; +// onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; +// addDocTab: (document: Doc, where: OpenWhere) => boolean; +// pinToPres: (document: Doc, pinProps: PinProps) => void; +// isSelected: (outsideReaction?: boolean) => boolean; +// isFocused: (document: Doc, outsideReaction: boolean) => boolean; +// setFocused: (document: Doc) => void; +// setPreviewDoc: (document: Opt) => void; +// columns: SchemaHeaderField[]; +// documentKeys: any[]; +// headerIsEditing: boolean; +// openHeader: (column: any, screenx: number, screeny: number) => void; +// onClick: (e: React.MouseEvent) => void; +// onPointerDown: (e: React.PointerEvent) => void; +// onResizedChange: (newResized: Resize[], event: any) => void; +// setColumns: (columns: SchemaHeaderField[]) => void; +// reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => void; +// changeColumns: (oldKey: string, newKey: string, addNew: boolean) => void; +// setHeaderIsEditing: (isEditing: boolean) => void; +// changeColumnSort: (columnField: SchemaHeaderField, descending: boolean | undefined) => void; +// } + +// @observer +// export class SchemaTable extends React.Component { +// @observable _cellIsEditing: boolean = false; +// @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 }; +// @observable _openCollections: Set = new Set(); + +// @observable _showDoc: Doc | undefined; +// @observable _showDataDoc: any = ''; +// @observable _showDocPos: number[] = []; + +// @observable _showTitleDropdown: boolean = false; + +// @computed get previewWidth() { +// return () => NumCast(this.props.Document.schemaPreviewWidth); +// } +// @computed get previewHeight() { +// return () => this.props.PanelHeight() - 2 * this.borderWidth; +// } +// @computed get tableWidth() { +// return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); +// } + +// @computed get childDocs() { +// if (this.props.childDocs) return this.props.childDocs; + +// const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; +// return DocListCast(doc[this.props.fieldKey]); +// } +// set childDocs(docs: Doc[]) { +// const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; +// doc[this.props.fieldKey] = new List(docs); +// } + +// @computed get textWrappedRows() { +// return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); +// } +// set textWrappedRows(textWrappedRows: string[]) { +// this.props.Document.textwrappedSchemaRows = new List(textWrappedRows); +// } + +// @computed get resized(): { id: string; value: number }[] { +// return this.props.columns.reduce((resized, shf) => { +// shf.width > -1 && resized.push({ id: shf.heading, value: shf.width }); +// return resized; +// }, [] as { id: string; value: number }[]); +// } +// @computed get sorted(): SortingRule[] { +// return this.props.columns.reduce((sorted, shf) => { +// shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc }); +// return sorted; +// }, [] as SortingRule[]); +// } + +// @action +// changeSorting = (col: any) => { +// this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true); +// }; + +// @action +// changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown); + +// @computed get borderWidth() { +// return Number(COLLECTION_BORDER_WIDTH); +// } +// @computed get tableColumns(): Column[] { +// const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); +// const columns: Column[] = []; +// const tableIsFocused = this.props.isFocused(this.props.Document, false); +// const focusedRow = this._focusedCell.row; +// const focusedCol = this._focusedCell.col; +// const isEditable = !this.props.headerIsEditing; + +// columns.push({ +// expander: true, +// Header: '', +// width: 58, +// Expander: rowInfo => { +// return rowInfo.original.type !== DocumentType.COL ? null : ( +//
this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}> +// +//
+// ); +// }, +// }); +// columns.push( +// ...this.props.columns.map(col => { +// const icon: IconProp = +// this.getColumnType(col) === ColumnType.Number +// ? 'hashtag' +// : this.getColumnType(col) === ColumnType.String +// ? 'font' +// : this.getColumnType(col) === ColumnType.Boolean +// ? 'check-square' +// : this.getColumnType(col) === ColumnType.Doc +// ? 'file' +// : this.getColumnType(col) === ColumnType.Image +// ? 'image' +// : this.getColumnType(col) === ColumnType.List +// ? 'list-ul' +// : this.getColumnType(col) === ColumnType.Date +// ? 'calendar' +// : 'align-justify'; + +// const keysDropdown = ( +// c.heading)} +// canAddNew={true} +// addNew={false} +// onSelect={this.props.changeColumns} +// setIsEditing={this.props.setHeaderIsEditing} +// docs={this.props.childDocs} +// Document={this.props.Document} +// dataDoc={this.props.dataDoc} +// fieldKey={this.props.fieldKey} +// ContainingCollectionDoc={this.props.ContainingCollectionDoc} +// ContainingCollectionView={this.props.ContainingCollectionView} +// active={this.props.active} +// openHeader={this.props.openHeader} +// icon={icon} +// col={col} +// // try commenting this out +// width={'100%'} +// /> +// ); + +// const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up'; +// const header = ( +//
+// {keysDropdown} +//
this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}> +// +//
+// {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined :
+ new
} */} +//
+// ); + +// return { +// Header: , +// accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0), +// id: col.heading, +// Cell: (rowProps: CellInfo) => { +// const rowIndex = rowProps.index; +// const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); +// const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; + +// const props: CellProps = { +// row: rowIndex, +// col: columnIndex, +// rowProps: rowProps, +// isFocused: isFocused, +// changeFocusedCellByIndex: this.changeFocusedCellByIndex, +// CollectionView: this.props.CollectionView, +// ContainingCollection: this.props.ContainingCollectionView, +// Document: this.props.Document, +// fieldKey: this.props.fieldKey, +// renderDepth: this.props.renderDepth, +// addDocTab: this.props.addDocTab, +// pinToPres: this.props.pinToPres, +// moveDocument: this.props.moveDocument, +// setIsEditing: this.setCellIsEditing, +// isEditable: isEditable, +// setPreviewDoc: this.props.setPreviewDoc, +// setComputed: this.setComputed, +// getField: this.getField, +// showDoc: this.showDoc, +// }; + +// switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { +// case ColumnType.Number: +// return ; +// case ColumnType.String: +// return ; +// case ColumnType.Boolean: +// return ; +// case ColumnType.Doc: +// return ; +// case ColumnType.Image: +// return ; +// case ColumnType.List: +// return ; +// case ColumnType.Date: +// return ; +// default: +// return ; +// } +// }, +// minWidth: 200, +// }; +// }) +// ); +// columns.push({ +// Header: , +// accessor: (doc: Doc) => 0, +// id: 'add', +// Cell: (rowProps: CellInfo) => { +// const rowIndex = rowProps.index; +// const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); +// const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; +// return ( +// +// ); +// }, +// width: 28, +// resizable: false, +// }); +// return columns; +// } + +// constructor(props: SchemaTableProps) { +// super(props); +// if (this.props.Document._schemaHeaders === undefined) { +// this.props.Document._schemaHeaders = new List([ +// new SchemaHeaderField('title', '#f1efeb'), +// new SchemaHeaderField('author', '#f1efeb'), +// new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date), +// new SchemaHeaderField('text', '#f1efeb', ColumnType.String), +// new SchemaHeaderField('type', '#f1efeb'), +// new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc), +// ]); +// } +// } + +// componentDidMount() { +// document.addEventListener('keydown', this.onKeyDown); +// } + +// componentWillUnmount() { +// document.removeEventListener('keydown', this.onKeyDown); +// } + +// tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => { +// const tableDoc = this.props.Document[DataSym]; +// const effectiveAcl = GetEffectiveAcl(tableDoc); + +// if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) { +// doc.context = this.props.Document; +// tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())); +// return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); +// } +// return false; +// }; + +// private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { +// return !rowInfo +// ? {} +// : { +// ScreenToLocalTransform: this.props.ScreenToLocalTransform, +// addDoc: this.tableAddDoc, +// removeDoc: this.props.deleteDocument, +// rowInfo, +// rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), +// textWrapRow: this.toggleTextWrapRow, +// rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, +// dropAction: StrCast(this.props.Document.childDropAction), +// addDocTab: this.props.addDocTab, +// }; +// }; + +// private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => { +// if (!rowInfo || column) return {}; + +// const row = rowInfo.index; +// //@ts-ignore +// const col = this.columns.map(c => c.heading).indexOf(column!.id); +// const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); +// // TODO: editing border doesn't work :( +// return { +// style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' }, +// }; +// }; + +// @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing); + +// @action +// onKeyDown = (e: KeyboardEvent): void => { +// if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) { +// // && this.props.isSelected(true)) { +// const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : ''; +// this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); + +// if (direction) { +// const pdoc = FieldValue(this.childDocs[this._focusedCell.row]); +// pdoc && this.props.setPreviewDoc(pdoc); +// e.stopPropagation(); +// } +// } else if (e.keyCode === 27) { +// this.props.setPreviewDoc(undefined); +// e.stopPropagation(); // stopPropagation for left/right arrows +// } +// }; + +// changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => { +// switch (direction) { +// case 'tab': +// return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 }; +// case 'right': +// return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 }; +// case 'left': +// return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 }; +// case 'up': +// return { row: curRow === 0 ? curRow : curRow - 1, col: curCol }; +// case 'down': +// return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol }; +// } +// return this._focusedCell; +// }; + +// @action +// changeFocusedCellByIndex = (row: number, col: number): void => { +// if (this._focusedCell.row !== row || this._focusedCell.col !== col) { +// this._focusedCell = { row: row, col: col }; +// } +// this.props.setFocused(this.props.Document); +// }; + +// @undoBatch +// createRow = action(() => { +// this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 })); +// this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col }; +// }); + +// @undoBatch +// @action +// createColumn = () => { +// const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`; +// for (let index = 0; index < 100; index++) { +// if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) { +// this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb')); +// break; +// } +// } +// }; + +// @action +// getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => { +// if (doc && field && column.type === ColumnType.Any) { +// const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)]; +// if (val instanceof ImageField) return ColumnType.Image; +// if (val instanceof Doc) return ColumnType.Doc; +// if (val instanceof DateField) return ColumnType.Date; +// if (val instanceof List) return ColumnType.List; +// } +// if (column.type && column.type !== 0) { +// return column.type; +// } +// if (columnTypes.get(column.heading)) { +// return (column.type = columnTypes.get(column.heading)!); +// } +// return (column.type = ColumnType.Any); +// }; + +// @undoBatch +// @action +// toggleTextwrap = async () => { +// const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); +// if (textwrappedRows.length) { +// this.props.Document.textwrappedSchemaRows = new List([]); +// } else { +// const docs = DocListCast(this.props.Document[this.props.fieldKey]); +// const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); +// this.props.Document.textwrappedSchemaRows = new List(allRows); +// } +// }; + +// @action +// toggleTextWrapRow = (doc: Doc): void => { +// const textWrapped = this.textWrappedRows; +// const index = textWrapped.findIndex(id => doc[Id] === id); + +// index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]); + +// this.textWrappedRows = textWrapped; +// }; + +// @computed +// get reactTable() { +// const children = this.childDocs; +// const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false); +// const expanded: { [name: string]: any } = {}; +// Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true)); +// const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :(((( + +// return ( +// +// row.original.type !== DocumentType.COL ? null : ( +//
+// +//
+// ) +// } +// /> +// ); +// } + +// onContextMenu = (e: React.MouseEvent): void => { +// ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' }); +// }; + +// getField = (row: number, col?: number) => { +// const docs = this.childDocs; + +// row = row % docs.length; +// while (row < 0) row += docs.length; +// const columns = this.props.columns; +// const doc = docs[row]; +// if (col === undefined) { +// return doc; +// } +// if (col >= 0 && col < columns.length) { +// const column = this.props.columns[col].heading; +// return doc[column]; +// } +// return undefined; +// }; + +// createTransformer = (row: number, col: number): Transformer => { +// const self = this; +// const captures: { [name: string]: Field } = {}; + +// const transformer: ts.TransformerFactory = context => { +// return root => { +// function visit(node: ts.Node) { +// node = ts.visitEachChild(node, visit, context); +// if (ts.isIdentifier(node)) { +// const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; +// const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; +// if (isntPropAccess && isntPropAssign) { +// if (node.text === '$r') { +// return ts.createNumericLiteral(row.toString()); +// } else if (node.text === '$c') { +// return ts.createNumericLiteral(col.toString()); +// } else if (node.text === '$') { +// if (ts.isCallExpression(node.parent)) { +// // captures.doc = self.props.Document; +// // captures.key = self.props.fieldKey; +// } +// } +// } +// } + +// return node; +// } +// return ts.visitNode(root, visit); +// }; +// }; + +// // const getVars = () => { +// // return { capturedVariables: captures }; +// // }; + +// return { transformer /*getVars*/ }; +// }; + +// setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => { +// script = `const $ = (row:number, col?:number) => { +// const rval = (doc as any)[key][row + ${row}]; +// return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading]; +// } +// return ${script}`; +// const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) }); +// if (compiled.compiled) { +// doc[field] = new ComputedField(compiled); +// return true; +// } +// return false; +// }; + +// @action +// showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => { +// this._showDoc = doc; +// if (dataDoc && screenX && screenY) { +// this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY); +// } +// }; + +// onOpenClick = () => { +// this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight); +// }; + +// getPreviewTransform = (): Transform => { +// return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth); +// }; + +// render() { +// const preview = ''; +// return ( +//
this.props.active(true) && e.stopPropagation()} +// onDrop={e => this.props.onDrop(e, {})} +// onContextMenu={this.onContextMenu}> +// {this.reactTable} +// {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : ( +//
+// + new +//
+// )} +// {!this._showDoc ? null : ( +//
+// 150} +// PanelHeight={() => 150} +// ScreenToLocalTransform={this.getPreviewTransform} +// docFilters={returnEmptyFilter} +// docRangeFilters={returnEmptyFilter} +// searchFilterDocs={returnEmptyDoclist} +// ContainingCollectionDoc={this.props.CollectionView?.props.Document} +// ContainingCollectionView={this.props.CollectionView} +// moveDocument={this.props.moveDocument} +// whenChildContentsActiveChanged={emptyFunction} +// addDocTab={this.props.addDocTab} +// pinToPres={this.props.pinToPres} +// bringToFront={returnFalse}> +//
+// )} +//
+// ); +// } +// } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 569579996..459554ebe 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -44,6 +44,7 @@ import { WebBox } from './WebBox'; import React = require('react'); import XRegExp = require('xregexp'); import { LoadingBox } from './LoadingBox'; +import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -268,6 +269,7 @@ export class DocumentContentsView extends React.Component< HTMLtag, ComparisonBox, LoadingBox, + SchemaRowBox, }} bindings={bindings} jsx={layoutFrame} -- cgit v1.2.3-70-g09d2 From 6c58ca9d473103624be82c6f2da90f22bafd7b98 Mon Sep 17 00:00:00 2001 From: mehekj Date: Sat, 28 Jan 2023 23:27:17 -0500 Subject: version without schemarow as documentview --- src/client/documents/DocumentTypes.ts | 1 - src/client/documents/Documents.ts | 11 --- .../collectionSchema/CollectionSchemaView.scss | 6 -- .../collectionSchema/CollectionSchemaView.tsx | 94 ++++++---------------- .../collectionSchema/SchemaColumnHeader.tsx | 2 +- .../collections/collectionSchema/SchemaRowBox.tsx | 88 +++++++------------- src/client/views/nodes/DocumentContentsView.tsx | 2 - 7 files changed, 53 insertions(+), 151 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index b910b26b0..d99cd2dac 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -40,7 +40,6 @@ export enum DocumentType { SEARCHITEM = 'searchitem', COMPARISON = 'comparison', GROUP = 'group', - SCHEMAROW = 'schemarow', LINKDB = 'linkdb', // database of links ??? why do we have this SCRIPTDB = 'scriptdb', // database of scripts diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f6104d655..7de1221cc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -26,7 +26,6 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; -import { SchemaRowBox } from '../views/collections/collectionSchema/SchemaRowBox'; import { CollectionView } from '../views/collections/CollectionView'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; @@ -650,12 +649,6 @@ export namespace Docs { options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' }, }, ], - [ - DocumentType.SCHEMAROW, - { - layout: { view: SchemaRowBox, dataField: defaultDataKey }, - }, - ], [ DocumentType.LOADING, { @@ -1148,10 +1141,6 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); } - export function SchemaRowDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.SCHEMAROW), undefined, { ...(options || {}) }); - } - export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, viewType: CollectionViewType.Docking, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index e0d0101a2..4fa5d80e2 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -126,16 +126,10 @@ } } -.schema-row-wrapper { - max-height: 70px; - overflow: hidden; -} - .schema-header-row, .schema-row { display: flex; flex-direction: row; - height: 100%; max-height: 70px; overflow: auto; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index c9f934aec..29b22c0d5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,29 +1,28 @@ import React = require('react'); -import { action, computed, observable, ObservableMap, ObservableSet, trace, untracked } from 'mobx'; +import { action, computed, observable, ObservableMap, ObservableSet, untracked } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, DocListCast, Opt, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils'; +import { emptyFunction, returnEmptyString, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; +import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { EditableView } from '../../EditableView'; +import { DocumentView } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; -import { DocFocusOptions, DocumentView, ViewAdjustment } from '../../nodes/DocumentView'; -import { DefaultStyleProvider } from '../../StyleProvider'; -import { Transform } from '../../../util/Transform'; export enum ColumnType { Number, @@ -33,7 +32,7 @@ export enum ColumnType { Image, } -const defaultColumnKeys: string[] = ['title', 'type', 'author', 'text', 'data', 'tags']; +const defaultColumnKeys: string[] = ['title', 'type', 'author', 'text', 'data', 'creationDate']; @observer export class CollectionSchemaView extends CollectionSubView() { @@ -399,25 +398,6 @@ export class CollectionSchemaView extends CollectionSubView() { ContextMenu.Instance.displayMenu(x, y, undefined, true); }; - focusDocument = (doc: Doc, options: DocFocusOptions) => { - Doc.BrushDoc(doc); - - let focusSpeed = 0; - const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); - if (found) { - const top = found.getBoundingClientRect().top; - const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); - if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); - } - } - const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; - this.props.focus(this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), - }); - }; - isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; @@ -465,52 +445,24 @@ export class CollectionSchemaView extends CollectionSubView() { let dref: Opt; return ( -
- (dref = r || undefined)} - LayoutTemplate={this.props.childLayoutTemplate} - LayoutTemplateString={SchemaRowBox.LayoutString(this.props.fieldKey)} - renderDepth={this.props.renderDepth + 1} - Document={doc} - DataDoc={dataDoc} - ContainingCollectionView={this.props.CollectionView} - ContainingCollectionDoc={this.Document} - PanelWidth={this.props.PanelWidth} - PanelHeight={() => 70} - styleProvider={DefaultStyleProvider} - focus={this.focusDocument} - docFilters={this.childDocFilters} - docRangeFilters={this.childDocRangeFilters} - searchFilterDocs={this.searchFilterDocs} - rootSelected={this.rootSelected} - ScreenToLocalTransform={() => this.getDocTransform(doc, dref)} - bringToFront={emptyFunction} - isContentActive={this.isChildContentActive} - hideDecorations={true} - hideTitle={true} - hideDocumentButtonBar={true} - /> -
+ ); - - // })}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index a6a5f66ab..8e6d3d78a 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -40,7 +40,7 @@ export class SchemaColumnHeader extends React.Component case ColumnType.Boolean: return ( <> - e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} /> + e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} /> {this._newFieldDefault ? 'true' : 'false'} ); diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index f790e9dbf..2cf0a1b79 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -1,20 +1,17 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, ObservableMap, ObservableSet } from 'mobx'; +import { action, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc } from '../../../../fields/Doc'; +import { DragManager } from '../../../util/DragManager'; import { undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { FieldView, FieldViewProps } from '../../nodes/FieldView'; +import { OpenWhere } from '../../nodes/DocumentView'; +import { FieldViewProps } from '../../nodes/FieldView'; import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; -import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; -import { DragManager } from '../../../util/DragManager'; -import { OpenWhere } from '../../nodes/DocumentView'; -import { Cast } from '../../../../fields/Types'; -import { listSpec } from '../../../../fields/Schema'; -import { CollectionSchemaView } from './CollectionSchemaView'; +import { setupMoveUpEvents, emptyFunction } from '../../../../Utils'; export interface SchemaRowBoxProps extends FieldViewProps { rowIndex: number; @@ -30,61 +27,37 @@ export interface SchemaRowBoxProps extends FieldViewProps { } @observer -export class SchemaRowBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(SchemaRowBox, fieldKey); - } - +export class SchemaRowBox extends ViewBoxBaseComponent() { private _ref: HTMLDivElement | null = null; bounds = () => this._ref?.getBoundingClientRect(); - @computed get columnKeys() { - return StrListCast(this.props.ContainingCollectionDoc?.columnKeys); - } - - @computed get storedColumnWidths() { - let widths = Cast( - this.props.ContainingCollectionDoc?.columnWidths, - listSpec('number'), - this.columnKeys.map(() => (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length) - ); - - const totalWidth = widths.reduce((sum, width) => sum + width, 0); - if (totalWidth !== this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth) { - widths = widths.map(w => { - const proportion = w / totalWidth; - return proportion * (this.props.PanelWidth() - CollectionSchemaView._rowMenuWidth); - }); - } - - return widths; - } + isSelected = () => this.props.selectedDocs.has(this.props.Document); @action onRowPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); - // setupMoveUpEvents( - // this, - // e, - // e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex), - // emptyFunction, - // e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex) - // ); + setupMoveUpEvents( + this, + e, + e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex), + emptyFunction, + e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex) + ); }; onPointerEnter = (e: any) => { - // if (!this.props.dragging) return; + if (!this.props.dragging) return; document.removeEventListener('pointermove', this.onPointerMove); document.addEventListener('pointermove', this.onPointerMove); }; onPointerMove = (e: any) => { - // if (!this.props.dragging) return; + if (!this.props.dragging) return; let dragIsRow: boolean = true; DragManager.docsBeingDragged.forEach(doc => { - // dragIsRow = this.props.selectedDocs.has(doc); + dragIsRow = this.props.selectedDocs.has(doc); }); if (this._ref && dragIsRow) { const rect = this._ref.getBoundingClientRect(); @@ -94,11 +67,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { if (y <= halfLine) { this._ref.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; this._ref.style.borderBottom = '0px'; - // this.props.dropIndex(this.props.rowIndex); + this.props.dropIndex(this.props.rowIndex); } else if (y > halfLine) { this._ref.style.borderTop = '0px'; this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; - // this.props.dropIndex(this.props.rowIndex + 1); + this.props.dropIndex(this.props.rowIndex + 1); } } }; @@ -115,22 +88,19 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { return (
{ - // row && this.props.addRowRef(this.props.Document, row); + row && this.props.addRowRef(this.props.Document, row); this._ref = row; }}>
+ style={{ + width: this.props.rowMenuWidth, + }}>
{ @@ -149,8 +119,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent() {
- {this.columnKeys.map((key, index) => ( - + {this.props.columnKeys.map((key, index) => ( + ))}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 459554ebe..569579996 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -44,7 +44,6 @@ import { WebBox } from './WebBox'; import React = require('react'); import XRegExp = require('xregexp'); import { LoadingBox } from './LoadingBox'; -import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -269,7 +268,6 @@ export class DocumentContentsView extends React.Component< HTMLtag, ComparisonBox, LoadingBox, - SchemaRowBox, }} bindings={bindings} jsx={layoutFrame} -- cgit v1.2.3-70-g09d2 From 4475adee0f13d9ec407aff6a47094c7ce808af0c Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Wed, 22 Feb 2023 17:35:26 -0500 Subject: added GPT summarization functionality --- package-lock.json | 73 ++++++++++++------------- package.json | 1 + src/client/apis/gpt/Summarization.ts | 35 ++++++++++++ src/client/views/MainView.tsx | 1 + src/client/views/MarqueeAnnotator.tsx | 19 +++++-- src/client/views/collections/CollectionView.tsx | 2 + src/client/views/nodes/WebBox.tsx | 19 +++++++ src/client/views/pdf/AnchorMenu.tsx | 43 +++++++++++++++ src/client/views/pdf/GPTPopup.scss | 9 +++ src/client/views/pdf/GPTPopup.tsx | 19 +++++++ src/client/views/pdf/PDFViewer.tsx | 22 +++++++- 11 files changed, 199 insertions(+), 44 deletions(-) create mode 100644 src/client/apis/gpt/Summarization.ts create mode 100644 src/client/views/pdf/GPTPopup.scss create mode 100644 src/client/views/pdf/GPTPopup.tsx (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 547a6e1d7..6ea3936cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5303,16 +5303,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6480,28 +6470,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6513,7 +6481,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -16724,6 +16691,40 @@ "mimic-fn": "^2.1.0" } }, + "openai": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.1.0.tgz", + "integrity": "sha512-v5kKFH5o+8ld+t0arudj833Mgm3GcgBnbyN9946bj6u7bvel4Yg6YFz2A4HLIYDzmMjIo0s6vSG9x73kOwvdCg==", + "requires": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "opentype.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", @@ -21392,12 +21393,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/package.json b/package.json index 712354355..480657d5f 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,7 @@ "nodemon": "^1.19.4", "normalize.css": "^8.0.1", "npm": "^6.14.18", + "openai": "^3.1.0", "p-limit": "^2.2.0", "passport": "^0.4.0", "passport-google-oauth20": "^2.0.0", diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts new file mode 100644 index 000000000..3706c7a5b --- /dev/null +++ b/src/client/apis/gpt/Summarization.ts @@ -0,0 +1,35 @@ +import { Configuration, OpenAIApi } from 'openai'; + +const gptSummarize = async (text: string) => { + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createCompletion({ + model: 'text-davinci-003', + max_tokens: 256, + temperature: 0.7, + prompt: `Summarize this text in one sentence: ${text}`, + }); + return response.data.choices[0].text; + } catch (err) { + console.log(err); + return ''; + } +}; + +// Summarizing with the MeaningCloud API +const fetchSummary = async (text: string, numSentences?: number) => { + const key = '0b41c071f838e573847f477e8f69e9d9'; + const queryURL = ''; + const sentences = numSentences ? numSentences : 3; + const URL = `https://api.meaningcloud.com/summarization-1.0?key=${key}&txt=${text}&sentences=${sentences}`; + + const res = await fetch(URL); + const data = await res.json(); + console.log(data.summary); + return data.summary; +}; + +export { fetchSummary, gptSummarize }; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 895ed9bda..68f1bd72b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -321,6 +321,7 @@ export class MainView extends React.Component { fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, + fa.faCommentDots, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 5ab91dd70..9e9f24393 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -28,6 +28,8 @@ export interface MarqueeAnnotatorProps { docView: DocumentView; savedAnnotations: () => ObservableMap; selectionText: () => string; + summaryText: () => string; + setSummaryText: () => Promise; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; getPageFromScroll?: (top: number) => number; @@ -50,6 +52,10 @@ export class MarqueeAnnotator extends React.Component { AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true); AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true)); + AnchorMenu.Instance.OnSummary = async (e: PointerEvent) => { + await this.props.setSummaryText(); + this.props.anchorMenuClick?.()?.(this.highlight('', true, undefined, undefined, true)); + }; AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true); @@ -141,7 +147,7 @@ export class MarqueeAnnotator extends React.Component { @undoBatch @action - makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap): Opt => { + makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap, summarize?: boolean): Opt => { const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations(); if (savedAnnoMap.size === 0) return undefined; const savedAnnos = Array.from(savedAnnoMap.values())[0]; @@ -159,7 +165,12 @@ export class MarqueeAnnotator extends React.Component { return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, text: this.props.selectionText(), backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { + annotationOn: this.props.rootDoc, + text: summarize ? this.props.summaryText() : this.props.selectionText(), + backgroundColor: 'transparent', + title: 'Selection on ' + this.props.rootDoc.title, + }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; @@ -194,10 +205,10 @@ export class MarqueeAnnotator extends React.Component { return textRegionAnno; }; @action - highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => { + highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean, summarize?: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); - const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); + const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations, summarize); addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); return (annotationDoc as Doc) ?? undefined; }; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a28b1ca19..55f46906e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -67,6 +67,8 @@ interface CollectionViewProps_ extends FieldViewProps { AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) + // for animation trail in-between selection + isTrailBox?: boolean; } export interface CollectionViewProps extends React.PropsWithChildren {} @observer diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index d0d638e98..996028ec8 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -36,6 +36,7 @@ import { VideoBox } from './VideoBox'; import './WebBox.scss'; import React = require('react'); import { DragManager } from '../../util/DragManager'; +import { gptSummarize } from '../../apis/gpt/Summarization'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -58,6 +59,21 @@ export class WebBox extends ViewBoxAnnotatableComponent(); private _searchString = ''; + // GPT Additions + private _summaryText: string = ''; + private _selectionText: string = ''; + setSummaryText = async () => { + try { + const summary = await gptSummarize(this.selectionText()); + this._summaryText = `Summary: ${summary}`; + } catch (err) { + console.log(err); + this._summaryText = 'Failed to fetch summary.'; + } + }; + summaryText = () => this._summaryText; + selectionText = () => this._selectionText; + private get _getAnchor() { return AnchorMenu.Instance?.GetAnchor; } @@ -341,6 +357,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); } @@ -1017,6 +1034,8 @@ export class WebBox extends ViewBoxAnnotatableComponent diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index c53cc608c..63c8f9145 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,6 +10,8 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; +import { gptSummarize } from '../../apis/gpt/Summarization'; +import { GPTPopup } from './GPTPopup'; import './AnchorMenu.scss'; @observer @@ -43,10 +45,30 @@ export class AnchorMenu extends AntimodeMenu { @observable public Highlighting: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; + // GPT additions (flow 2) + @observable private summarizedText: string = ''; + @observable private showGPTPopup: boolean = false; + @action + setGPTPopupVis = (vis: boolean) => { + this.showGPTPopup = vis; + }; + @action + setSummarizedText = (txt: string) => { + this.summarizedText = txt; + }; + + private selectedText: string = ''; + setSelectedText = (txt: string) => { + this.selectedText = txt; + }; + public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; + public OnSummary: (e: PointerEvent) => Promise = () => { + return new Promise(() => {}); + }; public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; @@ -83,11 +105,26 @@ export class AnchorMenu extends AntimodeMenu { () => SelectionManager.Views(), selected => { this._showLinkPopup = false; + this.setGPTPopupVis(false); AnchorMenu.Instance.fadeOut(true); } ); } + getGPTSummary = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnSummary?.(e)); + }; + + invokeGPT = async (e: React.PointerEvent) => { + this.setGPTPopupVis(true); + const res = await gptSummarize(this.selectedText); + if (res) { + this.setSummarizedText(res); + } else { + this.setSummarizedText('Something went wrong.'); + } + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -192,6 +229,12 @@ export class AnchorMenu extends AntimodeMenu { + Summarize with GPT-3
}> + + + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}> - Summarize with AI
}> - - - + {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && ( + Summarize with AI
}> + + + )} + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}> + + + ) : ( +
+ + + +
+ )} +
+ )} + {this.summaryDone && ( +
+ + AI generated responses can contain inaccurate or misleading content.{' '} + + Learn More + +
)}
); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 324f31f23..408ba07d1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -25,6 +25,7 @@ import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); import { gptSummarize } from '../../apis/gpt/Summarization'; +import { GPTPopup } from './GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; @@ -41,6 +42,7 @@ interface IViewerProps extends FieldViewProps { fieldKey: string; pdf: Pdfjs.PDFDocumentProxy; url: string; + sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void); @@ -83,19 +85,6 @@ export class PDFViewer extends React.Component { return AnchorMenu.Instance?.GetAnchor; } - // Fields for using GPT to summarize selected text - private _summaryText: string = ''; - setSummaryText = async () => { - try { - const summary = await gptSummarize(this.selectionText()); - this._summaryText = `Summary: ${summary}`; - } catch (err) { - console.log(err); - this._summaryText = 'Failed to fetch summary.'; - } - }; - summaryText = () => this._summaryText; - selectionText = () => this._selectionText; selectionContent = () => this._selectionContent; @@ -435,6 +424,10 @@ export class PDFViewer extends React.Component { this.createTextAnnotation(sel, sel.getRangeAt(0)); 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.props.sidebarAddDoc; }; @action -- cgit v1.2.3-70-g09d2 From 222f659b8c291fafce2648e367392dd9f467cb25 Mon Sep 17 00:00:00 2001 From: mehekj Date: Mon, 27 Feb 2023 17:00:49 -0500 Subject: rows are documentviews but clipped --- package-lock.json | 39 +++++++ .../collectionSchema/CollectionSchemaView.scss | 6 ++ .../collectionSchema/CollectionSchemaView.tsx | 96 ++++++++++++----- .../collections/collectionSchema/SchemaRowBox.tsx | 120 ++++++++++++--------- .../collectionSchema/SchemaTableCell.tsx | 12 ++- src/client/views/nodes/DocumentContentsView.tsx | 2 + 6 files changed, 195 insertions(+), 80 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 3c2dccbf8..8806887df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5308,6 +5308,16 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6475,6 +6485,28 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "dev": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6486,6 +6518,7 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { + "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -21371,6 +21404,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index bd0fc11b3..46c2e2d1a 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -134,10 +134,16 @@ } } +.schema-row-wrapper { + max-height: 70px; + overflow: hidden; +} + .schema-header-row, .schema-row { display: flex; flex-direction: row; + height: 100%; max-height: 70px; overflow: auto; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 64c39cf1a..46510b6fe 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -8,7 +8,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -17,7 +17,7 @@ import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { EditableView } from '../../EditableView'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, ViewAdjustment } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; @@ -41,9 +41,9 @@ export class CollectionSchemaView extends CollectionSubView() { private _lastSelectedRow: number | undefined; private _selectedDocSortedArray: Doc[] = []; private _closestDropIndex: number = 0; - private _minColWidth: number = 150; private _previewRef: HTMLDivElement | null = null; + public static _minColWidth: number = 150; public static _rowMenuWidth: number = 100; public static _previewDividerWidth: number = 4; @observable _selectedDocs: ObservableSet = new ObservableSet(); @@ -110,6 +110,7 @@ export class CollectionSchemaView extends CollectionSubView() { } componentDidMount() { + this.props.setContentView?.(this); document.addEventListener('keydown', this.onKeyDown); } @@ -243,8 +244,8 @@ export class CollectionSchemaView extends CollectionSubView() { if (shrinking === undefined || growing === undefined) return true; change = Math.abs(change); - if (this._displayColumnWidths[shrinking] - change < this._minColWidth) { - change = this._displayColumnWidths[shrinking] - this._minColWidth; + if (this._displayColumnWidths[shrinking] - change < CollectionSchemaView._minColWidth) { + change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth; } this._displayColumnWidths[shrinking] -= change; @@ -481,7 +482,7 @@ export class CollectionSchemaView extends CollectionSubView() { !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' }); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { Doc.GetProto(this.props.Document)[name] = ''; - const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true }); + const created = Docs.Create.TextDocument('', { title: name, _autoHeight: true }); if (created) { if (this.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, this.props.Document); @@ -492,9 +493,25 @@ export class CollectionSchemaView extends CollectionSubView() { ContextMenu.Instance.displayMenu(x, y, undefined, true); }; + focusDocument = (doc: Doc, options: DocFocusOptions) => { + Doc.BrushDoc(doc); + let focusSpeed = 0; + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + if (found) { + const top = found.getBoundingClientRect().top; + const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); + if (Math.floor(localTop[1]) !== 0) { + smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + } + } + const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; + this.props.focus(this.rootDoc, { + ...options, + afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), + }); + }; isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; - getDocTransform(doc: Doc, dref?: DocumentView) { const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off @@ -543,26 +560,55 @@ export class CollectionSchemaView extends CollectionSubView() { {this.childDocs.map((doc: Doc, index: number) => { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; let dref: Opt; - return ( - +
+ (dref = r || undefined)} + LayoutTemplate={this.props.childLayoutTemplate} + LayoutTemplateString={SchemaRowBox.LayoutString(this.props.fieldKey)} + renderDepth={this.props.renderDepth + 1} + Document={doc} + DataDoc={dataDoc} + ContainingCollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.Document} + PanelWidth={() => this.tableWidth} + PanelHeight={() => 70} + styleProvider={DefaultStyleProvider} + focus={this.focusDocument} + docFilters={this.childDocFilters} + docViewPath={this.props.docViewPath} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + rootSelected={this.rootSelected} + ScreenToLocalTransform={() => this.getDocTransform(doc, dref)} + bringToFront={emptyFunction} + isContentActive={this.isChildContentActive} + hideDecorations={true} + hideTitle={true} + hideDocumentButtonBar={true} + /> +
); + // return ( + // + // ); })}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 2cf0a1b79..0378fa67e 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -1,17 +1,17 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, ObservableSet } from 'mobx'; +import { computed, ObservableSet } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../../fields/Doc'; -import { DragManager } from '../../../util/DragManager'; import { undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; -import { Colors } from '../../global/globalEnums'; import { OpenWhere } from '../../nodes/DocumentView'; -import { FieldViewProps } from '../../nodes/FieldView'; +import { FieldView, FieldViewProps } from '../../nodes/FieldView'; +import { CollectionSchemaView } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; -import { setupMoveUpEvents, emptyFunction } from '../../../../Utils'; +import { Colors } from '../../global/globalEnums'; +import { DocCast, StrCast } from '../../../../fields/Types'; export interface SchemaRowBoxProps extends FieldViewProps { rowIndex: number; @@ -27,79 +27,93 @@ export interface SchemaRowBoxProps extends FieldViewProps { } @observer -export class SchemaRowBox extends ViewBoxBaseComponent() { +export class SchemaRowBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(SchemaRowBox, fieldKey); + } + private _ref: HTMLDivElement | null = null; bounds = () => this._ref?.getBoundingClientRect(); - isSelected = () => this.props.selectedDocs.has(this.props.Document); + @computed get schemaView() { + const vpath = this.props.docViewPath(); + console.log(vpath[vpath.length - 2]); + return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined; + } - @action - onRowPointerDown = (e: React.PointerEvent) => { - e.stopPropagation(); + @computed get schemaDoc() { + return this.props.ContainingCollectionDoc!; + } - setupMoveUpEvents( - this, - e, - e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex), - emptyFunction, - e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex) - ); - }; + // isSelected = () => this.props.selectedDocs.has(this.props.Document); - onPointerEnter = (e: any) => { - if (!this.props.dragging) return; - document.removeEventListener('pointermove', this.onPointerMove); - document.addEventListener('pointermove', this.onPointerMove); - }; + // @action + // onRowPointerDown = (e: React.PointerEvent) => { + // e.stopPropagation(); - onPointerMove = (e: any) => { - if (!this.props.dragging) return; - let dragIsRow: boolean = true; - DragManager.docsBeingDragged.forEach(doc => { - dragIsRow = this.props.selectedDocs.has(doc); - }); - if (this._ref && dragIsRow) { - const rect = this._ref.getBoundingClientRect(); - const y = e.clientY - rect.top; //y position within the element. - const height = this._ref.clientHeight; - const halfLine = height / 2; - if (y <= halfLine) { - this._ref.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; - this._ref.style.borderBottom = '0px'; - this.props.dropIndex(this.props.rowIndex); - } else if (y > halfLine) { - this._ref.style.borderTop = '0px'; - this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; - this.props.dropIndex(this.props.rowIndex + 1); - } - } - }; + // setupMoveUpEvents( + // this, + // e, + // e => this.props.startDrag(e, this.props.Document, this._ref!, this.props.rowIndex), + // emptyFunction, + // e => this.props.selectRow(e, this.props.Document, this._ref!, this.props.rowIndex) + // ); + // }; + + // onPointerEnter = (e: any) => { + // if (!this.props.dragging) return; + // document.removeEventListener('pointermove', this.onPointerMove); + // document.addEventListener('pointermove', this.onPointerMove); + // }; + + // onPointerMove = (e: any) => { + // if (!this.props.dragging) return; + // let dragIsRow: boolean = true; + // DragManager.docsBeingDragged.forEach(doc => { + // dragIsRow = this.props.selectedDocs.has(doc); + // }); + // if (this._ref && dragIsRow) { + // const rect = this._ref.getBoundingClientRect(); + // const y = e.clientY - rect.top; //y position within the element. + // const height = this._ref.clientHeight; + // const halfLine = height / 2; + // if (y <= halfLine) { + // this._ref.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; + // this._ref.style.borderBottom = '0px'; + // this.props.dropIndex(this.props.rowIndex); + // } else if (y > halfLine) { + // this._ref.style.borderTop = '0px'; + // this._ref.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; + // this.props.dropIndex(this.props.rowIndex + 1); + // } + // } + // }; onPointerLeave = (e: any) => { if (this._ref) { this._ref.style.borderTop = '0px'; this._ref.style.borderBottom = '0px'; } - document.removeEventListener('pointermove', this.onPointerMove); + // document.removeEventListener('pointermove', this.onPointerMove); }; render() { return (
{ - row && this.props.addRowRef(this.props.Document, row); + // row && this.props.addRowRef(this.props.Document, row); this._ref = row; }}>
() {
- {this.props.columnKeys.map((key, index) => ( - + {this.schemaView?.columnKeys?.map((key, index) => ( + ))}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 5d9474173..e2f6d99f1 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,7 +1,14 @@ import React = require('react'); import { observer } from 'mobx-react'; -import { Doc, Field } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import './CollectionSchemaView.scss'; +import { type } from 'jquery'; +import { action } from 'mobx'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { FieldValue } from '../../../../fields/Types'; +import { CompileScript } from '../../../util/Scripting'; +import { EditableView } from '../../EditableView'; +import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss'; export interface SchemaTableCellProps { Document: Doc; @@ -14,7 +21,8 @@ export class SchemaTableCell extends React.Component { render() { return (
- {Field.toString(this.props.Document[this.props.fieldKey] as Field)} + {/* {Field.toString(this.props.Document[this.props.fieldKey] as Field)} */} + Field.toKeyValueString(this.props.Document, this.props.fieldKey)} SetValue={(value: string) => true} />
); } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 569579996..459554ebe 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -44,6 +44,7 @@ import { WebBox } from './WebBox'; import React = require('react'); import XRegExp = require('xregexp'); import { LoadingBox } from './LoadingBox'; +import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -268,6 +269,7 @@ export class DocumentContentsView extends React.Component< HTMLtag, ComparisonBox, LoadingBox, + SchemaRowBox, }} bindings={bindings} jsx={layoutFrame} -- cgit v1.2.3-70-g09d2 From 9853c1e3ca1c3a1d2da210118b927adc559e67fa Mon Sep 17 00:00:00 2001 From: mehekj Date: Wed, 1 Mar 2023 17:53:56 -0500 Subject: override onclick script --- .../collectionSchema/CollectionSchemaView.tsx | 20 +++++++++++++------- .../collections/collectionSchema/SchemaRowBox.tsx | 20 ++++++++++---------- src/client/views/nodes/DocumentView.tsx | 3 ++- 3 files changed, 25 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 1a26f1178..02591489b 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -25,6 +25,7 @@ import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; import { DefaultStyleProvider } from '../../StyleProvider'; import { DocumentManager } from '../../../util/DocumentManager'; +import { ScriptField } from '../../../../fields/ScriptField'; export enum ColumnType { Number, @@ -338,12 +339,13 @@ export class CollectionSchemaView extends CollectionSubView() { this._lastSelectedRow = undefined; }; + rowOnClickScript = ScriptField.MakeFunction('scriptContext.selectRow(self, shiftKey, ctrlKey || metaKey)', { scriptContext: 'any', shiftKey: 'boolean', ctrlKey: 'boolean', metaKey: 'boolean' })!; + @action - selectRow = (e: PointerEvent, doc: Doc, ref: HTMLDivElement, index: number) => { - e.stopPropagation(); + selectRow = (doc: Doc, shift: boolean, ctrl: boolean) => { + console.log(ctrl); + const index = this.childDocs.indexOf(doc); if (index < 0) return; - const ctrl = e.ctrlKey || e.metaKey; - const shift = e.shiftKey; if (shift && this._lastSelectedRow !== undefined) { const startRow = Math.min(this._lastSelectedRow, index); const endRow = Math.max(this._lastSelectedRow, index); @@ -358,8 +360,10 @@ export class CollectionSchemaView extends CollectionSubView() { this.removeDocFromSelection(doc); } } else { - this.clearSelection(); - this.addDocToSelection(doc, false, index); + if (!this._selectedDocs.has(doc)) { + this.clearSelection(); + this.addDocToSelection(doc, false, index); + } } }; @@ -405,7 +409,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - startDrag = (e: PointerEvent, doc: Doc, ref: HTMLDivElement, index: number) => { + startDrag = (e: PointerEvent, doc: Doc, index: number) => { if (!this._selectedDocs.has(doc)) { this.clearSelection(); this.addDocToSelection(doc, false, index); @@ -604,6 +608,8 @@ export class CollectionSchemaView extends CollectionSubView() { hideTitle={true} hideDocumentButtonBar={true} fitWidth={returnTrue} + onClick={() => this.rowOnClickScript} + scriptContext={this} />
); diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index d6a00966d..5de9fdf5c 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -42,13 +42,13 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { onRowPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); - setupMoveUpEvents( - this, - e, - e => this.schemaView?.startDrag(e, this.rootDoc, this._ref!, this.rowIndex) ?? true, - emptyFunction, - e => this.schemaView?.selectRow(e, this.rootDoc, this._ref!, this.rowIndex) - ); + // setupMoveUpEvents( + // this, + // e, + // e => this.schemaView?.startDrag(e, this.rootDoc, this.rowIndex) ?? true, + // emptyFunction, + // e => this.schemaView?.selectRow(e, this.rootDoc, this.rowIndex) + // ); }; onPointerEnter = (e: any) => { @@ -97,9 +97,9 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { ? { height: CollectionSchemaView._rowHeight, backgroundColor: Colors.LIGHT_BLUE, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined /*, opacity: this.props.dragging ? 0.5 : 1 */ } : { height: CollectionSchemaView._rowHeight, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined } } - onPointerDown={this.onRowPointerDown} - onPointerEnter={this.onPointerEnter} - onPointerLeave={this.onPointerLeave} + // onPointerDown={this.onRowPointerDown} + // onPointerEnter={this.onPointerEnter} + // onPointerLeave={this.onPointerLeave} ref={(row: HTMLDivElement | null) => { row && this.schemaView?.addRowRef(this.rootDoc, row); this._ref = row; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 36c0240f1..5e7037754 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -644,7 +644,7 @@ export class DocumentViewInternal extends DocComponent this.onClickHandler.script.run( { @@ -658,6 +658,7 @@ export class DocumentViewInternal extends DocComponent Date: Wed, 1 Mar 2023 23:33:01 -0500 Subject: Added text completion --- src/client/apis/gpt/Summarization.ts | 45 ++++--- .../nodes/formattedText/FormattedTextBox.scss | 4 + .../views/nodes/formattedText/FormattedTextBox.tsx | 129 ++++++++++++++++----- src/client/views/pdf/AnchorMenu.tsx | 26 ++++- src/client/views/pdf/PDFViewer.tsx | 1 - 5 files changed, 154 insertions(+), 51 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts index ba98ad591..b65736237 100644 --- a/src/client/apis/gpt/Summarization.ts +++ b/src/client/apis/gpt/Summarization.ts @@ -1,27 +1,41 @@ import { Configuration, OpenAIApi } from 'openai'; +enum GPTCallType { + SUMMARY = 'summary', + COMPLETION = 'completion', +} + +type GPTCallOpts = { + model: string; + maxTokens: number; + temp: number; + prompt: string; +}; + +const callTypeMap: { [type: string]: GPTCallOpts } = { + summary: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: 'Summarize this text: ' }, + completion: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: '' }, +}; + /** - * Summarizes the inputted text with OpenAI. - * - * @param text Text to summarize - * @returns Summary of text + * Calls the OpenAI API. + * + * @param inputText Text to process + * @returns AI Output */ -const gptSummarize = async (text: string) => { - text += '.'; - const model = 'text-curie-001'; // Most advanced: text-davinci-003 - const maxTokens = 200; - const temp = 0.5; - +const gptAPICall = async (inputText: string, callType: GPTCallType) => { + if (callType === GPTCallType.SUMMARY) inputText += '.'; + const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, }); const openai = new OpenAIApi(configuration); const response = await openai.createCompletion({ - model: model, - max_tokens: maxTokens, - temperature: temp, - prompt: `Summarize this text: ${text}`, + model: opts.model, + max_tokens: opts.maxTokens, + temperature: opts.temp, + prompt: `${opts.prompt}${inputText}`, }); return response.data.choices[0].text; } catch (err) { @@ -30,4 +44,5 @@ const gptSummarize = async (text: string) => { } }; -export { gptSummarize }; + +export { gptAPICall, GPTCallType}; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index cbe0a465d..fd7fbb333 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -149,6 +149,10 @@ audiotag:hover { } } +.gpt-typing-wrapper { + padding: 10px; +} + // .menuicon { // display: inline-block; // border-right: 1px solid rgba(0, 0, 0, 0.2); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b9327db0d..35c845deb 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -64,12 +64,21 @@ import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { Configuration, OpenAIApi } from 'openai'; +import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; +enum GPTStatus { + LOADING, + TYPING, + NONE, +} + @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { @@ -172,6 +181,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + this.gptStatus = status; + }; + public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoad = ''; public static DontSelectInitialText = false; // whether initial text should be selected or not @@ -840,14 +862,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); + optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' }); optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; + mockGPT = async (): Promise => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve('Mock GPT Call.'); + }, 2000); + }); + }; + + askGPT = action(async () => { + try { + this.gptPrompt = (this.dataDoc.text as RichTextField)?.Text; + this.setGPTStatus(GPTStatus.LOADING); + let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); + // let res = await this.mockGPT(); + if (res) { + this.gptRes = res; + this.setGPTStatus(GPTStatus.TYPING); + } else { + this.setGPTStatus(GPTStatus.NONE); + } + } catch (err) { + console.log(err); + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong'; + this.setGPTStatus(GPTStatus.NONE); + } + }); + + setGPTText = action(() => { + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes; + this.gptRes = ''; + this.setGPTStatus(GPTStatus.NONE); + }); + generateImage = async () => { - console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text); + console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, @@ -856,17 +912,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this._editorView && this._recording) { @@ -1942,29 +1997,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent -
+ {this.gptStatus === GPTStatus.NONE || this.gptStatus === GPTStatus.LOADING ? (
-
+ onScroll={this.onScroll} + onDrop={this.ondrop}> +
+
+ ) : ( +
+
{this.gptPrompt}
+
+ { + this.setGPTText(); + }}> +
{this.gptRes}
+
+
+ )} {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} {this.audioHandle} diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 8f9261614..04904b3b1 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,7 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; -import { gptSummarize } from '../../apis/gpt/Summarization'; +import { gptAPICall, GPTCallType } from '../../apis/gpt/Summarization'; import { GPTPopup } from './GPTPopup'; import './AnchorMenu.scss'; @@ -136,16 +136,17 @@ export class AnchorMenu extends AntimodeMenu { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - invokeGPT = async (e: React.PointerEvent) => { + gptSummarize = async (e: React.PointerEvent) => { this.setGPTPopupVis(true); this.setLoading(true); - const res = await gptSummarize(this.selectedText); + const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); // const res = await this.mockSummarize(); if (res) { this.setSummarizedText(res); } else { this.setSummarizedText('Something went wrong.'); } + this.setLoading(false); }; @@ -243,6 +244,19 @@ export class AnchorMenu extends AntimodeMenu { this.highlightColor = Utils.colorString(col); }; + /** + * Returns whether the selected text can be summarized. The goal is to have + * all selected text available to summarize but its only supported for pdf and web ATM. + * @returns Whether the GPT icon for summarization should appear + */ + canSummarize = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs[0].type === 'pdf' || docs[0].type === 'web'; + } + return false; + }; + render() { const buttons = this.Status === 'marquee' ? ( @@ -254,14 +268,14 @@ export class AnchorMenu extends AntimodeMenu { {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} - {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && ( + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( Summarize with AI
}> - )} - + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation}> - - - ); - } else { - return
; + const width = NumCast(this.props.rootDoc._width) * 0.9; + const height = this.props.height * 0.9; + const margin = { top: 10, right: 50, bottom: 50, left: 50 }; + return ; } + return
; } } diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts index 494242ac5..8ebcc2a5f 100644 --- a/src/client/views/nodes/DataVizBox/ChartInterface.ts +++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts @@ -1,15 +1,15 @@ import { Doc } from '../../../../fields/Doc'; +import { PinProps } from '../trails'; import { DataPoint } from './ChartBox'; -import { LineChart } from './components/LineChart'; export interface Chart { tooltipContent: (data: DataPoint) => string; drawChart: () => void; + restoreView: (data: any) => boolean; height: number; width: number; // TODO: nda - probably want to get rid of this to keep charts more generic - _getAnchor: () => Doc; - setCurrSelected: (x: number, y: number) => void; + _getAnchor: (pinProps?: PinProps) => Doc; } export interface ChartProps { diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index e69de29bb..cd500e9ae 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss @@ -0,0 +1,4 @@ +.dataviz { + overflow: auto; + height: 100%; +} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index db677ff61..68766e8dc 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; -import { Cast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; @@ -10,6 +10,7 @@ import { FieldViewProps, FieldView } from '../FieldView'; import { ChartBox } from './ChartBox'; import './DataVizBox.scss'; import { TableBox } from './components/TableBox'; +import { PinProps } from '../trails'; enum DataVizView { TABLE = 'table', @@ -54,40 +55,23 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { }; @computed get currView() { - if (this.rootDoc._dataVizView) { - return StrCast(this.rootDoc._dataVizView); - } else { - return 'table'; - } + return StrCast(this.rootDoc._dataVizView, 'table'); } - scrollFocus = (doc: Doc, smooth: boolean) => { - // reconstruct the view based on the anchor -> - // keep track of the datavizbox view and the type of chart we're rendering - - // use dataview to restore part of it and then pass on the rest to the chartbox - - // pseudocode - return this._chartBox?.scrollFocus(doc, smooth); + @action + restoreView = (data: Doc) => { + const changed = this.currView !== data.dataView && (this.rootDoc._dataVizView = data.dataView); + const func = () => this._chartBox?.restoreView(data); + if (changed) { + setTimeout(func); + return changed ? true : false; + } + return func() ?? false; }; - getAnchor = () => { - // TODO: nda - look into DocumentButtonBar and DocumentLinksButton - - /* - if nothing selected: - return this.rootDoc - this part does not specify view type (doesn't change type when following the link) - - // this slightly diff than the stuff below: - below part returns an anchor that specifies the viewtype for the whole doc and nothing else - - */ - - // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) + getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { const anchor = - // this._COMPONENTNAME._getAnchor() ?? - this._chartBox?._getAnchor() ?? + this._chartBox?.getAnchor(pinProps) ?? Docs.Create.TextanchorDocument({ // 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) @@ -98,7 +82,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { this.addDocument(anchor); return anchor; - // have some other function in code that }; constructor(props: any) { super(props); @@ -117,7 +100,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { case 'table': return ; case 'histogram': - return ; + return ; // case "histogram": // return () } @@ -152,7 +135,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table'; + this.rootDoc._dataVizView = this.currView === 'table' ? 'histogram' : 'table'; } render() { @@ -161,7 +144,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } return ( -
+
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 } + ) + }> {this.selectView}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 7792a2758..2d6c5f0f2 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -1,10 +1,15 @@ .tooltip { // make the height width bigger - width: 50px; - height: 50px; + width: fit-content; + height: fit-content; } .highlight { // change the color of the circle element to be red fill: red; } +.chart-container { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index d893b3e12..e5f7dc4f4 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -9,6 +9,10 @@ import { Docs } from '../../../../documents/Documents'; import { Doc, DocListCast } from '../../../../../fields/Doc'; import { Chart, ChartProps } from '../ChartInterface'; import './Chart.scss'; +import { List } from '../../../../../fields/List'; +import { PinProps, PresBox } from '../../trails'; +import { listSpec } from '../../../../../fields/Schema'; +import { Cast } from '../../../../../fields/Types'; type minMaxRange = { xMin: number | undefined; @@ -103,21 +107,25 @@ export class LineChart extends React.Component implements Chart { // loop through and remove any annotations that no longer exist } - _getAnchor() { + restoreView = (data: Doc) => { + const coords = Cast(data.presDataViz, listSpec('number'), null); + if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) { + this.setCurrSelected(coords[0], coords[1]); + return true; + } + return false; + }; + _getAnchor = (pinProps?: PinProps) => { // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) - // TODO: nda - look at pdfviewer get anchor for args - const doc = Docs.Create.TextanchorDocument({ + const anchor = Docs.Create.TextanchorDocument({ /*put in some options*/ title: 'line doc selection' + this._currSelected?.x, }); - // access curr selected from the charts - doc.x = this._currSelected?.x; - doc.y = this._currSelected?.y; - doc.chartType = 'line'; - return doc; + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc); + return anchor; // have some other function in code that - } + }; componentWillUnmount() { if (this._dataReactionDisposer) { @@ -132,7 +140,7 @@ export class LineChart extends React.Component implements Chart { } tooltipContent(data: DataPoint) { - return `x: ${data.x} y: ${data.y}`; + return `(${data.x},${data.y})`; } @computed get height(): number { @@ -235,7 +243,7 @@ export class LineChart extends React.Component implements Chart { const mousemove = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos)); + const x0 = bisect(data, xScale.invert(xPos - 25)); const d0 = data[x0]; this._x = d0.x; this._y = d0.y; @@ -243,23 +251,22 @@ export class LineChart extends React.Component implements Chart { // TODO: nda - implement tooltips tooltip.transition().duration(300).style('opacity', 0.9); // TODO: nda - updating the inner html could be deadly cause injection attacks! - tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`); + tooltip + .html(() => this.tooltipContent(d0)) + .style('pointer-events', 'none') + .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`); }); const onPointClick = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos)); + const x0 = bisect(data, xScale.invert(xPos - 25)); const d0 = data[x0]; this._x = d0.x; this._y = d0.y; // find .circle-d1 with data-x = d0.x and data-y = d0.y const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y); this._currSelected = { x: d0.x, y: d0.y, elem: selected }; - console.log('Getting here'); - // this.drawAnnotations(this._x, this._y); - // this.props.getAnchor(); - console.log(this._currSelected); }); this._chartSvg diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0e0e22b84..e24bf35f5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -108,6 +108,7 @@ export type StyleProviderFunc = (doc: Opt, props: Opt, p export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) + restoreView?: (viewSpec: Doc) => boolean; scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; getView?: (doc: Doc) => Promise>; // returns a nested DocumentView for the specified doc or undefined diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index bfaae8069..0e22ea3b1 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -39,6 +39,7 @@ const { Howl } = require('howler'); export interface pinDataTypes { scrollable?: boolean; + dataviz?: number[]; pannable?: boolean; viewType?: boolean; inkable?: boolean; @@ -491,6 +492,11 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } + if (!pinDataTypes && activeItem.presDataViz !== undefined) { + if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { + changed = true; + } + } if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { if (bestTarget._scrollTop !== activeItem.presViewScroll) { @@ -648,6 +654,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; + if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List(pinProps.pinData.dataviz); if (pinProps.pinData.pannable) { pinDoc.presPanX = NumCast(targetDoc._panX); pinDoc.presPanY = NumCast(targetDoc._panY); diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts index 2528fb1ab..15f03b319 100644 --- a/src/server/DataVizUtils.ts +++ b/src/server/DataVizUtils.ts @@ -1,19 +1,17 @@ -import { readFileSync } from "fs"; +import { readFileSync } from 'fs'; export function csvParser(csv: string) { - const lines = csv.split("\n"); - const headers = lines[0].split(","); - const data = lines.slice(1).map(line => { - const values = line.split(","); - const obj: any = {}; - for (let i = 0; i < headers.length; i++) { - obj[headers[i]] = values[i]; - } - return obj; - }); + const lines = csv.split('\n'); + const headers = lines[0].split(',').map(header => header.trim()); + const data = lines.slice(1).map(line => + line.split(',').reduce((last, value, i) => { + last[headers[i]] = value.trim(); + return last; + }, {} as any) + ); return data; } export function csvToString(path: string) { return readFileSync(path, 'utf8'); -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From a8343cad405a146fdff8fc2d66ef41fdaefb8bda Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 6 Apr 2023 00:14:24 -0400 Subject: more simplification/cleanup of dataviz --- src/client/views/nodes/DataVizBox/ChartBox.tsx | 100 ----------- .../views/nodes/DataVizBox/ChartInterface.ts | 36 ---- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 84 ++++----- .../nodes/DataVizBox/components/LineChart.tsx | 187 ++++++++++----------- .../views/nodes/DataVizBox/components/TableBox.tsx | 31 ++-- src/client/views/nodes/DataVizBox/utils/D3Utils.ts | 2 +- 6 files changed, 133 insertions(+), 307 deletions(-) delete mode 100644 src/client/views/nodes/DataVizBox/ChartBox.tsx delete mode 100644 src/client/views/nodes/DataVizBox/ChartInterface.ts (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx deleted file mode 100644 index ee577b9fd..000000000 --- a/src/client/views/nodes/DataVizBox/ChartBox.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc } from '../../../../fields/Doc'; -import { action, computed, observable } from 'mobx'; -import { NumCast, StrCast } from '../../../../fields/Types'; -import { LineChart } from './components/LineChart'; -import { Chart, ChartData } from './ChartInterface'; -import { PinProps } from '../trails'; - -export interface ChartBoxProps { - rootDoc: Doc; - dataDoc: Doc; - height: number; - pairs: { x: number; y: number }[]; - setChartBox: (chartBox: ChartBox) => void; - getAnchor: () => Doc; -} - -export interface DataPoint { - x: number; - y: number; -} - -@observer -export class ChartBox extends React.Component { - @observable private _chartData: ChartData | undefined = undefined; - private currChart: Chart | undefined; - - @computed get currView() { - if (this.props.rootDoc._dataVizView) { - return StrCast(this.props.rootDoc._currChartView); - } else { - return 'table'; - } - } - - constructor(props: any) { - super(props); - if (!this.props.rootDoc._currChartView) { - this.props.rootDoc._currChartView = 'bar'; - } - } - - setCurrChart = (chart: Chart) => { - this.currChart = chart; - }; - - @action - generateChartData() { - if (this.props.rootDoc._chartData) { - this._chartData = JSON.parse(StrCast(this.props.rootDoc._chartData)); - return; - } - - const data: ChartData = { - xLabel: '', - yLabel: '', - data: [[]], - }; - - if (this.props.pairs && this.props.pairs.length > 0) { - data.xLabel = 'x'; - data.yLabel = 'y'; - this.props.pairs.forEach(pair => { - // TODO: nda - add actual support for multiple sets of data - data.data[0].push({ x: pair.x, y: pair.y }); - }); - } - - this._chartData = data; - this.props.rootDoc._chartData = JSON.stringify(data); - } - - componentDidMount = () => { - this.generateChartData(); - this.props.setChartBox(this); - }; - - @action - onClickChangeChart = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - console.log(e.currentTarget.value); - this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase(); - }; - - getAnchor = (pinProps?: PinProps) => this.currChart?._getAnchor(pinProps); - - restoreView = (data: Doc) => this.currChart?.restoreView(data); - - render() { - if (this.props.pairs && this._chartData) { - const width = NumCast(this.props.rootDoc._width) * 0.9; - const height = this.props.height * 0.9; - const margin = { top: 10, right: 50, bottom: 50, left: 50 }; - return ; - } - return
; - } -} diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts deleted file mode 100644 index 8ebcc2a5f..000000000 --- a/src/client/views/nodes/DataVizBox/ChartInterface.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Doc } from '../../../../fields/Doc'; -import { PinProps } from '../trails'; -import { DataPoint } from './ChartBox'; - -export interface Chart { - tooltipContent: (data: DataPoint) => string; - drawChart: () => void; - restoreView: (data: any) => boolean; - height: number; - width: number; - // TODO: nda - probably want to get rid of this to keep charts more generic - _getAnchor: (pinProps?: PinProps) => Doc; -} - -export interface ChartProps { - chartData: ChartData; - width: number; - height: number; - dataDoc: Doc; - fieldKey: string; - // returns linechart component but should be generic chart - setCurrChart: (chart: Chart) => void; - getAnchor: () => Doc; - margin: { - top: number; - right: number; - bottom: number; - left: number; - }; -} - -export interface ChartData { - xLabel: string; - yLabel: string; - data: DataPoint[][]; -} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 68766e8dc..e279a1262 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -2,29 +2,32 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; -import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; -import { FieldViewProps, FieldView } from '../FieldView'; -import { ChartBox } from './ChartBox'; -import './DataVizBox.scss'; -import { TableBox } from './components/TableBox'; +import { FieldView, FieldViewProps } from '../FieldView'; import { PinProps } from '../trails'; +import { LineChart } from './components/LineChart'; +import { TableBox } from './components/TableBox'; +import './DataVizBox.scss'; enum DataVizView { TABLE = 'table', - HISTOGRAM = 'histogram', + LINECHART = 'lineChart', } @observer export class DataVizBox extends ViewBoxAnnotatableComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataVizBox, fieldKey); + } // says we have an object and any string // 2 ways of doing it // @observable private pairs: { [key: string]: number | string | undefined }[] = []; // @observable private pairs: { [key: string]: FieldResult }[] = []; @observable private pairs: { x: number; y: number }[] = []; - private _chartBox: ChartBox | undefined; + private _chartRenderer: LineChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc // method1() { @@ -45,25 +48,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // [key: string]: FieldResult; // instead of numeric x,y in there, - // TODO: nda - make this use enum values instead - // @observable private currView: DataVizView = DataVizView.TABLE; - // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops - setChartBox = (chartBox: ChartBox) => { - this._chartBox = chartBox; - }; - - @computed get currView() { - return StrCast(this.rootDoc._dataVizView, 'table'); + @computed get dataVizView(): DataVizView { + return StrCast(this.rootDoc._dataVizView, 'table') as DataVizView; } @action restoreView = (data: Doc) => { - const changed = this.currView !== data.dataView && (this.rootDoc._dataVizView = data.dataView); - const func = () => this._chartBox?.restoreView(data); + const changed = this.dataVizView !== data.presDataVizView && (this.rootDoc._dataVizView = data.presDataVizView); + const func = () => this._chartRenderer?.restoreView(data); if (changed) { - setTimeout(func); + setTimeout(func, 100); return changed ? true : false; } return func() ?? false; @@ -71,38 +67,27 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { const anchor = - this._chartBox?.getAnchor(pinProps) ?? + this._chartRenderer?.getAnchor(pinProps) ?? Docs.Create.TextanchorDocument({ // 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*/ }); - anchor.dataView = this.currView; + anchor.presDataVizView = this.dataVizView; this.addDocument(anchor); return anchor; }; - constructor(props: any) { - super(props); - if (!this.rootDoc._dataVizView) { - // TODO: nda - this might not always want to default to "table" - this.rootDoc._dataVizView = 'table'; - } - } - - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DataVizBox, fieldKey); - } @computed get selectView() { - switch (this.currView) { - case 'table': - return ; - case 'histogram': - return ; - // case "histogram": - // return () + const width = NumCast(this.rootDoc._width) * 0.9; + const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; + const margin = { top: 10, right: 50, bottom: 50, left: 50 }; + // prettier-ignore + switch (this.dataVizView) { + case DataVizView.TABLE: return ; + case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />; } } @@ -116,34 +101,25 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { componentDidMount() { this.props.setContentView?.(this); - // this.createPairs(); this.fetchData(); } fetchData() { - const uri = this.dataUrl?.url.href; - fetch('/csvData?uri=' + uri).then(res => - res.json().then( - action(res => { - this.pairs = res; - }) - ) - ); + fetch('/csvData?uri=' + this.dataUrl?.url.href) // + .then(res => res.json().then(action(res => !res.errno && (this.pairs = res)))); } // handle changing the view using a button @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.currView === 'table' ? 'histogram' : 'table'; + this.rootDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; } render() { - if (this.pairs.length == 0) { - return
Loading...
; - } - - return ( + return this.pairs.length == 0 ? ( +
Loading...
+ ) : (
e.stopPropagation()} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index e5f7dc4f4..313164691 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -1,18 +1,15 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DataPoint } from '../ChartBox'; // import d3 import * as d3 from 'd3'; -import { minMaxRange, createLineGenerator, xGrid, yGrid, drawLine, xAxisCreator, yAxisCreator, scaleCreatorNumerical, scaleCreatorCategorical } from '../utils/D3Utils'; -import { Docs } from '../../../../documents/Documents'; import { Doc, DocListCast } from '../../../../../fields/Doc'; -import { Chart, ChartProps } from '../ChartInterface'; -import './Chart.scss'; -import { List } from '../../../../../fields/List'; -import { PinProps, PresBox } from '../../trails'; import { listSpec } from '../../../../../fields/Schema'; import { Cast } from '../../../../../fields/Types'; +import { Docs } from '../../../../documents/Documents'; +import { PinProps, PresBox } from '../../trails'; +import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils'; +import './Chart.scss'; type minMaxRange = { xMin: number | undefined; @@ -21,22 +18,43 @@ type minMaxRange = { yMax: number | undefined; }; -interface SelectedDataPoint { +export interface DataPoint { x: number; y: number; +} +interface SelectedDataPoint extends DataPoint { elem?: d3.Selection; } +export interface LineChartData { + xLabel: string; + yLabel: string; + dataSet: DataPoint[][]; +} +export interface LineChartProps { + rootDoc: Doc; + pairs: { x: number; y: number }[]; + width: number; + height: number; + dataDoc: Doc; + fieldKey: string; + margin: { + top: number; + right: number; + bottom: number; + left: number; + }; +} @observer -export class LineChart extends React.Component implements Chart { - private _dataReactionDisposer: IReactionDisposer | undefined = undefined; - private _heightReactionDisposer: IReactionDisposer | undefined = undefined; - private _widthReactionDisposer: IReactionDisposer | undefined; +export class LineChart extends React.Component { + private _disposers: { [key: string]: IReactionDisposer } = {}; @observable private _x: number = 0; @observable private _y: number = 0; @observable private _currSelected: SelectedDataPoint | undefined = undefined; + @observable private _lineChartData: LineChartData | undefined = undefined; // create ref for the div - private _chartRef: React.RefObject = React.createRef(); + private _lineChartRef: React.RefObject = React.createRef(); + private _lineChartSvg: d3.Selection | undefined; private _rangeVals: minMaxRange = { xMin: undefined, xMax: undefined, @@ -45,43 +63,47 @@ export class LineChart extends React.Component implements Chart { }; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates - private _chartSvg: d3.Selection | undefined; - - // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that - - // write the getanchor function that gets whatever I want as the link anchor - + componentWillUnmount() { + Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); + } componentDidMount = () => { - this._dataReactionDisposer = reaction( - () => this.props.chartData, - chartData => { - this._rangeVals = minMaxRange(chartData.data); - this.drawChart(); + this._disposers.chartData = reaction( + () => ({ dataSet: this._lineChartData?.dataSet, w: this.props.width, h: this.props.height }), + vals => { + if (vals.dataSet) { + this._rangeVals = minMaxRange(vals.dataSet); + this.drawChart(vals.dataSet); + } }, { fireImmediately: true } ); - // DocumentDecorations.Instance.Interacting - this._heightReactionDisposer = reaction(() => this.props.height, this.drawChart.bind(this)); - this._widthReactionDisposer = reaction(() => this.props.width, this.drawChart.bind(this)); - reaction( + this._disposers.annos = reaction( () => DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), annotations => { // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way // could be blue colored to make it look like anchor - console.log(annotations); // this.drawAnnotations() // loop through annotations and draw them - annotations.forEach(a => { - this.drawAnnotations(Number(a.x), Number(a.y)); - }); + annotations.forEach(a => this.drawAnnotations(Number(a.x), Number(a.y))); // this.drawAnnotations(annotations.x, annotations.y); }, { fireImmediately: true } ); - - this.props.setCurrChart(this); + this.generateChartData(); }; + @action + generateChartData() { + this._lineChartData = { + xLabel: 'x', + yLabel: 'y', + // TODO: nda - add actual support for multiple sets of data + dataSet: [this.props.pairs?.map(pair => ({ x: pair.x, y: pair.y }))], + }; + } + + // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that + // gets called whenever the "data-annotations" fields gets updated drawAnnotations(dataX: number, dataY: number) { // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements @@ -115,45 +137,27 @@ export class LineChart extends React.Component implements Chart { } return false; }; - _getAnchor = (pinProps?: PinProps) => { - // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element) + // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) + getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.TextanchorDocument({ - /*put in some options*/ title: 'line doc selection' + this._currSelected?.x, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc); return anchor; - // have some other function in code that }; - componentWillUnmount() { - if (this._dataReactionDisposer) { - this._dataReactionDisposer(); - } - if (this._heightReactionDisposer) { - this._heightReactionDisposer(); - } - if (this._widthReactionDisposer) { - this._widthReactionDisposer(); - } - } - - tooltipContent(data: DataPoint) { - return `(${data.x},${data.y})`; - } - - @computed get height(): number { + @computed get height() { return this.props.height - this.props.margin.top - this.props.margin.bottom; } - @computed get width(): number { + @computed get width() { return this.props.width - this.props.margin.left - this.props.margin.right; } setupTooltip() { const tooltip = d3 - .select(this._chartRef.current) + .select(this._lineChartRef.current) .append('div') .attr('class', 'tooltip') .style('opacity', 0) @@ -169,13 +173,13 @@ export class LineChart extends React.Component implements Chart { @action setCurrSelected(x: number, y: number) { // TODO: nda - get rid of svg element in the list? - this._currSelected = { x: x, y: y }; + this._currSelected = { x, y }; } drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear, yScale: d3.ScaleLinear) { - if (this._chartSvg) { + if (this._lineChartSvg) { const circleClass = '.circle-' + idx; - this._chartSvg + this._lineChartSvg .selectAll(circleClass) .data(data) .join('circle') // enter append @@ -189,30 +193,13 @@ export class LineChart extends React.Component implements Chart { } // TODO: nda - can use d3.create() to create html element instead of appending - drawChart() { - const { chartData, margin } = this.props; - const data = chartData.data[0]; + drawChart = (dataSet: DataPoint[][]) => { // clearing tooltip and the current chart - d3.select(this._chartRef.current).select('svg').remove(); - d3.select(this._chartRef.current).select('.tooltip').remove(); - - // TODO: nda - refactor code so that it only recalculates min max and things related to data on data update + d3.select(this._lineChartRef.current).select('svg').remove(); + d3.select(this._lineChartRef.current).select('.tooltip').remove(); const { xMin, xMax, yMin, yMax } = this._rangeVals; - // const svg = d3.select(this._chartRef.current).append(this.svgContainer.html()); - // adding svg - this._chartSvg = d3 - .select(this._chartRef.current) - .append('svg') - .attr('width', `${this.width + margin.right + margin.left}`) - .attr('height', `${this.height + margin.top + margin.bottom}`) - .append('g') - .attr('transform', `translate(${margin.left}, ${margin.top})`); - - const svg = this._chartSvg; - - if (xMin == undefined || xMax == undefined || yMin == undefined || yMax == undefined) { - // TODO: nda - error handle + if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) { return; } @@ -220,9 +207,15 @@ export class LineChart extends React.Component implements Chart { const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width); const yScale = scaleCreatorNumerical(0, yMax, this.height, 0); - // create a line function that takes in the data.data.x and data.data.y - // TODO: nda - fix the types for the d here - const lineGen = createLineGenerator(xScale, yScale); + // adding svg + const margin = this.props.margin; + const svg = (this._lineChartSvg = d3 + .select(this._lineChartRef.current) + .append('svg') + .attr('width', `${this.width + margin.right + margin.left}`) + .attr('height', `${this.height + margin.top + margin.bottom}`) + .append('g') + .attr('transform', `translate(${margin.left}, ${margin.top})`)); // create x and y grids xGrid(svg.append('g'), this.height, xScale); @@ -230,7 +223,9 @@ export class LineChart extends React.Component implements Chart { xAxisCreator(svg.append('g'), this.height, xScale); yAxisCreator(svg.append('g'), this.width, yScale); - // draw the line + // draw the plot line + const data = dataSet[0]; + const lineGen = createLineGenerator(xScale, yScale); drawLine(svg.append('path'), data, lineGen); // draw the datapoint circle @@ -243,16 +238,15 @@ export class LineChart extends React.Component implements Chart { const mousemove = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos - 25)); + const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis const d0 = data[x0]; this._x = d0.x; this._y = d0.y; focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`); - // TODO: nda - implement tooltips tooltip.transition().duration(300).style('opacity', 0.9); // TODO: nda - updating the inner html could be deadly cause injection attacks! tooltip - .html(() => this.tooltipContent(d0)) + .html(() => `(${d0.x},${d0.y})`) // text content for tooltip .style('pointer-events', 'none') .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`); }); @@ -260,7 +254,7 @@ export class LineChart extends React.Component implements Chart { const onPointClick = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos - 25)); + const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis const d0 = data[x0]; this._x = d0.x; this._y = d0.y; @@ -269,27 +263,22 @@ export class LineChart extends React.Component implements Chart { this._currSelected = { x: d0.x, y: d0.y, elem: selected }; }); - this._chartSvg - .append('rect') + svg.append('rect') .attr('class', 'overlay') .attr('width', this.width) .attr('height', this.height + margin.top + margin.bottom) .attr('fill', 'none') .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`) .style('opacity', 0) - .on('mouseover', () => { - focus.style('display', null); - }) - .on('mouseout', () => { - tooltip.transition().duration(300).style('opacity', 0); - }) + .on('mouseover', () => focus.style('display', null)) + .on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0)) .on('mousemove', mousemove) .on('click', onPointClick); - } + }; render() { return ( -
+
Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'}
); diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index dfa8262d8..28114036f 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,16 +1,12 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; interface TableBoxProps { - pairs: {x: number, y:number}[] + pairs: { x: number; y: number }[]; } - export class TableBox extends React.Component { - - - render() { return (
@@ -22,16 +18,17 @@ export class TableBox extends React.Component { - {this.props.pairs.map(p => { - return ( - {p.x} - {p.y} - ) - })} + {this.props.pairs?.map(p => { + return ( + + {p.x} + {p.y} + + ); + })}
- ) + ); } - -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index 2bb091999..e1ff6f8eb 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -1,5 +1,5 @@ import * as d3 from 'd3'; -import { DataPoint } from '../ChartBox'; +import { DataPoint } from '../components/LineChart'; // TODO: nda - implement function that can handle range for strings -- cgit v1.2.3-70-g09d2 From 7975e3f4aaa3601a5512a7dce4c261e7f9411374 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 6 Apr 2023 10:01:39 -0400 Subject: added selection ui for choosing linechart axes. --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 31 +++++++++------- .../nodes/DataVizBox/components/LineChart.tsx | 33 ++++++++++++----- .../views/nodes/DataVizBox/components/TableBox.tsx | 43 +++++++++++++++++++--- src/client/views/nodes/trails/PresBox.tsx | 7 +--- 4 files changed, 79 insertions(+), 35 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index e279a1262..0ce589a13 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,7 +1,8 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Docs } from '../../../documents/Documents'; @@ -26,7 +27,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // 2 ways of doing it // @observable private pairs: { [key: string]: number | string | undefined }[] = []; // @observable private pairs: { [key: string]: FieldResult }[] = []; - @observable private pairs: { x: number; y: number }[] = []; + @observable private pairs: { [key: string]: string }[] = []; private _chartRenderer: LineChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc @@ -51,16 +52,17 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // TODO: nda - use onmousedown and onmouseup when dragging and changing height and width to update the height and width props only when dragging stops @computed get dataVizView(): DataVizView { - return StrCast(this.rootDoc._dataVizView, 'table') as DataVizView; + return StrCast(this.layoutDoc._dataVizView, 'table') as DataVizView; } @action restoreView = (data: Doc) => { - const changed = this.dataVizView !== data.presDataVizView && (this.rootDoc._dataVizView = data.presDataVizView); + const changedView = this.dataVizView !== data.presDataVizView && (this.layoutDoc._dataVizView = data.presDataVizView); + const changedAxes = this.axes.join('') !== StrListCast(data.presDataVizAxes).join('') && (this.layoutDoc._dataVizAxes = new List(StrListCast(data.presDataVizAxes))); const func = () => this._chartRenderer?.restoreView(data); - if (changed) { + if (changedView || changedAxes) { setTimeout(func, 100); - return changed ? true : false; + return true; } return func() ?? false; }; @@ -75,26 +77,27 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { }); anchor.presDataVizView = this.dataVizView; + anchor.presDataVizAxes = this.axes.length ? new List(this.axes) : undefined; this.addDocument(anchor); return anchor; }; + @computed.struct get axes() { + return StrListCast(this.layoutDoc.dataVizAxes); + } + selectAxes = (axes: string[]) => (this.layoutDoc.dataVizAxes = new List(axes)); + @computed get selectView() { const width = NumCast(this.rootDoc._width) * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 50, bottom: 50, left: 50 }; // prettier-ignore switch (this.dataVizView) { - case DataVizView.TABLE: return ; - case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />; + case DataVizView.TABLE: return ; + case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; } } - - @computed get pairVals() { - return fetch('/csvData?uri=' + this.dataUrl?.url.href).then(res => res.json()); - } - @computed get dataUrl() { return Cast(this.dataDoc[this.fieldKey], CsvField); } @@ -113,7 +116,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @action changeViewHandler(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); - this.rootDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; + this.layoutDoc._dataVizView = this.dataVizView === DataVizView.TABLE ? DataVizView.LINECHART : DataVizView.TABLE; } render() { diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 313164691..2357b7c69 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; // import d3 import * as d3 from 'd3'; import { Doc, DocListCast } from '../../../../../fields/Doc'; +import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; @@ -32,7 +33,8 @@ export interface LineChartData { } export interface LineChartProps { rootDoc: Doc; - pairs: { x: number; y: number }[]; + axes: string[]; + pairs: { [key: string]: any }[]; width: number; height: number; dataDoc: Doc; @@ -67,8 +69,13 @@ export class LineChart extends React.Component { Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); } componentDidMount = () => { + this._disposers.chartdata = reaction( + () => this.props.axes.slice(), + axes => axes.length > 1 && this.generateChartData(), + { fireImmediately: true } + ); this._disposers.chartData = reaction( - () => ({ dataSet: this._lineChartData?.dataSet, w: this.props.width, h: this.props.height }), + () => ({ dataSet: this._lineChartData?.dataSet, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }), vals => { if (vals.dataSet) { this._rangeVals = minMaxRange(vals.dataSet); @@ -89,16 +96,15 @@ export class LineChart extends React.Component { }, { fireImmediately: true } ); - this.generateChartData(); }; @action generateChartData() { this._lineChartData = { - xLabel: 'x', - yLabel: 'y', + xLabel: this.props.axes[0], + yLabel: this.props.axes[1], // TODO: nda - add actual support for multiple sets of data - dataSet: [this.props.pairs?.map(pair => ({ x: pair.x, y: pair.y }))], + dataSet: [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))], }; } @@ -129,12 +135,17 @@ export class LineChart extends React.Component { // loop through and remove any annotations that no longer exist } + @action restoreView = (data: Doc) => { - const coords = Cast(data.presDataViz, listSpec('number'), null); - if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) { + const coords = Cast(data.presDataVizSelection, listSpec('number'), null); + if (coords?.length > 1 && (this._currSelected?.x !== coords[0] || this._currSelected?.y !== coords[1])) { this.setCurrSelected(coords[0], coords[1]); return true; } + if (this._currSelected) { + this._currSelected = undefined; + return true; + } return false; }; @@ -143,7 +154,8 @@ export class LineChart extends React.Component { const anchor = Docs.Create.TextanchorDocument({ title: 'line doc selection' + this._currSelected?.x, }); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); + anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected.x, this._currSelected.y]) : undefined; return anchor; }; @@ -277,9 +289,10 @@ export class LineChart extends React.Component { }; render() { + const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return (
- Curr Selected: {this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'} + {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Curr Selected: ${selectedPt}`}
); } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 28114036f..adefe90cd 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,28 +1,59 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../../Utils'; interface TableBoxProps { - pairs: { x: number; y: number }[]; + pairs: { [key: string]: any }[]; + selectAxes: (axes: string[]) => void; + axes: string[]; } +@observer export class TableBox extends React.Component { + @computed get columns() { + return this.props.pairs.length ? Array.from(Object.keys(this.props.pairs[0])) : []; + } render() { return (
- - + {this.columns.map(col => ( + + ))} {this.props.pairs?.map(p => { return ( - - + {this.columns.map(col => ( + + ))} ); })} diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0e22ea3b1..3589a9065 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -492,10 +492,8 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } - if (!pinDataTypes && activeItem.presDataViz !== undefined) { - if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { - changed = true; - } + if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) { + changed = true; } if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { @@ -654,7 +652,6 @@ export class PresBox extends ViewBoxBaseComponent() { if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; - if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List(pinProps.pinData.dataviz); if (pinProps.pinData.pannable) { pinDoc.presPanX = NumCast(targetDoc._panX); pinDoc.presPanY = NumCast(targetDoc._panY); -- cgit v1.2.3-70-g09d2 From e9633592cdd11df4d4d240dc42c254f60d16b572 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 6 Apr 2023 11:57:09 -0400 Subject: more cleanup of lineChar --- .../nodes/DataVizBox/components/LineChart.tsx | 46 ++++------------------ 1 file changed, 7 insertions(+), 39 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 2357b7c69..e6a06a454 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -12,13 +12,6 @@ import { PinProps, PresBox } from '../../trails'; import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils'; import './Chart.scss'; -type minMaxRange = { - xMin: number | undefined; - xMax: number | undefined; - yMin: number | undefined; - yMax: number | undefined; -}; - export interface DataPoint { x: number; y: number; @@ -26,11 +19,6 @@ export interface DataPoint { interface SelectedDataPoint extends DataPoint { elem?: d3.Selection; } -export interface LineChartData { - xLabel: string; - yLabel: string; - dataSet: DataPoint[][]; -} export interface LineChartProps { rootDoc: Doc; axes: string[]; @@ -50,19 +38,11 @@ export interface LineChartProps { @observer export class LineChart extends React.Component { private _disposers: { [key: string]: IReactionDisposer } = {}; - @observable private _x: number = 0; - @observable private _y: number = 0; - @observable private _currSelected: SelectedDataPoint | undefined = undefined; - @observable private _lineChartData: LineChartData | undefined = undefined; - // create ref for the div private _lineChartRef: React.RefObject = React.createRef(); private _lineChartSvg: d3.Selection | undefined; - private _rangeVals: minMaxRange = { - xMin: undefined, - xMax: undefined, - yMin: undefined, - yMax: undefined, - }; + private _rangeVals: { xMin?: number; xMax?: number;yMin?: number;yMax?: number;}= {}; + @observable _currSelected: SelectedDataPoint | undefined = undefined; + @observable _lineChartData: DataPoint[][] | undefined = undefined; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates componentWillUnmount() { @@ -71,11 +51,13 @@ export class LineChart extends React.Component { componentDidMount = () => { this._disposers.chartdata = reaction( () => this.props.axes.slice(), - axes => axes.length > 1 && this.generateChartData(), + axes => {if (axes.length > 1) { + this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a,b) => a.x < b.x ? 1:-1)] + }}, { fireImmediately: true } ); this._disposers.chartData = reaction( - () => ({ dataSet: this._lineChartData?.dataSet, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }), + () => ({ dataSet: this._lineChartData, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }), vals => { if (vals.dataSet) { this._rangeVals = minMaxRange(vals.dataSet); @@ -98,16 +80,6 @@ export class LineChart extends React.Component { ); }; - @action - generateChartData() { - this._lineChartData = { - xLabel: this.props.axes[0], - yLabel: this.props.axes[1], - // TODO: nda - add actual support for multiple sets of data - dataSet: [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) }))], - }; - } - // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that // gets called whenever the "data-annotations" fields gets updated @@ -252,8 +224,6 @@ export class LineChart extends React.Component { const xPos = d3.pointer(e)[0]; const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis const d0 = data[x0]; - this._x = d0.x; - this._y = d0.y; focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`); tooltip.transition().duration(300).style('opacity', 0.9); // TODO: nda - updating the inner html could be deadly cause injection attacks! @@ -268,8 +238,6 @@ export class LineChart extends React.Component { const xPos = d3.pointer(e)[0]; const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis const d0 = data[x0]; - this._x = d0.x; - this._y = d0.y; // find .circle-d1 with data-x = d0.x and data-y = d0.y const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y); this._currSelected = { x: d0.x, y: d0.y, elem: selected }; -- cgit v1.2.3-70-g09d2 From 986c5bdc899954c4609f71405d3334147be721fe Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 7 Apr 2023 10:42:16 -0400 Subject: experimetnal changes to datavizbox to allow brushing data items and better highlighting of selections. Also working on drawing link lines between chart aliases. --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 11 ++- .../views/nodes/DataVizBox/components/Chart.scss | 20 ++++ .../nodes/DataVizBox/components/LineChart.tsx | 105 +++++++++++++++------ .../views/nodes/DataVizBox/components/TableBox.tsx | 98 +++++++++++++------ src/fields/Doc.ts | 1 + 5 files changed, 170 insertions(+), 65 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 0ce589a13..aaa8c3c53 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -13,7 +13,7 @@ import { LineChart } from './components/LineChart'; import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; -enum DataVizView { +export enum DataVizView { TABLE = 'table', LINECHART = 'lineChart', } @@ -27,7 +27,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { // 2 ways of doing it // @observable private pairs: { [key: string]: number | string | undefined }[] = []; // @observable private pairs: { [key: string]: FieldResult }[] = []; - @observable private pairs: { [key: string]: string }[] = []; + @observable pairs: { [key: string]: string }[] = []; private _chartRenderer: LineChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc @@ -71,6 +71,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { const anchor = this._chartRenderer?.getAnchor(pinProps) ?? Docs.Create.TextanchorDocument({ + unrendered: true, // 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*/ @@ -89,12 +90,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { selectAxes = (axes: string[]) => (this.layoutDoc.dataVizAxes = new List(axes)); @computed get selectView() { - const width = NumCast(this.rootDoc._width) * 0.9; + const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 50, bottom: 50, left: 50 }; // prettier-ignore switch (this.dataVizView) { - case DataVizView.TABLE: return ; + case DataVizView.TABLE: return ; case DataVizView.LINECHART: return (this._chartRenderer = r ?? undefined)} height={height} width={width} fieldKey={this.fieldKey} margin={margin} rootDoc={this.rootDoc} axes={this.axes} pairs={this.pairs} dataDoc={this.dataDoc} />; } } @@ -136,7 +137,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { { passive: false } ) }> - + {this.selectView} ); diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 2d6c5f0f2..c8daf7c90 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -8,8 +8,28 @@ // change the color of the circle element to be red fill: red; } +.focus-selected, +.selected { + // change the color of the circle element to be red + fill: lightblue; + position: absolute; + transform-box: fill-box; + scale: 2; + transform-origin: center; +} +.focus { + fill: transparent; + outline: black solid 1px; + border-radius: 100%; +} +.focus-selected { + scale: 1; + outline: black solid 1px; + border-radius: 100%; +} .chart-container { display: flex; flex-direction: column; align-items: center; + cursor: default; } diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index e6a06a454..6a223e683 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -6,11 +6,15 @@ import * as d3 from 'd3'; import { Doc, DocListCast } from '../../../../../fields/Doc'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; -import { Cast } from '../../../../../fields/Types'; +import { Cast, DocCast, NumCast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; import { PinProps, PresBox } from '../../trails'; import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils'; import './Chart.scss'; +import { LinkManager } from '../../../../util/LinkManager'; +import { DocumentManager } from '../../../../util/DocumentManager'; +import { Id } from '../../../../../fields/FieldSymbols'; +import { DataVizBox } from '../DataVizBox'; export interface DataPoint { x: number; @@ -40,7 +44,7 @@ export class LineChart extends React.Component { private _disposers: { [key: string]: IReactionDisposer } = {}; private _lineChartRef: React.RefObject = React.createRef(); private _lineChartSvg: d3.Selection | undefined; - private _rangeVals: { xMin?: number; xMax?: number;yMin?: number;yMax?: number;}= {}; + private _rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number } = {}; @observable _currSelected: SelectedDataPoint | undefined = undefined; @observable _lineChartData: DataPoint[][] | undefined = undefined; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @@ -51,9 +55,11 @@ export class LineChart extends React.Component { componentDidMount = () => { this._disposers.chartdata = reaction( () => this.props.axes.slice(), - axes => {if (axes.length > 1) { - this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a,b) => a.x < b.x ? 1:-1)] - }}, + axes => { + if (axes.length > 1) { + this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1))]; + } + }, { fireImmediately: true } ); this._disposers.chartData = reaction( @@ -78,12 +84,38 @@ export class LineChart extends React.Component { }, { fireImmediately: true } ); + this._disposers.highlights = reaction( + () => ({ + selected: this._currSelected, + pairs: LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) + .filter(link => link.anchor1 !== this.props.rootDoc) // all links that are pointing to this node + .map(link => DocCast(link.anchor1)) // get the documents that are pointing to this node + .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes + .filter(dvb => dvb) + .map(dvb => dvb.pairs.filter(pair => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor + .lastElement(), + }), + ({ selected, pairs }) => { + this.clearAnnoations(); + selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); + pairs.forEach(pair => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); + }, + { fireImmediately: true } + ); }; // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that + clearAnnoations = () => { + const elements = document.querySelectorAll('.datapoint'); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + element.classList.remove('highlight'); + element.classList.remove('selected'); + } + }; // gets called whenever the "data-annotations" fields gets updated - drawAnnotations(dataX: number, dataY: number) { + drawAnnotations = (dataX: number, dataY: number, selected?: boolean) => { // TODO: nda - can optimize this by having some sort of mapping of the x and y values to the individual circle elements // loop through all html elements with class .circle-d1 and find the one that has "data-x" and "data-y" attributes that match the dataX and dataY // if it exists, then highlight it @@ -94,14 +126,13 @@ export class LineChart extends React.Component { const x = element.getAttribute('data-x'); const y = element.getAttribute('data-y'); if (x === dataX.toString() && y === dataY.toString()) { - element.classList.add('highlight'); + element.classList.add(selected ? 'selected' : 'highlight'); } // TODO: nda - this remove highlight code should go where we remove the links // } else { - // element.classList.remove('highlight'); // } } - } + }; removeAnnotations(dataX: number, dataY: number) { // loop through and remove any annotations that no longer exist @@ -115,7 +146,7 @@ export class LineChart extends React.Component { return true; } if (this._currSelected) { - this._currSelected = undefined; + this.setCurrSelected(); return true; } return false; @@ -123,9 +154,7 @@ export class LineChart extends React.Component { // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { - const anchor = Docs.Create.TextanchorDocument({ - title: 'line doc selection' + this._currSelected?.x, - }); + const anchor = Docs.Create.TextanchorDocument({ title: 'line doc selection' + this._currSelected?.x, unrendered: true }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); anchor.presDataVizSelection = this._currSelected ? new List([this._currSelected.x, this._currSelected.y]) : undefined; return anchor; @@ -140,7 +169,7 @@ export class LineChart extends React.Component { } setupTooltip() { - const tooltip = d3 + return d3 .select(this._lineChartRef.current) .append('div') .attr('class', 'tooltip') @@ -150,14 +179,15 @@ export class LineChart extends React.Component { .style('padding', '5px') .style('position', 'absolute') .style('font-size', '12px'); - return tooltip; } // TODO: nda - use this everyewhere we update currSelected? @action - setCurrSelected(x: number, y: number) { + setCurrSelected(x?: number, y?: number) { // TODO: nda - get rid of svg element in the list? - this._currSelected = { x, y }; + this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; + this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); + this.props.pairs.forEach(pair => (pair.selected = pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y ? true : undefined)); } drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear, yScale: d3.ScaleLinear) { @@ -215,32 +245,29 @@ export class LineChart extends React.Component { // draw the datapoint circle this.drawDataPoints(data, 0, xScale, yScale); - const focus = svg.append('g').attr('class', 'focus').style('display', 'none'); - focus.append('circle').attr('r', 5).attr('class', 'circle'); + const higlightFocusPt = svg.append('g').style('display', 'none'); + higlightFocusPt.append('circle').attr('r', 5).attr('class', 'circle'); const tooltip = this.setupTooltip(); // add all the tooltipContent to the tooltip const mousemove = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis + const x0 = Math.min(data.length - 1, bisect(data, xScale.invert(xPos - 5))); // shift x by -5 so that you can reach points on the left-side axis const d0 = data[x0]; - focus.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`); - tooltip.transition().duration(300).style('opacity', 0.9); - // TODO: nda - updating the inner html could be deadly cause injection attacks! - tooltip - .html(() => `(${d0.x},${d0.y})`) // text content for tooltip - .style('pointer-events', 'none') - .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`); + if (!d0) return; + + this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip); }); const onPointClick = action((e: any) => { const bisect = d3.bisector((d: DataPoint) => d.x).left; const xPos = d3.pointer(e)[0]; - const x0 = bisect(data, xScale.invert(xPos - 25)); // shift x by -25 so that you can reach points on the left-side axis + const x0 = bisect(data, xScale.invert(xPos - 5)); // shift x by -5 so that you can reach points on the left-side axis const d0 = data[x0]; // find .circle-d1 with data-x = d0.x and data-y = d0.y const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y); - this._currSelected = { x: d0.x, y: d0.y, elem: selected }; + this.setCurrSelected(d0.x, d0.y); + this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip); }); svg.append('rect') @@ -250,17 +277,33 @@ export class LineChart extends React.Component { .attr('fill', 'none') .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`) .style('opacity', 0) - .on('mouseover', () => focus.style('display', null)) + .on('mouseover', () => higlightFocusPt.style('display', null)) .on('mouseout', () => tooltip.transition().duration(300).style('opacity', 0)) .on('mousemove', mousemove) .on('click', onPointClick); }; + private updateTooltip( + higlightFocusPt: d3.Selection, + xScale: d3.ScaleLinear, + d0: DataPoint, + yScale: d3.ScaleLinear, + tooltip: d3.Selection + ) { + higlightFocusPt.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`).attr('class', this._currSelected?.x === d0.x && this._currSelected?.y === d0.y ? 'focus-selected' : 'focus'); + tooltip.transition().duration(300).style('opacity', 0.9); + // TODO: nda - updating the inner html could be deadly cause injection attacks! + tooltip + .html(() => `(${d0.x},${d0.y})`) // text content for tooltip + .style('pointer-events', 'none') + .style('transform', `translate(${xScale(d0.x) - this.width / 2}px,${yScale(d0.y) - 30}px)`); + } + render() { const selectedPt = this._currSelected ? `x: ${this._currSelected.x} y: ${this._currSelected.y}` : 'none'; return (
- {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Curr Selected: ${selectedPt}`} + {this.props.axes.length < 2 ? 'first use table view to select two axes to plot' : `Selected: ${selectedPt}`}
); } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index adefe90cd..0d69ac890 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,12 +1,19 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../../Utils'; +import { AnimationSym, Doc } from '../../../../../fields/Doc'; +import { Id } from '../../../../../fields/FieldSymbols'; +import { List } from '../../../../../fields/List'; +import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils'; +import { DragManager } from '../../../../util/DragManager'; +import { DocumentView } from '../../DocumentView'; +import { DataVizView } from '../DataVizBox'; interface TableBoxProps { pairs: { [key: string]: any }[]; selectAxes: (axes: string[]) => void; axes: string[]; + docView?: () => DocumentView | undefined; } @observer @@ -20,39 +27,72 @@ export class TableBox extends React.Component {
xy + setupMoveUpEvents( + {}, + e, + returnFalse, + emptyFunction, + action(e => { + const newAxes = this.props.axes; + if (newAxes.includes(col)) { + newAxes.splice(newAxes.indexOf(col), 1); + } else if (newAxes.length >= 1) { + newAxes[1] = col; + } else { + newAxes[0] = col; + } + this.props.selectAxes(newAxes); + }) + ) + }> + {col} +
{p.x}{p.y}{p[col]}
- {this.columns.map(col => ( - - ))} + {this.columns + .filter(col => !col.startsWith('select')) + .map(col => { + const header = React.createRef(); + return ( + + ); + })} - {this.props.pairs?.map(p => { + {this.props.pairs?.map((p, i) => { return ( - + (p['select' + this.props.docView?.()?.rootDoc![Id]] = !p['select' + this.props.docView?.()?.rootDoc![Id]]))}> {this.columns.map(col => ( - + ))} ); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index cc024d83a..e89f5db52 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1335,6 +1335,7 @@ export namespace Doc { } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { + if (linkDoc.anchor2 === anchorDoc || (linkDoc.anchor2 as Doc).annotationOn) return '2'; return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2'; } -- cgit v1.2.3-70-g09d2 From 893c48e17c3285613c9c3fa4e84d4d3317ee2772 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 7 Apr 2023 10:57:34 -0400 Subject: updated linechart highlighting --- .../views/nodes/DataVizBox/components/Chart.scss | 23 +++++++++++++--------- .../nodes/DataVizBox/components/LineChart.tsx | 6 +++--- 2 files changed, 17 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index c8daf7c90..56fcb1fcf 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -4,29 +4,34 @@ height: fit-content; } -.highlight { - // change the color of the circle element to be red - fill: red; -} -.focus-selected, +.hoverHighlight-selected, .selected { // change the color of the circle element to be red - fill: lightblue; + fill: transparent; + outline: lightblue solid 2px; + border-radius: 100%; position: absolute; transform-box: fill-box; - scale: 2; transform-origin: center; } -.focus { +.hoverHighlight { fill: transparent; outline: black solid 1px; border-radius: 100%; } -.focus-selected { +.hoverHighlight-selected { + fill: transparent; scale: 1; outline: black solid 1px; border-radius: 100%; } +.datapoint { + fill: black; +} +.brushed { + // change the color of the circle element to be red + fill: red; +} .chart-container { display: flex; flex-direction: column; diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 6a223e683..308ef4cba 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -110,7 +110,7 @@ export class LineChart extends React.Component { const elements = document.querySelectorAll('.datapoint'); for (let i = 0; i < elements.length; i++) { const element = elements[i]; - element.classList.remove('highlight'); + element.classList.remove('brushed'); element.classList.remove('selected'); } }; @@ -126,7 +126,7 @@ export class LineChart extends React.Component { const x = element.getAttribute('data-x'); const y = element.getAttribute('data-y'); if (x === dataX.toString() && y === dataY.toString()) { - element.classList.add(selected ? 'selected' : 'highlight'); + element.classList.add(selected ? 'selected' : 'brushed'); } // TODO: nda - this remove highlight code should go where we remove the links // } else { @@ -290,7 +290,7 @@ export class LineChart extends React.Component { yScale: d3.ScaleLinear, tooltip: d3.Selection ) { - higlightFocusPt.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`).attr('class', this._currSelected?.x === d0.x && this._currSelected?.y === d0.y ? 'focus-selected' : 'focus'); + higlightFocusPt.attr('transform', `translate(${xScale(d0.x)},${yScale(d0.y)})`).attr('class', this._currSelected?.x === d0.x && this._currSelected?.y === d0.y ? 'hoverHighlight-selected' : 'hoverHighlight'); tooltip.transition().duration(300).style('opacity', 0.9); // TODO: nda - updating the inner html could be deadly cause injection attacks! tooltip -- cgit v1.2.3-70-g09d2 From 68c6c36af823255824a6b0692e8c33618c2d7ca2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 10 Apr 2023 11:32:44 -0400 Subject: fixed brushing of fonticon boxes with dropdowns. made line charts use computed values instead of observables --- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 14 +++- .../views/nodes/DataVizBox/components/Chart.scss | 2 +- .../nodes/DataVizBox/components/LineChart.tsx | 89 ++++++++++++---------- src/client/views/nodes/button/FontIconBox.tsx | 33 ++++++-- .../nodes/button/colorDropdown/ColorDropdown.tsx | 57 +++++++------- src/fields/Doc.ts | 2 +- 7 files changed, 120 insertions(+), 81 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9fe8f5f49..9d4a788db 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -664,8 +664,8 @@ export class CollectionFreeFormView extends CollectionSubView() { // 2 ways of doing it // @observable private pairs: { [key: string]: number | string | undefined }[] = []; // @observable private pairs: { [key: string]: FieldResult }[] = []; - @observable pairs: { [key: string]: string }[] = []; + static pairSet = new ObservableMap(); + @computed.struct get pairs() { + return DataVizBox.pairSet.get(StrCast(this.rootDoc.fileUpload)); + } private _chartRenderer: LineChart | undefined; // // another way would be store a schema that defines the type of data we are expecting from an imported doc @@ -93,6 +96,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 50, bottom: 50, left: 50 }; + if (!this.pairs) return 'no data'; // prettier-ignore switch (this.dataVizView) { case DataVizView.TABLE: return ; @@ -109,8 +113,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } fetchData() { + if (DataVizBox.pairSet.has(StrCast(this.rootDoc.fileUpload))) return; + DataVizBox.pairSet.set(StrCast(this.rootDoc.fileUpload), []); fetch('/csvData?uri=' + this.dataUrl?.url.href) // - .then(res => res.json().then(action(res => !res.errno && (this.pairs = res)))); + .then(res => res.json().then(action(res => !res.errno && DataVizBox.pairSet.set(StrCast(this.rootDoc.fileUpload), res)))); } // handle changing the view using a button @@ -121,7 +127,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { } render() { - return this.pairs.length == 0 ? ( + return !this.pairs?.length ? (
Loading...
) : (
{ private _disposers: { [key: string]: IReactionDisposer } = {}; private _lineChartRef: React.RefObject = React.createRef(); private _lineChartSvg: d3.Selection | undefined; - private _rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number } = {}; @observable _currSelected: SelectedDataPoint | undefined = undefined; - @observable _lineChartData: DataPoint[][] | undefined = undefined; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates + @computed get _lineChartData() { + if (this.props.axes.length <= 1) return []; + return this.props.pairs + ?.filter(pair => (!this.incomingLinks.length ? true : Array.from(Object.keys(pair)).some(key => pair[key] && key.startsWith('select')))) + .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) + .sort((a, b) => (a.x < b.x ? -1 : 1)); + } + @computed get incomingLinks() { + return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + .filter(link => link.anchor1 !== this.props.rootDoc) // get links where this chart doc is the target of the link + .map(link => DocCast(link.anchor1)); // then return the source of the link + } + @computed get incomingSelected() { + return this.incomingLinks // all links that are pointing to this node + .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes + .filter(dvb => dvb) + .map(dvb => dvb.pairs?.filter(pair => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor + .lastElement(); + } + @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { + return minMaxRange([this._lineChartData]); + } componentWillUnmount() { Array.from(Object.keys(this._disposers)).forEach(key => this._disposers[key]()); } componentDidMount = () => { - this._disposers.chartdata = reaction( - () => this.props.axes.slice(), - axes => { - if (axes.length > 1) { - this._lineChartData = [this.props.pairs?.map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1))]; - } - }, - { fireImmediately: true } - ); this._disposers.chartData = reaction( - () => ({ dataSet: this._lineChartData, axes: this.props.axes.slice(), w: this.props.width, h: this.props.height }), - vals => { - if (vals.dataSet) { - this._rangeVals = minMaxRange(vals.dataSet); - this.drawChart(vals.dataSet); + () => ({ dataSet: this._lineChartData, w: this.props.width, h: this.props.height }), + ({ dataSet, w, h }) => { + if (dataSet) { + this.drawChart([dataSet], this.rangeVals, w, h); + // redraw annotations when the chart data has changed, or the local or inherited selection has changed + this.clearAnnotations(); + this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true); + this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); } }, { fireImmediately: true } @@ -87,18 +101,13 @@ export class LineChart extends React.Component { this._disposers.highlights = reaction( () => ({ selected: this._currSelected, - pairs: LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) - .filter(link => link.anchor1 !== this.props.rootDoc) // all links that are pointing to this node - .map(link => DocCast(link.anchor1)) // get the documents that are pointing to this node - .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes - .filter(dvb => dvb) - .map(dvb => dvb.pairs.filter(pair => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor - .lastElement(), + incomingSelected: this.incomingSelected, }), - ({ selected, pairs }) => { - this.clearAnnoations(); + ({ selected, incomingSelected }) => { + // redraw annotations when the chart data has changed, or the local or inherited selection has changed + this.clearAnnotations(); selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); - pairs.forEach(pair => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); + incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); }, { fireImmediately: true } ); @@ -106,7 +115,7 @@ export class LineChart extends React.Component { // anything that doesn't need to be recalculated should just be stored as drawCharts (i.e. computed values) and drawChart is gonna iterate over these observables and generate svgs based on that - clearAnnoations = () => { + clearAnnotations = () => { const elements = document.querySelectorAll('.datapoint'); for (let i = 0; i < elements.length; i++) { const element = elements[i]; @@ -207,12 +216,12 @@ export class LineChart extends React.Component { } // TODO: nda - can use d3.create() to create html element instead of appending - drawChart = (dataSet: DataPoint[][]) => { + drawChart = (dataSet: DataPoint[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => { // clearing tooltip and the current chart d3.select(this._lineChartRef.current).select('svg').remove(); d3.select(this._lineChartRef.current).select('.tooltip').remove(); - const { xMin, xMax, yMin, yMax } = this._rangeVals; + const { xMin, xMax, yMin, yMax } = rangeVals; if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) { return; } @@ -226,16 +235,16 @@ export class LineChart extends React.Component { const svg = (this._lineChartSvg = d3 .select(this._lineChartRef.current) .append('svg') - .attr('width', `${this.width + margin.right + margin.left}`) - .attr('height', `${this.height + margin.top + margin.bottom}`) + .attr('width', `${width + margin.right + margin.left}`) + .attr('height', `${height + margin.top + margin.bottom}`) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`)); // create x and y grids - xGrid(svg.append('g'), this.height, xScale); - yGrid(svg.append('g'), this.width, yScale); - xAxisCreator(svg.append('g'), this.height, xScale); - yAxisCreator(svg.append('g'), this.width, yScale); + xGrid(svg.append('g'), height, xScale); + yGrid(svg.append('g'), width, yScale); + xAxisCreator(svg.append('g'), height, xScale); + yAxisCreator(svg.append('g'), width, yScale); // draw the plot line const data = dataSet[0]; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 8410fda18..8eacfbc51 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -161,7 +161,13 @@ export class FontIconBox extends DocComponent() {
); return ( -
e.stopPropagation()} onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}> +
e.stopPropagation()} + onClick={action(() => { + this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + Doc.UnBrushAllDocs(); + })}> {checkResult} {label} {this.rootDoc.dropDownOpen ? dropdown : null} @@ -198,7 +204,10 @@ export class FontIconBox extends DocComponent() { e.stopPropagation(); e.preventDefault(); }} - onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}> + onClick={action(() => { + this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + Doc.UnBrushAllDocs(); + })}> setValue(Number(e.target.value))))} />
setValue(Number(checkResult) + 1))}> @@ -215,6 +224,7 @@ export class FontIconBox extends DocComponent() { onClick={e => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; + Doc.UnBrushAllDocs(); }} />
@@ -237,7 +247,10 @@ export class FontIconBox extends DocComponent() {
(this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}> + onClick={action(() => { + this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + Doc.UnBrushAllDocs(); + })}> {this.Icon(color)} {!this.label || !FontIconBox.GetShowLabels() ? null : (
@@ -316,7 +329,14 @@ export class FontIconBox extends DocComponent() {
(this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen) : undefined}> + onClick={ + dropdown + ? () => { + this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + Doc.UnBrushAllDocs(); + } + : undefined + }> {dropdown ? null : }
{text && text[0].toUpperCase() + text.slice(1)}
{label} @@ -335,6 +355,7 @@ export class FontIconBox extends DocComponent() { onClick={e => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; + Doc.UnBrushAllDocs(); }} />
@@ -379,6 +400,7 @@ export class FontIconBox extends DocComponent() { style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(e => { this.colorPickerClosed = !this.colorPickerClosed; + setTimeout(() => Doc.UnBrushAllDocs()); e.stopPropagation(); })} onPointerDown={e => e.stopPropagation()}> @@ -397,6 +419,7 @@ export class FontIconBox extends DocComponent() { e.preventDefault(); e.stopPropagation(); this.colorPickerClosed = true; + Doc.UnBrushAllDocs(); })} />
@@ -816,7 +839,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | ' setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), }], ['fillColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ? Colors.MEDIUM_BLUE : 'transparent'), + checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"), setInk: (doc: Doc) => (doc.fillColor = StrCast(value)), setMode: () => SetActiveFillColor(StrCast(value)), }], diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx index 7f414ddbb..74c3c563c 100644 --- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx +++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx @@ -12,7 +12,7 @@ export class ColorDropdown extends Component { const active: string = StrCast(this.props.rootDoc.dropDownOpen); const script: string = StrCast(this.props.rootDoc.script); - const scriptCheck: string = script + "(undefined, true)"; + const scriptCheck: string = script + '(undefined, true)'; const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result; const stroke: boolean = false; @@ -24,24 +24,21 @@ export class ColorDropdown extends Component { // strokeIcon = (
); // } - const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', - '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', - '#FFFFFF', '#f1efeb']; + const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb']; - const colorBox = (func: (color: ColorState) => void) => ; - const label = !this.props.label || !FontIconBox.GetShowLabels() ? (null) : -
- {this.props.label} -
; + const colorBox = (func: (color: ColorState) => void) => ; + const label = + !this.props.label || !FontIconBox.GetShowLabels() ? null : ( +
+ {this.props.label} +
+ ); - const dropdownCaret =
- -
; + const dropdownCaret = ( +
+ +
+ ); const click = (value: ColorState) => { const hex: string = value.hex; @@ -51,26 +48,30 @@ export class ColorDropdown extends Component { } }; return ( -
this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen} + onClick={() => (this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen)} onPointerDown={e => e.stopPropagation()}> -
+
{label} {/* {dropdownCaret} */} - {this.props.rootDoc.dropDownOpen ? + {this.props.rootDoc.dropDownOpen ? (
-
e.stopPropagation()} - onClick={e => e.stopPropagation()}> +
e.stopPropagation()} onClick={e => e.stopPropagation()}> {colorBox(click)}
-
{ e.stopPropagation(); this.props.rootDoc.dropDownOpen = false; }} /> +
{ + e.stopPropagation(); + this.props.rootDoc.dropDownOpen = false; + }} + />
- : null} + ) : null}
); } -} \ No newline at end of file +} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index e89f5db52..1db56cbdd 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1394,7 +1394,7 @@ export namespace Doc { }); } export function UnBrushAllDocs() { - brushManager.BrushedDoc.clear(); + runInAction(() => brushManager.BrushedDoc.clear()); } export function getDocTemplate(doc?: Doc) { -- cgit v1.2.3-70-g09d2 From 6520bb8594b6cee9a4df09b25adf7aa590ef29e4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 10 Apr 2023 13:49:28 -0400 Subject: fixed linechart margins --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 2 +- src/client/views/nodes/DataVizBox/components/LineChart.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 8593c8994..2666787c5 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -95,7 +95,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @computed get selectView() { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; - const margin = { top: 10, right: 50, bottom: 50, left: 50 }; + const margin = { top: 10, right: 10, bottom: 10, left: 10 }; if (!this.pairs) return 'no data'; // prettier-ignore switch (this.dataVizView) { diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index bc9f0be77..ecb06f64f 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -235,8 +235,8 @@ export class LineChart extends React.Component { const svg = (this._lineChartSvg = d3 .select(this._lineChartRef.current) .append('svg') - .attr('width', `${width + margin.right + margin.left}`) - .attr('height', `${height + margin.top + margin.bottom}`) + .attr('width', `${width - margin.right - margin.left}`) + .attr('height', `${height - margin.top - margin.bottom}`) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`)); -- cgit v1.2.3-70-g09d2 From 3cb7f85b23eb0ae3a432bbe15b8a2cda37290ce2 Mon Sep 17 00:00:00 2001 From: geireann Date: Mon, 10 Apr 2023 15:32:06 -0400 Subject: fixed linechart margins --- package-lock.json | 8 ++++---- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 2 +- src/client/views/nodes/DataVizBox/components/LineChart.tsx | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 83bb8cd9c..4b83d07f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10498,14 +10498,14 @@ "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { - "image-size": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", "tryit": "^1.0.1" }, "dependencies": { "image-size": { - "version": "git+ssh://git@github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1", - "from": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" + "version": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "from": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" }, "isarray": { "version": "0.0.1", @@ -21004,7 +21004,7 @@ "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", "optional": true, "requires": { "memory-pager": "^1.0.2" diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 2666787c5..eb25d3264 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -95,7 +95,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @computed get selectView() { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; - const margin = { top: 10, right: 10, bottom: 10, left: 10 }; + const margin = { top: 10, right: 25, bottom: 50, left:25}; if (!this.pairs) return 'no data'; // prettier-ignore switch (this.dataVizView) { diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index ecb06f64f..777bf2f66 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -74,7 +74,7 @@ export class LineChart extends React.Component { } componentDidMount = () => { this._disposers.chartData = reaction( - () => ({ dataSet: this._lineChartData, w: this.props.width, h: this.props.height }), + () => ({ dataSet: this._lineChartData, w: this.width, h: this.height }), ({ dataSet, w, h }) => { if (dataSet) { this.drawChart([dataSet], this.rangeVals, w, h); @@ -227,16 +227,16 @@ export class LineChart extends React.Component { } // creating the x and y scales - const xScale = scaleCreatorNumerical(xMin, xMax, 0, this.width); - const yScale = scaleCreatorNumerical(0, yMax, this.height, 0); + const xScale = scaleCreatorNumerical(xMin, xMax, 0, width); + const yScale = scaleCreatorNumerical(0, yMax,height, 0); // adding svg const margin = this.props.margin; const svg = (this._lineChartSvg = d3 .select(this._lineChartRef.current) .append('svg') - .attr('width', `${width - margin.right - margin.left}`) - .attr('height', `${height - margin.top - margin.bottom}`) + .attr('width', `${width +margin.left + margin.right}`) + .attr('height', `${height + margin.top + margin.bottom }`) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`)); @@ -281,7 +281,7 @@ export class LineChart extends React.Component { svg.append('rect') .attr('class', 'overlay') - .attr('width', this.width) + .attr('width', width) .attr('height', this.height + margin.top + margin.bottom) .attr('fill', 'none') .attr('translate', `translate(${margin.left}, ${-(margin.top + margin.bottom)})`) -- cgit v1.2.3-70-g09d2 From 1e781aebb1c1d9c95592f63035c08566958d972a Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Apr 2023 09:50:29 -0400 Subject: linechart css fix --- .../views/nodes/DataVizBox/components/Chart.scss | 69 +++++++++++----------- 1 file changed, 35 insertions(+), 34 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index f1d09d2e7..d4f7bfb32 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -1,40 +1,41 @@ -.tooltip { - // make the height width bigger - width: fit-content; - height: fit-content; -} - -.hoverHighlight-selected, -.selected { - // change the color of the circle element to be red - fill: transparent; - outline: red solid 2px; - border-radius: 100%; - position: absolute; - transform-box: fill-box; - transform-origin: center; -} -.hoverHighlight { - fill: transparent; - outline: black solid 1px; - border-radius: 100%; -} -.hoverHighlight-selected { - fill: transparent; - scale: 1; - outline: black solid 1px; - border-radius: 100%; -} -.datapoint { - fill: black; -} -.brushed { - // change the color of the circle element to be red - fill: red; -} .chart-container { display: flex; flex-direction: column; align-items: center; cursor: default; + + .tooltip { + // make the height width bigger + width: fit-content; + height: fit-content; + } + + .hoverHighlight-selected, + .selected { + // change the color of the circle element to be red + fill: transparent; + outline: red solid 2px; + border-radius: 100%; + position: absolute; + transform-box: fill-box; + transform-origin: center; + } + .hoverHighlight { + fill: transparent; + outline: black solid 1px; + border-radius: 100%; + } + .hoverHighlight-selected { + fill: transparent; + scale: 1; + outline: black solid 1px; + border-radius: 100%; + } + .datapoint { + fill: black; + } + .brushed { + // change the color of the circle element to be red + fill: red; + } } -- cgit v1.2.3-70-g09d2 From 8a1267faf796b2e2a30a6ba9f86879854e9ee983 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Apr 2023 13:51:44 -0400 Subject: removed arrangeItems context button for collections to make it a funciton on drop instead of a reaction. Converted isLinkButton to be an onClick script. got rid of unused PARAMS field on templates. fixed PresElementBox rendering of embedded docs. --- src/client/documents/Documents.ts | 7 +-- src/client/util/CurrentUserUtils.ts | 4 +- src/client/util/DropConverter.ts | 10 +--- src/client/util/LinkFollower.ts | 6 ++ src/client/views/DocumentButtonBar.tsx | 9 +-- src/client/views/MarqueeAnnotator.tsx | 8 ++- src/client/views/PropertiesButtons.tsx | 7 ++- src/client/views/StyleProvider.tsx | 5 +- src/client/views/collections/CollectionMenu.tsx | 19 +----- .../views/collections/CollectionNoteTakingView.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 56 ++++++++++-------- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 67 +++++----------------- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/LinkAnchorBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 7 ++- .../views/nodes/formattedText/DashFieldView.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/fields/Doc.ts | 10 ++-- src/fields/documentSchemas.ts | 1 - 24 files changed, 104 insertions(+), 147 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 979b294e0..7600fcac9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -263,7 +263,6 @@ export class DocumentOptions { caption?: RichTextField; opacity?: number; defaultBackgroundColor?: string; - _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint hideLinkAnchors?: boolean; // suppresses link anchor dots from being displayed isFolder?: boolean; @@ -508,13 +507,13 @@ export namespace Docs { layout: { view: LinkBox, dataField: defaultDataKey }, options: { childDontRegisterViews: true, - _isLinkButton: true, + onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }), hideLinkAnchors: true, _height: 150, description: '', showCaption: 'description', backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area - _removeDropProperties: new List(['isLinkButton']), + _removeDropProperties: new List(['onClick']), }, }, ], @@ -1695,7 +1694,7 @@ export namespace DocUtils { _width: 15, _height: 15, _xPadding: 0, - _isLinkButton: true, + onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }), _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 623aaf357..3d0db04b4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -271,7 +271,7 @@ export class CurrentUserUtils { {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, - {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, onClick: ScriptField.MakeScript("followLink(this,altKey)", {altKey:"boolean"}) }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}}, @@ -622,7 +622,7 @@ export class CurrentUserUtils { { title: "Snap\xA0Lines",icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform { title: "View\xA0All", icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform ] } diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 7c209d1e0..2e1d6ba23 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -33,15 +33,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt = und let any = false; docs.forEach(d => { if (!StrCast(d.title).startsWith('-')) { - const params = StrCast(d.title) - .match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1] - .replace('()', ''); - if (params) { - any = makeTemplate(d, false) || any; - d.PARAMS = params; - } else { - any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; - } + any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) { any = makeTemplate(d, false) || any; } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index df61ecece..f5aae6632 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -7,6 +7,7 @@ import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/no import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; +import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; /* @@ -118,3 +119,8 @@ export class LinkFollower { }); } } + +ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { + SelectionManager.DeselectAll(); + LinkFollower.FollowLink(undefined, doc, altKey); +}); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index e83ea00cf..7e1b32398 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,7 +5,7 @@ import { action, computed, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, DocCast, NumCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast, ScriptCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -238,11 +238,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); }; + const followLink = ScriptCast(targetDoc?.onClick)?.script.originalScript.includes('followLink('); return !targetDoc ? null : ( Set onClick to follow primary link
}>
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
{followBtn( @@ -384,7 +385,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: {dataview: e.altKey}, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
@@ -593,7 +594,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)} + linkCreated={link => (link.linkDisplay = !ScriptCast(this.props.views().lastElement()?.rootDoc.onClick)?.script.originalScript.includes('followLink('))} linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this.props.views().lastElement()?.rootDoc} /> diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 30867a774..6b4f8ad85 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -14,6 +14,7 @@ import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { AnchorMenu } from './pdf/AnchorMenu'; import React = require('react'); +import { ScriptField } from '../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { @@ -146,7 +147,12 @@ export class MarqueeAnnotator extends React.Component { const scale = this.props.scaling?.() || 1; const anno = savedAnnos[0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; - const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title }); + const marqueeAnno = Docs.Create.FreeformDocument([], { + onClick: isLinkButton ? ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }) : undefined, + backgroundColor: color, + annotationOn: this.props.rootDoc, + title: 'Annotation on ' + this.props.rootDoc.title, + }); marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1); marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop); marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 11e9dd9c9..0723966e1 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -289,6 +289,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => { + const linkButton = ScriptCast(docView.props.Document.onClick)?.script.originalScript.includes('followLink('); docView.noOnClick(); switch (onClick) { case 'enterPortal': @@ -299,11 +300,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { break; case 'linkInPlace': docView.toggleFollowLink(false, false); - docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.lightbox : undefined; + docView.props.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined; break; case 'linkOnRight': docView.toggleFollowLink(false, false); - docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.addRight : undefined; + docView.props.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined; break; } }); @@ -326,7 +327,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { ]; const list = buttonList.map(value => { const click = () => this.handleOptionChange(value[0]); - const linkButton = BoolCast(this.selectedDoc._isLinkButton); + const linkButton = BoolCast(ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('followLink(')); const followLoc = this.selectedDoc._followLinkLocation; const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index f7b9420a7..8d67f0528 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -4,7 +4,7 @@ import { Shadows } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; @@ -279,7 +279,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt {}), initialize: emptyFunction, }; - _openLinkInCommand = { - params: ['target', 'container'], - title: 'link follow target', - script: `{ if (self.container?.length) { - getProto(self.target).linkContainer = self.container[0]; - getProto(self.target).isLinkButton = true; - getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])"); - }}`, - immediate: undoBatch((container: Doc[]) => { - if (container.length) { - Doc.GetProto(this.target).linkContainer = container[0]; - Doc.GetProto(this.target).isLinkButton = true; - Doc.GetProto(this.target).onClick = ScriptField.MakeScript('getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])'); - } - }), - initialize: emptyFunction, - }; _viewCommand = { params: ['target'], title: 'bookmark view', @@ -328,7 +311,7 @@ export class CollectionViewBaseChrome extends React.Component (this.docsDraggedRowCol.length ? 'none' : undefined); // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView) getDisplayDoc(doc: Doc, width: () => number) { - const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt; const noteTakingDocTransform = () => this.getDocTransform(doc, dref); @@ -293,7 +293,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc; + const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc; const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 642d29d2a..5891cfe78 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -7,7 +7,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, ScriptCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; import { Docs } from '../../documents/Documents'; @@ -406,7 +406,7 @@ export class CollectionStackedTimeline extends CollectionSubView { - if (anchorDoc.isLinkButton) { + if (ScriptCast(anchorDoc.onClick)?.script.originalScript.includes('followLink(')) { LinkFollower.FollowLink(undefined, anchorDoc, false); } const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 1e02fc9d4..a85ee0e02 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -305,7 +305,7 @@ export class CollectionStackingView extends CollectionSubView number, count: number) { - const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt; @@ -323,7 +323,7 @@ export class CollectionStackingView extends CollectionSubView (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1ecc69174..7ae7be3c8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -349,6 +349,7 @@ export class CollectionFreeFormView extends CollectionSubView (doc.zIndex = doc.isInkMask ? 5000 : index + 1)); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; + for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); @@ -370,6 +371,30 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(a.layout.y) - NumCast(b.layout.y)); + sorted.splice( + sorted.findIndex(pair => pair.layout === refDoc), + 1 + ); + if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) { + const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height); + const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0; + const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0; + + let lastx = NumCast(refDoc.x); + let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height)); + setTimeout( + action(() => + sorted.slice(1).forEach((pair, i) => { + lastx = pair.layout.x = lastx + deltax; + lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); + }) + ) + ); + } + } + (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); return true; } @@ -1409,24 +1434,6 @@ export class CollectionFreeFormView extends CollectionSubView) { - if (this.layoutDoc._autoArrange) { - const sorted = this.childLayoutPairs.slice().sort((a, b) => (NumCast(a.layout.y) < NumCast(b.layout.y) ? -1 : 1)); - if (sorted.length > 1) { - const deltay = sorted.length > 1 ? NumCast(sorted[1].layout.y) - (NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height)) : 0; - const deltax = sorted.length > 1 ? NumCast(sorted[1].layout.x) - NumCast(sorted[0].layout.x) : 0; - - let lastx = NumCast(sorted[0].layout.x); - let lasty = NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height); - setTimeout( - action(() => - sorted.slice(1).forEach((pair, i) => { - lastx = pair.layout.x = lastx + deltax; - lasty = (pair.layout.y = lasty + deltay) + NumCast(pair.layout._height); - }) - ) - ); - } - } this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document }))); return [] as ViewDefResult[]; } @@ -1712,11 +1719,12 @@ export class CollectionFreeFormView extends CollectionSubView (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), - icon: 'compress-arrows-alt', - }); + !Doc.noviceMode && + appearanceItems.push({ + description: 'Toggle auto arrange', + event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), + icon: 'compress-arrows-alt', + }); if (this.props.setContentView === emptyFunction) { !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; @@ -1729,7 +1737,7 @@ export class CollectionFreeFormView extends CollectionSubView TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); - appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); + this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 9e56de8c2..2d12005fb 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -139,7 +139,6 @@ export class DocumentContentsView extends React.Component< return proto instanceof Promise ? undefined : proto; } get layoutDoc() { - const params = StrCast(this.props.Document.PARAMS); // bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); const template: Doc = @@ -147,7 +146,7 @@ export class DocumentContentsView extends React.Component< (this.props.LayoutTemplateString && this.props.Document) || (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); - return Doc.expandTemplateLayout(template, this.props.Document, params ? '(' + params + ')' : this.props.layoutKey); + return Doc.expandTemplateLayout(template, this.props.Document, this.props.layoutKey); } CreateBindings(onClick: Opt, onInput: Opt): JsxBindings { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e24bf35f5..a39c3cf8f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -469,11 +469,6 @@ export class DocumentViewInternal extends DocComponent (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); - } else if (!this.disableClickScriptFunc && this.allLinks.length && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { - clickFunc = () => { - SelectionManager.DeselectAll(); - LinkFollower.FollowLink(undefined, this.Document, e.altKey); - }; } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { @@ -563,48 +558,24 @@ export class DocumentViewInternal extends DocComponent { - this.Document.ignoreClick = false; - if (setTargetToggle) { - this.Document.followLinkToggle = !this.Document.followLinkToggle; - this.Document._isLinkButton = this.Document.followLinkToggle || this.Document._isLinkButton; - } else { - this.Document._isLinkButton = !this.Document._isLinkButton; - } - if (this.Document._isLinkButton && !this.onClickHandler) { - zoom !== undefined && (this.Document.followLinkZoom = zoom); - } else if (this.Document._isLinkButton && this.onClickHandler) { - this.Document._isLinkButton = false; - this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined; - } - }; - @undoBatch - @action - toggleTargetOnClick = (): void => { - this.Document.ignoreClick = false; - this.Document._isLinkButton = true; - this.Document.followLinkToggle = true; + const hadOnClick = this.rootDoc.onClick; + this.noOnClick(); + this.Document.onClick = hadOnClick ? undefined : ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); + this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never'; }; @undoBatch @action - followLinkOnClick = (location: Opt, zoom: boolean): void => { + followLinkOnClick = (): void => { this.Document.ignoreClick = false; - this.Document._isLinkButton = true; + this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); this.Document.followLinkToggle = false; - this.Document.followLinkZoom = zoom; - this.Document.followLinkLocation = location; - }; - @undoBatch - @action - selectOnClick = (): void => { - this.Document.ignoreClick = false; - this.Document._isLinkButton = false; - this.Document.followLinkToggle = false; - this.Document.onClick = this.layoutDoc.onClick = undefined; + this.Document.followLinkZoom = false; + this.Document.followLinkLocation = undefined; }; @undoBatch noOnClick = (): void => { this.Document.ignoreClick = false; - this.Document._isLinkButton = false; + this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined; }; @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document); @@ -652,8 +623,7 @@ export class DocumentViewInternal extends DocComponent { @@ -744,29 +714,20 @@ export class DocumentViewInternal extends DocComponent (this.Document.followLinkZoom = !this.Document.followLinkZoom), - icon: this.Document.ignoreClick ? 'unlock' : 'lock', - }); if (!this.Document.annotationOn) { const options = cm.findByDescription('Options...'); const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); - onClicks.push({ description: this.Document.ignoreClick ? 'Select' : 'Do Nothing', event: () => (this.Document.ignoreClick = !this.Document.ignoreClick), icon: this.Document.ignoreClick ? 'unlock' : 'lock' }); - onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); - onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(false, true), icon: 'map-pin' }); + onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (LinkManager.Links(this.Document).length) { - onClicks.push({ description: 'Select on Click', event: () => this.selectOnClick(), icon: 'link' }); - onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(undefined, false), icon: 'link' }); - onClicks.push({ description: 'Toggle Link Target on Click', event: () => this.toggleTargetOnClick(), icon: 'map-pin' }); + onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' }); + onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' }); } } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index f38ebba27..22ecaa299 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -202,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { if (separation > 100) { const dragData = new DragManager.DocumentDragData([this.rootDoc]); dragData.dropAction = 'alias'; - dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'isLinkButton']; + dragData.removeDropProperties = ['anchor1_x', 'anchor1_y', 'anchor2_x', 'anchor2_y', 'onClick']; DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]); return true; } else { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index e38d7e2a8..502811e35 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -119,7 +119,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent 1 ? new List(splits) : newText; if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 361e000f9..809315963 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { ComputedField } from '../../../../fields/ScriptField'; +import { ComputedField, ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; @@ -1933,7 +1933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 9a74b5dba..92696240b 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
1 ? args.split('=')[0] : 'PARAMS'; + const params = args.split('=').length > 1 ? args.split('=')[0] : ''; const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']'; let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; @@ -945,7 +945,7 @@ export namespace Doc { expandedTemplateLayout = undefined; _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { - if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) { + if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype @@ -981,8 +981,8 @@ export namespace Doc { console.log('No, no, no!'); return { layout: childDoc, data: childDoc }; } - const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc; - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc }; + const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc; + return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '()'), data: resolvedDataDoc }; } export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 5b489a96c..b7fd06973 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -89,7 +89,6 @@ export const documentSchema = createSchema({ hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints. isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations - isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked layers: listSpec('string'), // which layers the document is part of _lockedPosition: 'boolean', // whether the document can be moved (dragged) _lockedTransform: 'boolean', // whether a freeformview can pan/zoom -- cgit v1.2.3-70-g09d2 From 5d1e3710a015d8915bd367ece753817d84d9d916 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Apr 2023 14:43:54 -0400 Subject: exracted FollowLinkScript() function. got rid of template parameters to simplify templating. --- src/client/documents/Documents.ts | 5 +-- src/client/util/CurrentUserUtils.ts | 3 +- src/client/util/LinkFollower.ts | 22 ++++++++---- src/client/views/DocumentButtonBar.tsx | 13 ++++--- src/client/views/DocumentDecorations.tsx | 6 ++-- src/client/views/MarqueeAnnotator.tsx | 3 +- src/client/views/PropertiesButtons.tsx | 5 +-- src/client/views/StyleProvider.tsx | 12 +++---- .../collections/CollectionStackedTimeline.tsx | 6 ++-- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 ++--- src/client/views/nodes/VideoBox.tsx | 5 +-- .../views/nodes/formattedText/DashDocView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- src/fields/Doc.ts | 41 +++++++--------------- 15 files changed, 65 insertions(+), 71 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7600fcac9..8d97ce5af 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -21,6 +21,7 @@ import { Networking } from '../Network'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager, dropActionType } from '../util/DragManager'; import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox'; +import { FollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { undoBatch, UndoManager } from '../util/UndoManager'; @@ -507,7 +508,7 @@ export namespace Docs { layout: { view: LinkBox, dataField: defaultDataKey }, options: { childDontRegisterViews: true, - onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }), + onClick: FollowLinkScript(), hideLinkAnchors: true, _height: 150, description: '', @@ -1694,7 +1695,7 @@ export namespace DocUtils { _width: 15, _height: 15, _xPadding: 0, - onClick: ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }), + onClick: FollowLinkScript(), _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3d0db04b4..76ea3e3ea 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -25,6 +25,7 @@ import { OpenWhere } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; import { DragManager } from "./DragManager"; import { MakeTemplate } from "./DropConverter"; +import { FollowLinkScript } from "./LinkFollower"; import { LinkManager } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; import { ColorScheme } from "./SettingsManager"; @@ -271,7 +272,7 @@ export class CurrentUserUtils { {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, - {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, onClick: ScriptField.MakeScript("followLink(this,altKey)", {altKey:"boolean"}) }}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, onClick: FollowLinkScript()}}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}}, diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index f5aae6632..d4d7c66f5 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,9 +1,9 @@ -import { action, runInAction } from 'mobx'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; +import { action, observable, runInAction } from 'mobx'; +import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc'; +import { ScriptField } from '../../fields/ScriptField'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; -import { DocumentDecorations } from '../views/DocumentDecorations'; -import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/nodes/DocumentView'; +import { DocFocusOptions, OpenWhere } from '../views/nodes/DocumentView'; import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; @@ -23,18 +23,19 @@ import { UndoManager } from './UndoManager'; * - user defined kvps */ export class LinkFollower { + @observable public static IsFollowing = false; // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, altKey: boolean) => { const batch = UndoManager.StartBatch('follow link click'); - runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value + runInAction(() => (LinkFollower.IsFollowing = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value LinkFollower.traverseLink( linkDoc, sourceDoc, action(() => { batch.end(); - Doc.AddUnHighlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false))); + Doc.AddUnHighlightWatcher(action(() => (LinkFollower.IsFollowing = false))); }), altKey ? true : undefined ); @@ -124,3 +125,10 @@ ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { SelectionManager.DeselectAll(); LinkFollower.FollowLink(undefined, doc, altKey); }); + +export function FollowLinkScript() { + return ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); +} +export function IsFollowLinkScript(field: FieldResult) { + return ScriptCast(field)?.script.originalScript.includes('followLink('); +} diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 7e1b32398..8c2ced55a 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,16 +1,17 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction, trace } from 'mobx'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, DocCast, NumCast, ScriptCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs, DocUtils } from '../documents/Documents'; import { DragManager } from '../util/DragManager'; +import { IsFollowLinkScript } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; @@ -24,11 +25,9 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; +import { PinProps } from './nodes/trails'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); -import { DocumentType } from '../documents/DocumentTypes'; -import { FontIconBox } from './nodes/button/FontIconBox'; -import { PinProps } from './nodes/trails'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -238,7 +237,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); }; - const followLink = ScriptCast(targetDoc?.onClick)?.script.originalScript.includes('followLink('); + const followLink = IsFollowLinkScript(targetDoc?.onClick); return !targetDoc ? null : ( Set onClick to follow primary link
}>
(DocumentV (link.linkDisplay = !ScriptCast(this.props.views().lastElement()?.rootDoc.onClick)?.script.originalScript.includes('followLink('))} + linkCreated={link => (link.linkDisplay = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))} linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this.props.views().lastElement()?.rootDoc} /> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9bc583ce5..9ffbe083f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -32,6 +32,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); import { RichTextField } from '../../fields/RichTextField'; +import { LinkFollower } from '../util/LinkFollower'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -83,10 +84,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); } - @observable overrideBounds = false; @computed get Bounds() { - if (this.overrideBounds) return { x: 0, y: 0, r: 0, b: 0 }; + if (LinkFollower.IsFollowing) return { x: 0, y: 0, r: 0, b: 0 }; const views = SelectionManager.Views(); return views .filter(dv => dv.props.renderDepth > 0) @@ -908,7 +908,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P {this._isRotating ? null : ( tap to set rotate center, drag to rotate
}>
e.preventDefault()}> - } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> + } color={Colors.LIGHT_GRAY} />
)} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 6b4f8ad85..ede387927 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -15,6 +15,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { AnchorMenu } from './pdf/AnchorMenu'; import React = require('react'); import { ScriptField } from '../../fields/ScriptField'; +import { FollowLinkScript } from '../util/LinkFollower'; const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { @@ -148,7 +149,7 @@ export class MarqueeAnnotator extends React.Component { const anno = savedAnnos[0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; const marqueeAnno = Docs.Create.FreeformDocument([], { - onClick: isLinkButton ? ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }) : undefined, + onClick: isLinkButton ? FollowLinkScript() : undefined, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title, diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 0723966e1..031d501ad 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -21,6 +21,7 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView'; import { pasteImageBitmap } from './nodes/WebBoxRenderer'; import './PropertiesButtons.scss'; import React = require('react'); +import { IsFollowLinkScript } from '../util/LinkFollower'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -289,7 +290,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { .filter(dv => dv.docView) .map(dv => dv.docView!) .forEach(docView => { - const linkButton = ScriptCast(docView.props.Document.onClick)?.script.originalScript.includes('followLink('); + const linkButton = IsFollowLinkScript(docView.props.Document.onClick); docView.noOnClick(); switch (onClick) { case 'enterPortal': @@ -327,7 +328,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { ]; const list = buttonList.map(value => { const click = () => this.handleOptionChange(value[0]); - const linkButton = BoolCast(ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('followLink(')); + const linkButton = IsFollowLinkScript(this.selectedDoc.onClick); const followLoc = this.selectedDoc._followLinkLocation; const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 8d67f0528..b950b4860 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -1,13 +1,15 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; import { Shadows } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; +import { IsFollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { ColorScheme, SettingsManager } from '../util/SettingsManager'; @@ -18,13 +20,10 @@ import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; import { DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; +import { KeyValueBox } from './nodes/KeyValueBox'; import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); -import { KeyValueBox } from './nodes/KeyValueBox'; -import { listSpec } from '../../fields/Schema'; -import { AudioField } from '../../fields/URLField'; -import { Tooltip } from '@material-ui/core'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', @@ -279,8 +278,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { - if (ScriptCast(anchorDoc.onClick)?.script.originalScript.includes('followLink(')) { + if (IsFollowLinkScript(anchorDoc.onClick)) { LinkFollower.FollowLink(undefined, anchorDoc, false); } const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 2d12005fb..dbcfe43cf 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -146,7 +146,7 @@ export class DocumentContentsView extends React.Component< (this.props.LayoutTemplateString && this.props.Document) || (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); - return Doc.expandTemplateLayout(template, this.props.Document, this.props.layoutKey); + return Doc.expandTemplateLayout(template, this.props.Document); } CreateBindings(onClick: Opt, onInput: Opt): JsxBindings { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a39c3cf8f..f161a7b9b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -22,7 +22,7 @@ import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; -import { LinkFollower } from '../../util/LinkFollower'; +import { FollowLinkScript, LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; @@ -560,14 +560,14 @@ export class DocumentViewInternal extends DocComponent { const hadOnClick = this.rootDoc.onClick; this.noOnClick(); - this.Document.onClick = hadOnClick ? undefined : ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); + this.Document.onClick = hadOnClick ? undefined : FollowLinkScript(); this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never'; }; @undoBatch @action followLinkOnClick = (): void => { this.Document.ignoreClick = false; - this.Document.onClick = ScriptField.MakeScript('followLink(this,altKey)', { altKey: 'boolean' }); + this.Document.onClick = FollowLinkScript(); this.Document.followLinkToggle = false; this.Document.followLinkZoom = false; this.Document.followLinkLocation = undefined; @@ -623,7 +623,7 @@ export class DocumentViewInternal extends DocComponent { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 83ad5628c..b144c9318 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -34,6 +34,7 @@ import { RecordingBox } from './RecordingBox'; import { PinProps, PresBox } from './trails'; import './VideoBox.scss'; import { ScriptField } from '../../../fields/ScriptField'; +import { FollowLinkScript } from '../../util/LinkFollower'; const path = require('path'); /** @@ -325,7 +326,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { updateDoc = action((dashDoc: Doc) => { this._dashDoc = dashDoc; - this._finalLayout = this.props.docId ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey); + this._finalLayout = this.props.docId ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc); if (this._finalLayout) { if (!Doc.AreProtosEqual(this._finalLayout, dashDoc)) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 809315963..677c4662b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -64,6 +64,7 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); +import { IsFollowLinkScript } from '../../../util/LinkFollower'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; @@ -1933,7 +1934,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 3a5ba4f75..662792ff0 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -906,57 +906,40 @@ export namespace Doc { saveAs(content, doc.title + '.zip'); // glr: Possibly change the name of the document to match the title? }); } - // - // Determines whether the layout needs to be expanded (as a template). - // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template - // - export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) { - return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc; - } const _pendingMap: Map = new Map(); // // Returns an expanded template layout for a target data document if there is a template relationship // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties // of the original layout while allowing for individual layout properties to be overridden in the expanded layout. - // templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key. - // NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field - // so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and - // the derefence will then occur on the rootDocument (the original document). - // in the future, field references could be written as @ and then arguments would be passed in the layout key as: - // layout_mytemplate(somparam=somearg). - // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument - export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) { - const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || ''; - if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc; - - const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders + export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) { + // nothing to do if the layout isn't a template or we don't have a target that's different than the template + if (!targetDoc || templateLayoutDoc === targetDoc || (!templateLayoutDoc.isTemplateForField && !templateLayoutDoc.isTemplateDoc)) { + return templateLayoutDoc; + } + + const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutFieldKey(templateLayoutDoc)); // the field that the template renders // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc // using the template layout doc's id as the field key. // If it doesn't find the expanded layout, then it makes a delegate of the template layout and // saves it on the data doc indexed by the template layout's id. // - const params = args.split('=').length > 1 ? args.split('=')[0] : ''; - const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); - const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']'; + const expandedLayoutFieldKey = templateField + '-layout[' + templateLayoutDoc[Id] + ']'; let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; if (templateLayoutDoc.resolvedDataDoc instanceof Promise) { expandedTemplateLayout = undefined; _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); - } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { + } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey)) { if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype if (!targetDoc[expandedLayoutFieldKey]) { - _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true); + _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); setTimeout( action(() => { const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, '[' + templateLayoutDoc.title + ']'); - // the template's arguments are stored in params which is derefenced to find - // the actual field key where the parameterized template data is stored. - newLayoutDoc[params] = args !== '...' ? args : ''; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances newLayoutDoc.rootDocument = targetDoc; const dataDoc = Doc.GetProto(targetDoc); newLayoutDoc.resolvedDataDoc = dataDoc; @@ -965,7 +948,7 @@ export namespace Doc { } targetDoc[expandedLayoutFieldKey] = newLayoutDoc; - _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); + _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey); }) ); } @@ -982,7 +965,7 @@ export namespace Doc { return { layout: childDoc, data: childDoc }; } const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc; - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '()'), data: resolvedDataDoc }; + return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc }; } export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { -- cgit v1.2.3-70-g09d2 From db582e135fceb6162d0c9cf00e2580fb1349fddb Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 13 Apr 2023 01:13:33 -0400 Subject: added text edits --- src/client/apis/gpt/GPT.ts | 87 ++++++++++ src/client/apis/gpt/Summarization.ts | 48 ------ src/client/views/nodes/WebBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 31 ++-- src/client/views/pdf/AnchorMenu.tsx | 147 +++++++++++++--- src/client/views/pdf/GPTPopup.scss | 123 -------------- src/client/views/pdf/GPTPopup.tsx | 131 --------------- src/client/views/pdf/GPTPopup/GPTPopup.scss | 132 +++++++++++++++ src/client/views/pdf/GPTPopup/GPTPopup.tsx | 186 +++++++++++++++++++++ src/client/views/pdf/PDFViewer.tsx | 2 +- 10 files changed, 547 insertions(+), 342 deletions(-) create mode 100644 src/client/apis/gpt/GPT.ts delete mode 100644 src/client/apis/gpt/Summarization.ts delete mode 100644 src/client/views/pdf/GPTPopup.scss delete mode 100644 src/client/views/pdf/GPTPopup.tsx create mode 100644 src/client/views/pdf/GPTPopup/GPTPopup.scss create mode 100644 src/client/views/pdf/GPTPopup/GPTPopup.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts new file mode 100644 index 000000000..4b3960902 --- /dev/null +++ b/src/client/apis/gpt/GPT.ts @@ -0,0 +1,87 @@ +import { Configuration, OpenAIApi } from 'openai'; + +enum GPTCallType { + SUMMARY = 'summary', + COMPLETION = 'completion', + EDIT = 'edit', +} + +type GPTCallOpts = { + model: string; + maxTokens: number; + temp: number; + prompt: string; +}; + +const callTypeMap: { [type: string]: GPTCallOpts } = { + summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text briefly: ' }, + edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, +}; + +/** + * Calls the OpenAI API. + * + * @param inputText Text to process + * @returns AI Output + */ +const gptAPICall = async (inputText: string, callType: GPTCallType) => { + if (callType === GPTCallType.SUMMARY) inputText += '.'; + const opts: GPTCallOpts = callTypeMap[callType]; + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createCompletion({ + model: opts.model, + max_tokens: opts.maxTokens, + temperature: opts.temp, + prompt: `${opts.prompt}${inputText}`, + }); + console.log(response.data.choices[0]); + return response.data.choices[0].text; + } catch (err) { + console.log(err); + return 'Error connecting with API.'; + } +}; + +const gptImageCall = async (prompt: string) => { + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createImage({ + prompt: prompt, + n: 1, + size: '1024x1024', + }); + return response.data.data[0].url; + } catch (err) { + console.error(err); + return; + } +}; + +// const gptEditCall = async (selectedText: string, fullText: string) => { +// try { +// const configuration = new Configuration({ +// apiKey: process.env.OPENAI_KEY, +// }); +// const openai = new OpenAIApi(configuration); +// const response = await openai.createCompletion({ +// model: 'text-davinci-003', +// max_tokens: 256, +// temperature: 0.1, +// prompt: `Replace the phrase ${selectedText} inside of ${fullText}.`, +// }); +// return response.data.choices[0].text.trim(); +// } catch (err) { +// console.log(err); +// return 'Error connecting with API.'; +// } +// }; + +export { gptAPICall, gptImageCall, GPTCallType }; diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts deleted file mode 100644 index 0d9b5dfcd..000000000 --- a/src/client/apis/gpt/Summarization.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Configuration, OpenAIApi } from 'openai'; - -enum GPTCallType { - SUMMARY = 'summary', - COMPLETION = 'completion', -} - -type GPTCallOpts = { - model: string; - maxTokens: number; - temp: number; - prompt: string; -}; - -const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: 'Summarize this text: ' }, - completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, -}; - -/** - * Calls the OpenAI API. - * - * @param inputText Text to process - * @returns AI Output - */ -const gptAPICall = async (inputText: string, callType: GPTCallType) => { - if (callType === GPTCallType.SUMMARY) inputText += '.'; - const opts: GPTCallOpts = callTypeMap[callType]; - try { - const configuration = new Configuration({ - apiKey: process.env.OPENAI_KEY, - }); - const openai = new OpenAIApi(configuration); - const response = await openai.createCompletion({ - model: opts.model, - max_tokens: opts.maxTokens, - temperature: opts.temp, - prompt: `${opts.prompt}${inputText}`, - }); - return response.data.choices[0].text; - } catch (err) { - console.log(err); - return 'Error connecting with API.'; - } -}; - - -export { gptAPICall, GPTCallType}; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index e5ef85b5a..abde5a9ea 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -36,7 +36,7 @@ import { PinProps, PresBox } from './trails'; import './WebBox.scss'; import React = require('react'); import { DragManager } from '../../util/DragManager'; -import { GPTPopup } from '../pdf/GPTPopup'; +import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 4ad1f73b0..3eae47f49 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -23,7 +23,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; -import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization'; +import { gptAPICall, GPTCallType, gptImageCall } from '../../../apis/gpt/GPT'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -891,26 +891,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); try { - const configuration = new Configuration({ - apiKey: process.env.OPENAI_KEY, - }); - const openai = new OpenAIApi(configuration); - const response = await openai.createImage({ - prompt: (this.dataDoc.text as RichTextField)?.Text, - n: 1, - size: '1024x1024', - }); - let image_url = response.data.data[0].url; - console.log(image_url); + let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); if (image_url) { const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] }); const source = Utils.prepend(accessPaths.agnostic.client); const newDoc = Docs.Create.ImageDocument(source, { - x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, - y: NumCast(this.rootDoc.y), - _height: 200, - _width: 200, - }) + x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, + y: NumCast(this.rootDoc.y), + _height: 200, + _width: 200, + }); if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { newDoc.overlayX = this.rootDoc.x; newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); @@ -919,7 +909,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { @@ -46,27 +47,55 @@ export class AnchorMenu extends AntimodeMenu { @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions - @observable private summarizedText: string = ''; + @observable private GPTpopupText: string = ''; @observable private loadingSummary: boolean = false; @observable private showGPTPopup: boolean = false; + @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY; + @observable private selectedText: string = ''; + @observable private editorView?: EditorView; + @observable private textDoc?: Doc; + @observable private highlightRange: number[] | undefined; + private selectionRange: number[] | undefined; + @action setGPTPopupVis = (vis: boolean) => { this.showGPTPopup = vis; }; @action - setSummarizedText = (txt: string) => { - this.summarizedText = txt; + setGPTMode = (mode: GPTPopupMode) => { + this.GPTMode = mode; + }; + + @action + setGPTPopupText = (txt: string) => { + this.GPTpopupText = txt; }; + @action setLoading = (loading: boolean) => { this.loadingSummary = loading; }; - private selectedText: string = ''; + @action + setHighlightRange(r: number[] | undefined) { + this.highlightRange = r; + } + + @action public setSelectedText = (txt: string) => { this.selectedText = txt; }; + @action + public setEditorView = (editor: EditorView) => { + this.editorView = editor; + }; + + @action + public setTextDoc = (textDoc: Doc) => { + this.textDoc = textDoc; + }; + public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; @@ -104,7 +133,7 @@ export class AnchorMenu extends AntimodeMenu { if (!opacity) { this._showLinkPopup = false; this.setGPTPopupVis(false); - this.setSummarizedText(''); + this.setGPTPopupText(''); } }, { fireImmediately: true } @@ -114,20 +143,20 @@ export class AnchorMenu extends AntimodeMenu { selected => { this._showLinkPopup = false; this.setGPTPopupVis(false); - this.setSummarizedText(''); + this.setGPTPopupText(''); AnchorMenu.Instance.fadeOut(true); } ); } /** - * Returns a mock summary simulating an API call. + * Returns a mock api response. * @returns A Promise that resolves into a string */ - mockSummarize = async (): Promise => { + mockGPTCall = async (): Promise => { return new Promise((resolve, reject) => { setTimeout(() => { - resolve('Mock summary. This is a summary of the highlighted text.'); + resolve('test'); }, 1000); }); }; @@ -137,19 +166,68 @@ export class AnchorMenu extends AntimodeMenu { * @param e pointer down event */ gptSummarize = async (e: React.PointerEvent) => { + this.setHighlightRange(undefined); + this.setGPTPopupVis(true); + this.setGPTMode(GPTPopupMode.SUMMARY); + this.setLoading(true); + + try { + const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); + if (res) { + this.setGPTPopupText(res); + } else { + this.setGPTPopupText('Something went wrong.'); + } + } catch (err) { + console.error(err); + } + + this.setLoading(false); + }; + + /** + * Makes a GPT call to edit selected text. + * @returns nothing + */ + gptEdit = async () => { + if (!this.editorView) return; + this.setHighlightRange(undefined); + const state = this.editorView.state; + const sel = state.selection; + const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n'); + const selectedText = state.doc.textBetween(sel.from, sel.to); + this.setGPTPopupVis(true); + this.setGPTMode(GPTPopupMode.EDIT); this.setLoading(true); - const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); - // const res = await this.mockSummarize(); - if (res) { - this.setSummarizedText(res); - } else { - this.setSummarizedText('Something went wrong.'); + + try { + let res = await gptAPICall(selectedText, GPTCallType.EDIT); + // let res = await this.mockGPTCall(); + res = res.trim(); + const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to); + + if (res) { + this.setGPTPopupText(resultText); + this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]); + } else { + this.setGPTPopupText('Something went wrong.'); + } + } catch (err) { + console.error(err); } this.setLoading(false); }; + /** + * Replaces text suggestions from GPT. + */ + replaceText = (replacement: string) => { + if (!this.editorView || !this.textDoc) return; + this.textDoc.text = replacement; + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -250,7 +328,19 @@ export class AnchorMenu extends AntimodeMenu { canSummarize = (): boolean => { const docs = SelectionManager.Docs(); if (docs.length > 0) { - return docs[0].type === 'pdf' || docs[0].type === 'web'; + return docs.some(doc => doc.type === 'pdf' || doc.type === 'web'); + } + return false; + }; + + /** + * Returns whether the selected text can be edited. + * @returns Whether the GPT icon for summarization should appear + */ + canEdit = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs.some(doc => doc.type === 'rtf'); } return false; }; @@ -273,7 +363,17 @@ export class AnchorMenu extends AntimodeMenu { )} - + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}> )} + {this.canEdit() && ( + Edit text with AI
}> + + + )} Find document to link to selected text
}> - - - ) : ( -
- - - -
- )} -
- )} - {this.summaryDone && ( -
- - AI generated responses can contain inaccurate or misleading content.{' '} - - Learn More - -
- )} -
- ); - } -} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss new file mode 100644 index 000000000..50fbe5211 --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -0,0 +1,132 @@ +$textgrey: #707070; +$lighttextgrey: #a3a3a3; +$greyborder: #d3d3d3; +$lightgrey: #ececec; +$button: #5b97ff; +$highlightedText: #82e0ff; + +.summary-box { + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #ffffff; + box-shadow: 0 2px 5px #7474748d; + color: $textgrey; + position: fixed; + bottom: 5px; + right: 5px; + width: 250px; + min-height: 200px; + border-radius: 15px; + padding: 15px; + padding-bottom: 0; + z-index: 999; + + .summary-heading { + display: flex; + 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; + } + + .ai-warning { + padding: 10px 0; + font-size: 10px; + color: $lighttextgrey; + border-top: 1px solid $greyborder; + } + + .highlighted-text { + background-color: $highlightedText; + } +} + +// 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/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx new file mode 100644 index 000000000..91bc0a7ff --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -0,0 +1,186 @@ +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; +import { Doc } from '../../../../fields/Doc'; +import { Docs } from '../../../documents/Documents'; +import './GPTPopup.scss'; + +export enum GPTPopupMode { + SUMMARY, + EDIT, +} + +interface GPTPopupProps { + visible: boolean; + text: string; + loading: boolean; + mode: GPTPopupMode; + callSummaryApi: (e: React.PointerEvent) => Promise; + callEditApi: (e: React.PointerEvent) => Promise; + replaceText: (replacement: string) => void; + highlightRange?: number[]; +} + +@observer +export class GPTPopup extends React.Component { + static Instance: GPTPopup; + + @observable + private done: boolean = false; + @observable + private sidebarId: string = ''; + + @action + public setDone = (done: boolean) => { + this.done = done; + }; + @action + public setSidebarId = (id: string) => { + this.sidebarId = id; + }; + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; + + /** + * Transfers the summarization text to a sidebar annotation text document. + */ + private transferToText = () => { + const newDoc = Docs.Create.TextDocument(this.props.text.trim(), { + _width: 200, + _height: 50, + _fitWidth: true, + _autoHeight: true, + }); + this.addDoc(newDoc, this.sidebarId); + }; + + constructor(props: GPTPopupProps) { + super(props); + GPTPopup.Instance = this; + } + + componentDidUpdate = () => { + if (this.props.loading) { + this.setDone(false); + } + }; + + summaryBox = () => ( + <> +
+ {this.heading('SUMMARY')} +
+ {!this.props.loading && + (!this.done ? ( + { + setTimeout(() => { + this.setDone(true); + }, 500); + }}> + {this.props.text} + + ) : ( + this.props.text + ))} +
+
+ {!this.props.loading && ( +
+ {this.done ? ( + <> + + + + ) : ( +
+ Summarizing + + +
+ )} +
+ )} + + ); + + editBox = () => { + const hr = this.props.highlightRange; + return ( + hr && ( + <> +
+ {this.heading('TEXT EDIT SUGGESTIONS')} +
+
+ {this.props.text.slice(0, hr[0])} {this.props.text.slice(hr[0], hr[1])} {this.props.text.slice(hr[1])} +
+
+
+ {!this.props.loading && ( +
+ <> + + + +
+ )} + {this.aiWarning()} + + ) + ); + }; + + aiWarning = () => + this.done ? ( +
+ + AI generated responses can contain inaccurate or misleading content. +
+ ) : ( + <> + ); + + heading = (headingText: string) => ( +
+ + {this.props.loading && } +
+ ); + + render() { + return ( +
+ {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} +
+ ); + } +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3f891789a..d17d0e13c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -24,7 +24,7 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); -import { GPTPopup } from './GPTPopup'; +import { GPTPopup } from './GPTPopup/GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; -- cgit v1.2.3-70-g09d2 From 5cd64622f14ede408d3baca4a10d155b60392e46 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 13 Apr 2023 22:22:07 -0400 Subject: lots of changes to get rid of ContainingCollectionDoc and ContainingCollectionView props. --- src/client/documents/Documents.ts | 8 +-- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DocumentManager.ts | 83 ++++++++++------------ src/client/util/DragManager.ts | 4 +- .../util/Import & Export/DirectoryImportBox.tsx | 6 +- src/client/util/ReplayMovements.ts | 64 ++++++----------- src/client/util/SelectionManager.ts | 6 +- src/client/util/SharingManager.tsx | 5 +- src/client/util/TrackMovements.ts | 36 +++++----- src/client/views/DashboardView.tsx | 17 ++--- src/client/views/DocComponent.tsx | 1 - src/client/views/DocumentDecorations.tsx | 8 +-- src/client/views/GestureOverlay.tsx | 4 +- src/client/views/InkStrokeProperties.ts | 3 +- src/client/views/LightboxView.tsx | 4 +- src/client/views/MainView.tsx | 20 ++---- src/client/views/OverlayView.tsx | 10 +-- src/client/views/Palette.tsx | 31 ++++---- src/client/views/PropertiesDocContextSelector.tsx | 22 ++++-- src/client/views/PropertiesView.tsx | 2 - src/client/views/StyleProvider.tsx | 11 ++- src/client/views/TemplateMenu.tsx | 2 - .../views/collections/CollectionCarouselView.tsx | 1 - src/client/views/collections/CollectionMenu.tsx | 6 +- .../views/collections/CollectionNoteTakingView.tsx | 4 -- .../views/collections/CollectionPileView.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 2 - .../views/collections/CollectionStackingView.tsx | 4 -- .../views/collections/CollectionTreeView.tsx | 10 +-- src/client/views/collections/CollectionView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 6 +- src/client/views/collections/TreeView.tsx | 54 ++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 31 ++++---- .../collectionLinear/CollectionLinearView.tsx | 5 +- .../CollectionMulticolumnView.tsx | 2 - .../CollectionMultirowView.tsx | 2 - .../collectionSchema/CollectionSchemaView.tsx | 6 +- .../collections/collectionSchema/SchemaRowBox.tsx | 2 +- .../collectionSchema/SchemaTableCell.tsx | 2 - src/client/views/linking/LinkMenuItem.tsx | 21 +----- src/client/views/linking/LinkPopup.tsx | 4 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 7 +- src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 12 ++-- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/KeyValuePair.tsx | 2 - src/client/views/nodes/LinkDocPreview.tsx | 4 +- src/client/views/nodes/ScreenshotBox.tsx | 5 +- src/client/views/nodes/button/FontIconBox.tsx | 4 +- .../views/nodes/formattedText/DashDocView.tsx | 7 +- .../views/nodes/formattedText/DashFieldView.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 20 ++++-- src/client/views/nodes/trails/PresBox.tsx | 1 - src/client/views/nodes/trails/PresElementBox.tsx | 27 ++++--- src/client/views/topbar/TopBar.tsx | 51 ++----------- src/mobile/AudioUpload.tsx | 76 ++++++++++---------- src/mobile/MobileInterface.tsx | 4 +- 58 files changed, 291 insertions(+), 456 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8d97ce5af..09b45a481 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1285,10 +1285,6 @@ export namespace DocUtils { return rangeFilteredDocs; } - export function DefaultFocus(doc: Doc, options: DocFocusOptions) { - return undefined; - } - export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { @@ -1299,10 +1295,9 @@ export namespace DocUtils { }); } - export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { + export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, showPopup?: number[]) { if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; const sv = DocumentManager.Instance.getDocumentView(source); - if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return; if (target.doc === Doc.UserDoc()) return undefined; const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { @@ -1371,7 +1366,6 @@ export namespace DocUtils { value: 'any', _readOnly_: 'boolean', scriptContext: 'any', - thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 76ea3e3ea..abf7313a4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -110,7 +110,7 @@ export class CurrentUserUtils { const tempClicks = DocCast(doc[field]); const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ - { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"}, + { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"}, { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ccf370662..ad89e8653 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,15 +1,11 @@ -import { loadAsync } from 'jszip'; -import { action, observable, ObservableSet, runInAction } from 'mobx'; +import { action, observable, ObservableSet } from 'mobx'; import { AnimationSym, Doc, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../fields/Types'; import { AudioField } from '../../fields/URLField'; -import { emptyFunction } from '../../Utils'; import { CollectionViewType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; -import { CollectionView } from '../views/collections/CollectionView'; import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; import { MainView } from '../views/MainView'; @@ -39,7 +35,7 @@ export class DocumentManager { private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => { if (doc) { - const dv = this.getDocumentViewById(doc[Id]); + const dv = this.getDocumentView(doc); this._viewRenderedCbs.push({ doc, func }); if (dv) { this.callAddViewFuncs(dv); @@ -129,42 +125,24 @@ export class DocumentManager { return this.getDocumentViewsById(doc[Id]); } - public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined { - if (!id) return undefined; - let toReturn: DocumentView | undefined; - const passes = preferredCollection ? [preferredCollection, undefined] : [undefined]; - - for (const pass of passes) { - Array.from(DocumentManager.Instance.DocumentViews).map(view => { - if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { - toReturn = view; - return; - } - }); - if (!toReturn) { - Array.from(DocumentManager.Instance.DocumentViews).map(view => { - const doc = view.rootDoc.proto; - if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { - toReturn = view; - } - }); - } else { - break; - } - } - - return toReturn; - } - - public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined { - const found = + public getDocumentView(toFind: Doc | undefined, preferredCollection?: DocumentView): DocumentView | undefined { + const doc = + // bcz: this was temporary code used to match documents by data url instead of by id. intended only for repairing the DB // Array.from(DocumentManager.Instance.DocumentViews).find( // dv => // ((dv.rootDoc.data as any)?.url?.href && (dv.rootDoc.data as any)?.url?.href === (toFind.data as any)?.url?.href) || // ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href) // )?.rootDoc ?? toFind; - return this.getDocumentViewById(found[Id], preferredCollection); + const docViewArray = Array.from(DocumentManager.Instance.DocumentViews); + const passes = !doc ? [] : preferredCollection ? [preferredCollection, undefined] : [undefined]; + return passes.reduce( + (pass, toReturn) => + toReturn ?? + docViewArray.filter(view => view.rootDoc === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection) ?? + docViewArray.filter(view => Doc.GetProto(view.rootDoc) === doc).find(view => !pass || view.props.docViewPath().lastElement() === preferredCollection), + undefined as Opt + ); } public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { @@ -247,7 +225,8 @@ export class DocumentManager { public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => { const docViewPath = targetDocView.docViewPath.slice(); let rootContextView = docViewPath.shift(); - return rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })); + await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }))); + if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden; }; // shows a document by first: @@ -318,15 +297,25 @@ export class DocumentManager { } } } -export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { - const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc); - const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView); - if (dv) { - DocumentManager.Instance.showDocumentView(dv, { willZoomCentered: true }); - } else { - const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null); - const showDoc = context || doc; - DocumentManager.Instance.showDocument(Doc.BestAlias(showDoc), { openLocation: OpenWhere.addRight }, () => DocumentManager.Instance.showDocument(doc, { willZoomCentered: true })); - } +export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) { + const func = () => { + const cv = DocumentManager.Instance.getDocumentView(containingDoc); + const dv = DocumentManager.Instance.getDocumentView(doc, cv); + if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) { + DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc)); + } else { + const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc)); + DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => { + const cv = DocumentManager.Instance.getDocumentView(containingDoc); + const dv = DocumentManager.Instance.getDocumentView(doc, cv); + dv && Doc.linkFollowHighlight(dv.rootDoc); + }); + } + }; + if (doc.hidden) { + doc.hidden = false; + options.toggleTarget = false; + setTimeout(func); + } else func(); } ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 7d2aa813f..7e6de5e67 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -3,10 +3,8 @@ import { DateField } from '../../fields/DateField'; import { Doc, Field, Opt, StrListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; -import { listSpec } from '../../fields/Schema'; -import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ScriptField } from '../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; import { emptyFunction, Utils } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import * as globalCssVariables from '../views/global/globalCssVariables.scss'; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 559958c2b..76b1323c4 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -14,12 +14,14 @@ import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; import { Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; import { DocumentManager } from '../DocumentManager'; import './DirectoryImportBox.scss'; import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; import React = require('react'); +import _ = require('lodash'); const unsupported = ['text/html', 'text/plain']; @@ -155,8 +157,8 @@ export class DirectoryImportBox extends React.Component { x: NumCast(doc.x), y: NumCast(doc.y) + offset, }; - const parent = this.props.ContainingCollectionView; - if (parent) { + const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it. + if (parent?.rootDoc.type === DocumentType.COL) { let importContainer: Doc; if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index d5bffc5e2..40261985a 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -7,6 +7,7 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie import { DocServer } from '../DocServer'; import { Movement, Presentation } from './TrackMovements'; import { OpenWhereMod } from '../views/nodes/DocumentView'; +import { returnTransparent } from '../../Utils'; export class ReplayMovements { private timers: NodeJS.Timeout[] | null; @@ -60,17 +61,11 @@ export class ReplayMovements { return; } - let docIdtoDoc: Map = new Map(); - try { - docIdtoDoc = await this.loadPresentation(presentation); - } catch { - console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements'); - throw 'error loading docs from server'; - } + const docIdtoDoc = this.loadPresentation(presentation); this.videoBoxDisposeFunc = reaction( () => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), - ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements()) + ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, timeViewed) : this.pauseMovements()) ); this.videoBox = videoBox; }; @@ -93,45 +88,33 @@ export class ReplayMovements { this.pauseMovements(); }; - loadPresentation = async (presentation: Presentation) => { + loadPresentation = (presentation: Presentation) => { const { movements } = presentation; if (movements === null) { throw '[recordingApi.ts] followMovements() failed: no presentation data'; } // generate a set of all unique docIds - const docIds = new Set(); - for (const { docId } of movements) { - if (!docIds.has(docId)) docIds.add(docId); - } - - const docIdtoDoc = new Map(); - - let refFields = await DocServer.GetRefFields([...docIds.keys()]); - for (const docId in refFields) { - if (!refFields[docId]) { - throw `one field was undefined`; - } - docIdtoDoc.set(docId, refFields[docId] as Doc); + const docs = new Set(); + for (const { doc } of movements) { + if (!docs.has(doc)) docs.add(doc); } - // console.info('loadPresentation refFields', refFields, docIdtoDoc); - return docIdtoDoc; + return docs; }; // returns undefined if the docView isn't open on the screen - getCollectionFFView = (docId: string) => { - const isInView = DocumentManager.Instance.getDocumentViewById(docId); + getCollectionFFView = (doc: Doc) => { + const isInView = DocumentManager.Instance.getDocumentView(doc); if (isInView) { return isInView.ComponentView as CollectionFreeFormView; } }; // will open the doc in a tab then return the CollectionFFView that holds it - openTab = (docId: string, docIdtoDoc: Map) => { - const doc = docIdtoDoc.get(docId); - if (doc == undefined) { - console.error(`docIdtoDoc did not contain docId ${docId}`); + openTab = (doc: Doc) => { + if (doc === undefined) { + console.error(`doc undefined`); return undefined; } // console.log('openTab', docId, doc); @@ -149,13 +132,12 @@ export class ReplayMovements { document.Document._panY = panY; }; - getFirstMovements = (movements: Movement[]): Map => { + getFirstMovements = (movements: Movement[]): Map => { if (movements === null) return new Map(); // generate a set of all unique docIds - const docIdtoFirstMove = new Map(); + const docIdtoFirstMove = new Map(); for (const move of movements) { - const { docId } = move; - if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move); + if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move); } return docIdtoFirstMove; }; @@ -165,7 +147,7 @@ export class ReplayMovements { Doc.UserDoc().presentationMode = 'none'; }; - public playMovements = (presentation: Presentation, docIdtoDoc: Map, timeViewed: number = 0) => { + public playMovements = (presentation: Presentation, timeViewed: number = 0) => { // console.info('playMovements', presentation, timeViewed, docIdtoDoc); if (presentation.movements === null || presentation.movements.length === 0) { @@ -183,13 +165,13 @@ export class ReplayMovements { const handleFirstMovements = () => { // if the first movement is a closed tab, open it const firstMovement = filteredMovements[0]; - const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined; - if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc); + const isClosed = this.getCollectionFFView(firstMovement.doc) === undefined; + if (isClosed) this.openTab(firstMovement.doc); // for the open tabs, set it to the first move const docIdtoFirstMove = this.getFirstMovements(filteredMovements); - for (const [docId, firstMove] of docIdtoFirstMove) { - const colFFView = this.getCollectionFFView(docId); + for (const [doc, firstMove] of docIdtoFirstMove) { + const colFFView = this.getCollectionFFView(doc); if (colFFView) this.zoomAndPan(firstMove, colFFView); } }; @@ -200,12 +182,12 @@ export class ReplayMovements { const timeDiff = movement.time - timeViewed * 1000; return setTimeout(() => { - const collectionFFView = this.getCollectionFFView(movement.docId); + const collectionFFView = this.getCollectionFFView(movement.doc); if (collectionFFView) { this.zoomAndPan(movement, collectionFFView); } else { // tab wasn't open - open it and play the movement - const openedColFFView = this.openTab(movement.docId, docIdtoDoc); + const openedColFFView = this.openTab(movement.doc); console.log('openedColFFView', openedColFFView); openedColFFView && this.zoomAndPan(movement, openedColFFView); } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 313c255a0..bfad93334 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,4 +1,3 @@ -import { ModalManager } from '@material-ui/core'; import { action, observable, ObservableMap } from 'mobx'; import { computedFn } from 'mobx-utils'; import { Doc, Opt } from '../../fields/Doc'; @@ -64,8 +63,9 @@ export namespace SelectionManager { export function DeselectView(docView?: DocumentView): void { manager.DeselectView(docView); } - export function SelectView(docView: DocumentView, ctrlPressed: boolean): void { - manager.SelectView(docView, ctrlPressed); + export function SelectView(docView: DocumentView | undefined, ctrlPressed: boolean): void { + if (!docView) DeselectAll(); + else manager.SelectView(docView, ctrlPressed); } export function SelectSchemaViewDoc(document: Opt, deselectAllFirst?: boolean): void { if (deselectAllFirst) manager.DeselectAll(); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 3c05af4bb..a73eda04c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -370,11 +370,10 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc]; return ( { - let context: Opt; - if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) { + if (this.targetDoc && this.targetDocView && docs.length === 1) { DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true }); } }} diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index 4a2ccd706..2f16307b3 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -9,7 +9,7 @@ export type Movement = { panX: number; panY: number; scale: number; - docId: string; + doc: Doc; }; export type Presentation = { @@ -28,7 +28,7 @@ export class TrackMovements { private tracking: boolean; private absoluteStart: number; // instance variable for holding the FFViews and their disposers - private recordingFFViews: Map | null; + private recordingFFViews: Map | null; private tabChangeDisposeFunc: IReactionDisposer | null; // create static instance and getter for global use @@ -55,33 +55,33 @@ export class TrackMovements { return this.currentPresentation.movements === null; } - private addRecordingFFView(doc: Doc, key: string = doc[Id]): void { + private addRecordingFFView(doc: Doc): void { // console.info('adding dispose func : docId', key, 'doc', doc); if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; } - if (this.recordingFFViews.has(key)) { - console.warn('addFFView : key already in map'); + if (this.recordingFFViews.has(doc)) { + console.warn('addFFView : doc already in map'); return; } const disposeFunc = reaction( () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }), - res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale) + res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, doc, res.scale) ); - this.recordingFFViews?.set(key, disposeFunc); + this.recordingFFViews?.set(doc, disposeFunc); } - private removeRecordingFFView = (key: string) => { + private removeRecordingFFView = (doc: Doc) => { // console.info('removing dispose func : docId', key); if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } - this.recordingFFViews.get(key)?.(); - this.recordingFFViews.delete(key); + this.recordingFFViews.get(doc)?.(); + this.recordingFFViews.delete(doc); }; // in the case where only one tab was changed (updates not across dashboards), set only one to true @@ -90,15 +90,15 @@ export class TrackMovements { // so that the size comparisons are correct, we must filter to only the FFViews const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform'; - const tabbedFFViews = new Set(); + const tabbedFFViews = new Set(); for (const DashDoc of tabbedDocs) { - if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); + if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc); } // new tab was added - need to add it if (tabbedFFViews.size > this.recordingFFViews.size) { for (const DashDoc of tabbedDocs) { - if (!this.recordingFFViews.has(DashDoc[Id])) { + if (!this.recordingFFViews.has(DashDoc)) { if (isFFView(DashDoc)) { this.addRecordingFFView(DashDoc); @@ -110,9 +110,9 @@ export class TrackMovements { } // tab was removed - need to remove it from recordingFFViews else if (tabbedFFViews.size < this.recordingFFViews.size) { - for (const [key] of this.recordingFFViews) { - if (!tabbedFFViews.has(key)) { - this.removeRecordingFFView(key); + for (const [doc] of this.recordingFFViews) { + if (!tabbedFFViews.has(doc)) { + this.removeRecordingFFView(doc); if (onlyOne) return; } } @@ -214,7 +214,7 @@ export class TrackMovements { } }; - private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => { + private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => { // ensure we are recording to track if (!this.tracking) { console.error('[recordingApi.ts] trackMovements(): tracking is false'); @@ -231,7 +231,7 @@ export class TrackMovements { // get the time const time = new Date().getTime() - this.absoluteStart; // make new movement object - const movement: Movement = { time, panX, panY, scale, docId }; + const movement: Movement = { time, panX, panY, scale, doc }; // add that movement to the current presentation data's movement array this.currentPresentation.movements && this.currentPresentation.movements.push(movement); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 2b586b0e2..dee161931 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -109,17 +109,8 @@ export class DashboardView extends React.Component { />
-
); @@ -163,7 +154,7 @@ export class DashboardView extends React.Component {
-
this.selectDashboardGroup(DashboardGroup.MyDashboards)}> My Dashboards @@ -196,7 +187,7 @@ export class DashboardView extends React.Component { e.stopPropagation(); this.onContextMenu(dashboard, e); }}> - } /> +
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 9fc1487a0..a59189fd2 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -52,7 +52,6 @@ export function DocComponent

() { interface ViewBoxBaseProps { Document: Doc; DataDoc?: Doc; - ContainingCollectionDoc: Opt; DocumentView?: () => DocumentView; fieldKey: string; isSelected: (outsideReaction?: boolean) => boolean; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9ffbe083f..9a4aaf00e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -33,6 +33,7 @@ import { ImageBox } from './nodes/ImageBox'; import React = require('react'); import { RichTextField } from '../../fields/RichTextField'; import { LinkFollower } from '../util/LinkFollower'; +import _ = require('lodash'); @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -303,8 +304,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P SelectionManager.DeselectAll(); }; - onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false); - + onSelectorClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false); /** * Handles setting up events when user clicks on the border radius editor * @param e PointerEvent @@ -744,7 +744,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P seldocview.props.hideDeleteButton || seldocview.rootDoc.hideDeleteButton || SelectionManager.Views().some(docView => { - const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; + const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DataSym]) : AclEdit; return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); }); @@ -865,7 +865,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P

e.preventDefault()} />
e.preventDefault()} /> - {seldocview.props.renderDepth <= 1 || !seldocview.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} + {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} )} {useRounding && ( diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0feccb742..13faae783 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1010,14 +1010,12 @@ export class GestureOverlay extends Touchable { renderDepth={0} styleProvider={returnEmptyString} docViewPath={returnEmptyDoclist} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docRangeFilters={returnEmptyFilter} docFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> ); }; diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index e6df0801c..156825f41 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -373,10 +373,11 @@ export class InkStrokeProperties { snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => { const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const containingDocView = inkView.props.CollectionFreeFormDocumentView?.().props.DocumentView?.(); containingCollection?.childDocs .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { - const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView); + const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView); const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []); if (snapped && snapped.distance < snapData.distance) { const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 976c8763e..69eec8456 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -266,8 +266,6 @@ export class LightboxView extends React.Component { docFilters={this.docFilters} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} @@ -275,7 +273,7 @@ export class LightboxView extends React.Component { pinToPres={TabDocView.PinDoc} bringToFront={emptyFunction} onBrowseClick={MainView.Instance.exploreMode} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 60459cf30..0384d925e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -595,14 +595,12 @@ export class MainView extends React.Component { PanelWidth={this.headerBarDocWidth} PanelHeight={this.headerBarDocHeight} renderDepth={0} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
); @@ -626,14 +624,12 @@ export class MainView extends React.Component { ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity} PanelWidth={this.mainDocViewWidth} PanelHeight={this.mainDocViewHeight} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} suppressSetHeight={true} renderDepth={this._hideUI ? 0 : -1} /> @@ -727,14 +723,12 @@ export class MainView extends React.Component { renderDepth={0} isContentActive={returnTrue} scriptContext={CollectionDockingView.Instance?.props.Document} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
{this.docButtons} @@ -758,7 +752,7 @@ export class MainView extends React.Component { PanelHeight={this.leftMenuHeight} renderDepth={0} docViewPath={returnEmptyDoclist} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={DefaultStyleProvider} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} @@ -766,8 +760,6 @@ export class MainView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} scriptContext={this} /> @@ -900,13 +892,11 @@ export class MainView extends React.Component { PanelWidth={this.leftMenuFlyoutWidth} PanelHeight={this.leftMenuFlyoutHeight} renderDepth={0} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ?
{StrCast(this.userDoc?.presentationMode)}
: <>} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index ec22128d4..bdc48d03a 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, trace } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; @@ -10,15 +10,13 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, retu import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; import { LightboxView } from './LightboxView'; import { MainView } from './MainView'; import { DocumentView } from './nodes/DocumentView'; import './OverlayView.scss'; -import { ScriptingRepl } from './ScriptingRepl'; -import { DefaultStyleProvider, testDocProps } from './StyleProvider'; +import { DefaultStyleProvider } from './StyleProvider'; export type OverlayDisposer = () => void; @@ -223,7 +221,7 @@ export class OverlayView extends React.Component { isDocumentActive={returnTrue} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} addDocTab={d.type === DocumentType.PRES ? MainView.addDocTabFunc : returnFalse} @@ -231,8 +229,6 @@ export class OverlayView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> ); diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 954529bc9..3ad28c418 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -1,12 +1,12 @@ -import { IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc } from "../../fields/Doc"; -import { NumCast } from "../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { DocumentView } from "./nodes/DocumentView"; -import "./Palette.scss"; +import { IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc } from '../../fields/Doc'; +import { NumCast } from '../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from '../../Utils'; +import { Transform } from '../util/Transform'; +import { DocumentView } from './nodes/DocumentView'; +import './Palette.scss'; export interface PaletteProps { x: number; @@ -23,20 +23,20 @@ export default class Palette extends React.Component { componentDidMount = () => { this._selectedDisposer = reaction( () => NumCast(this.props.thumbDoc.selectedIndex), - (i) => this._selectedIndex = i, + i => (this._selectedIndex = i), { fireImmediately: true } ); - } + }; componentWillUnmount = () => { this._selectedDisposer?.(); - } + }; render() { return (
-
+
{ docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> + />
); } -} \ No newline at end of file +} diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 2c7da5931..93eec61f3 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -22,9 +22,9 @@ export class PropertiesDocContextSelector extends React.Component alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set()); + const containerProtos = aliases.filter(alias => alias.context && alias.context instanceof Doc).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set()); const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases)); const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); @@ -42,6 +42,7 @@ export class PropertiesDocContextSelector extends React.Component !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document)) .filter(doc => !Doc.IsSystem(doc)) + .filter(doc => doc !== targetContext) .map(doc => ({ col: doc, target })); } @@ -53,8 +54,21 @@ export class PropertiesDocContextSelector extends React.Component DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100); + //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere); + setTimeout( + () => + this.props.DocView && + DocFocusOrOpen( + Doc.GetProto(this.props.DocView.props.Document), + { + // + willZoomCentered: true, + openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere, + }, + col + ), + 100 + ); }; render() { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index fbc7d7696..6582c3f2a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -341,8 +341,6 @@ export class PropertiesView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} addDocument={returnFalse} moveDocument={undefined} removeDocument={returnFalse} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index b950b4860..739d6d819 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -18,7 +18,7 @@ import { TreeSort } from './collections/TreeView'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; -import { DocumentViewProps } from './nodes/DocumentView'; +import { DocumentViewProps, OpenWhere } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { SliderBox } from './nodes/SliderBox'; @@ -295,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt { - if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) { + if (props?.docViewPath().lastElement()?.rootDoc?._viewType === CollectionViewType.Freeform) { return doc?.pointerEvents !== 'none' ? null : (
toggleLockedPosition(doc)}> @@ -383,10 +383,7 @@ export function DashboardStyleProvider(doc: Opt, props: Opt {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => { - doc.hidden = doc.hidden ? undefined : true; - if (!doc.hidden) { - DocFocusOrOpen(doc, props?.ContainingCollectionDoc); - } + DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc); })} ); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 45db240a9..c5a501aa6 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -114,8 +114,6 @@ export class TemplateMenu extends React.Component { { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
); @@ -642,8 +640,8 @@ export class CollectionViewBaseChrome extends React.Component this.props.docView.props.CollectionFreeFormDocumentView?.().float())}> diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index aec0734b4..80e81bc1c 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -255,8 +255,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { hideTitle={this.props.childHideTitle?.()} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} @@ -632,8 +630,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} />
); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index ba90ed8cd..fd9b0c0ce 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -30,7 +30,7 @@ export class CollectionPileView extends CollectionSubView() { // pileups are designed to go away when they are empty. this._disposers.selected = reaction( () => this.childDocs.length, - num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document) + num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document) ); } componentWillUnmount() { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index bbd81d06d..22a575989 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -815,8 +815,6 @@ class StackedTimelineAnchor extends React.Component whenChildContentsActiveChanged={emptyFunction} focus={focusFunc} isContentActive={returnFalse} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} searchFilterDocs={returnEmptyDoclist} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index a85ee0e02..67f5dc9f4 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -346,8 +346,6 @@ export class CollectionStackingView extends CollectionSubView ); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4a11e8f0b..92932fb61 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView 0) { FormattedTextBox.SelectOnLoad = prev[Id]; - DocumentManager.Instance.getDocumentView(prev, this.props.CollectionView)?.select(false); + DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false); } return true; } @@ -242,8 +242,6 @@ export class CollectionTreeView extends CollectionSubView ); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index bc25ad43a..53fbcc3cc 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -279,7 +279,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent +
{this.showIsTagged()} {this.renderSubView(this.collectionViewType, props)}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 458712999..ef8f395c5 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -425,8 +425,6 @@ export class TabDocView extends React.Component { hideTitle={this.props.keyValue} Document={this._document} DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} onBrowseClick={MainView.Instance.exploreMode} waitForDoubleClickToClick={MainView.Instance.waitForDoubleClick} isContentActive={returnTrue} @@ -580,8 +578,6 @@ export class TabMinimapView extends React.Component { { ScreenToLocalTransform={Transform.Identity} renderDepth={0} whenChildContentsActiveChanged={emptyFunction} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} styleProvider={TabMinimapView.miniStyleProvider} addDocTab={this.props.addDocTab} pinToPres={TabDocView.PinDoc} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 75e76019e..8b1dfc767 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -42,7 +42,7 @@ export interface TreeViewProps { prevSibling?: Doc; document: Doc; dataDoc?: Doc; - containerCollection: Doc; + treeViewParent: Doc; renderDepth: number; dropAction: dropActionType; addDocTab: (doc: Doc, where: OpenWhere) => boolean; @@ -143,7 +143,7 @@ export class TreeView extends React.Component { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; } @computed get MAX_EMBED_HEIGHT() { - return NumCast(this.props.containerCollection.maxEmbedHeight, 200); + return NumCast(this.props.treeViewParent.maxEmbedHeight, 200); } @computed get dataDoc() { return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; @@ -194,7 +194,7 @@ export class TreeView extends React.Component { const ind = this.dataDoc[key].indexOf(doc); const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); - res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false); + res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.DocumentView?.())?.select(false); return res; }; @@ -385,7 +385,7 @@ export class TreeView extends React.Component { }; const addDoc = inside ? localAdd : parentAddDoc; const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument; - const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd; + const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.freezeChildren).includes('add')) || forceAdd; if (canAdd) { this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true); const res = UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false)); @@ -406,7 +406,7 @@ export class TreeView extends React.Component { getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { - const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( layoutDoc[HeightSym](), this.MAX_EMBED_HEIGHT, @@ -416,7 +416,7 @@ export class TreeView extends React.Component { return layoutDoc._fitWidth ? !Doc.NativeHeight(layoutDoc) ? NumCast(layoutDoc._height) - : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) + : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height))) : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym](); })() ); @@ -452,7 +452,7 @@ export class TreeView extends React.Component { this, doc, undefined, - this.props.containerCollection, + this.props.treeViewParent, this.props.prevSibling, addDoc, remDoc, @@ -596,7 +596,7 @@ export class TreeView extends React.Component { this, this.layoutDoc, this.dataDoc, - this.props.containerCollection, + this.props.treeViewParent, this.props.prevSibling, addDoc, remDoc, @@ -656,7 +656,7 @@ export class TreeView extends React.Component { this.onCheckedClick?.script.run( { this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.containerCollection.title, + heading: this.props.treeViewParent.title, checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check', containingTreeView: this.props.treeView.props.Document, }, @@ -724,9 +724,11 @@ export class TreeView extends React.Component { }; @observable headerEleWidth = 0; - @computed get headerElements() { + @computed get titleButtons() { + const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations); return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( <> + {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */} {this.doc.hideContextMenu ? null : ( { return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; - onChildClick = () => { - return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); - }; + onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); @@ -906,7 +906,7 @@ export class TreeView extends React.Component { styleProvider={this.titleStyleProvider} enableDragWhenActive={true} onClickScriptDisable="never" // tree docViews have a script to show fields, etc. - docViewPath={returnEmptyDoclist} + docViewPath={this.props.treeView.props.docViewPath} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} addDocTab={this.props.addDocTab} @@ -937,12 +937,8 @@ export class TreeView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={this.props.treeView.props.CollectionView} - ContainingCollectionDoc={this.props.treeView.props.Document} /> ); - - const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : '')); return ( <>
{ {view}
r && (this.headerEleWidth = r.getBoundingClientRect().width))}> - {buttons} {/* hide and lock buttons */} - {this.headerElements} + {this.titleButtons}
); @@ -1017,8 +1012,6 @@ export class TreeView extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containerCollection} - ContainingCollectionView={undefined} addDocument={this.props.addDocument} moveDocument={this.move} removeDocument={this.props.removeDoc} @@ -1131,7 +1124,7 @@ export class TreeView extends React.Component { childDocs: Doc[], treeView: CollectionTreeView, parentTreeView: CollectionTreeView | TreeView | undefined, - containerCollection: Doc, + treeViewParent: Doc, dataDoc: Doc | undefined, parentCollectionDoc: Doc | undefined, containerPrevSibling: Doc | undefined, @@ -1162,19 +1155,19 @@ export class TreeView extends React.Component { hierarchyIndex?: number[], renderCount?: number ) { - const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); + const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField); if (viewSpecScript) { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); + const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1); const treeViewRefs = new Map(); return docs .filter(child => child instanceof Doc) .map((child, i) => { if (renderCount && i > renderCount) return null; - const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); + const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; } @@ -1192,10 +1185,7 @@ export class TreeView extends React.Component { } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); - const outdent = - parentCollectionDoc?._viewType !== CollectionViewType.Tree - ? undefined - : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); + const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); const rowHeight = () => { @@ -1208,7 +1198,7 @@ export class TreeView extends React.Component { ref={r => treeViewRefs.set(child, r ? r : undefined)} document={pair.layout} dataDoc={pair.data} - containerCollection={containerCollection} + treeViewParent={treeViewParent} prevSibling={docs[i]} // TODO: [AL] add these hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} @@ -1220,7 +1210,7 @@ export class TreeView extends React.Component { onCheckedClick={onCheckedClick} onChildClick={onChildClick} renderDepth={renderDepth} - removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove} + removeDoc={StrCast(treeViewParent.freezeChildren).includes('remove') ? undefined : remove} addDocument={addDocument} styleProvider={styleProvider} panelWidth={rowWidth} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7ae7be3c8..a3f5e73fb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -262,7 +262,7 @@ export class CollectionFreeFormView extends CollectionSubView { SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true)); }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; @@ -319,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0; const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0; let lastx = NumCast(refDoc.x); let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height)); - setTimeout( - action(() => - sorted.slice(1).forEach((pair, i) => { - lastx = pair.layout.x = lastx + deltax; - lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); - }) - ) + runInAction(() => + sorted.slice(1).forEach((pair, i) => { + lastx = pair.layout.x = lastx + deltax; + lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); + }) ); } } @@ -464,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster); - const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); + const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!); const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined); de.moveDocument = this.props.moveDocument; @@ -872,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)) + .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) .filter( @@ -966,7 +964,7 @@ export class CollectionFreeFormView extends CollectionSubView doc.type === DocumentType.INK && !doc.dontIntersect) .forEach(doc => { - const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke; + const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke; const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt)); @@ -1121,7 +1119,8 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) { + const collectionDoc = this.props.docViewPath().lastElement().rootDoc; + if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._panX !== undefined) { this.setPan( NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), @@ -1256,8 +1255,6 @@ export class CollectionFreeFormView extends CollectionSubView @@ -251,8 +249,7 @@ export class CollectionLinearView extends CollectionSubView() { self: this.rootDoc, _readOnly_: false, scriptContext: this.props.scriptContext, - thisContainer: this.props.ContainingCollectionDoc, - documentView: this.props.docViewPath().lastElement(), + documentView: this.props.DocumentView?.(), }); this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked; })} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b73b1d779..78d3d1b6e 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -262,8 +262,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} dontRegisterView={this.props.dontRegisterView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 0cca83803..4d61dc272 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -261,8 +261,6 @@ export class CollectionMultirowView extends CollectionSubView() { docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} dontRegisterView={this.props.dontRegisterView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index fd9bcf681..6d5a73e55 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -793,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() { fitContentsToBox={returnTrue} dontCenter={'y'} onClickScriptDisable="always" - focus={DocUtils.DefaultFocus} + focus={emptyFunction} renderDepth={this.props.renderDepth + 1} rootSelected={this.rootSelected} PanelWidth={this.previewWidthFunc} @@ -806,8 +806,6 @@ export class CollectionSchemaView extends CollectionSubView() { searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} moveDocument={this.props.moveDocument} addDocument={this.addRow} removeDocument={this.props.removeDocument} @@ -849,8 +847,6 @@ class CollectionSchemaViewDocs extends React.Component() { } @computed get schemaDoc() { - return this.props.ContainingCollectionDoc!; + return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc; } @computed get rowIndex() { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 13e45963e..5f8ffe8b0 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -28,8 +28,6 @@ export class SchemaTableCell extends React.Component { searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, - ContainingCollectionView: undefined, - ContainingCollectionDoc: undefined, fieldKey: this.props.fieldKey, rootSelected: returnFalse, isSelected: returnFalse, diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 29e7cd3ad..d703c9595 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -44,25 +44,10 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume } const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); - dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - docView.props.removeDocument?.(doc); - addDocument(doc); - return true; - }; - const containingView = docView.props.ContainingCollectionView; - const finishDrag = (e: DragManager.DragCompleteEvent) => - e.docDragData && - (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => { - const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); - if (dvs.length) { - dvs.forEach(dv => droppedDocs.push(dv.props.Document)); - } else { - droppedDocs.push(Doc.MakeAlias(d)); - } - return droppedDocs; - }, [] as Doc[])); + dragData.canEmbed = true; + dragData.moveDocument = (docView.props.docViewPath().lastElement()?.ComponentView as any)?.props.CollectionView?.moveDocument; // this is equal to docView.props.moveDocument, but moveDocument is not a defined prop of a DocumentView .. but maybe it should be? - DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); + DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined); } } diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index 7bdace2b6..5f2d4a7b6 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -85,15 +85,13 @@ export class LinkPopup extends React.Component { PanelWidth={this.getPWidth} PanelHeight={this.getPHeight} renderDepth={0} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9bdb2cee7..24b9f3b25 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -160,8 +160,9 @@ export class CollectionFreeFormDocumentView extends DocComponent { - const { Document: topDoc, ContainingCollectionView: container } = this.props; - const screenXf = container?.screenToLocalTransform(); + const topDoc = this.rootDoc; + const containerDocView = this.props.docViewPath().lastElement(); + const screenXf = containerDocView?.screenToLocalTransform(); if (screenXf) { SelectionManager.DeselectAll(); if (topDoc.z) { @@ -178,7 +179,7 @@ export class CollectionFreeFormDocumentView extends DocComponent SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0); + setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0); } }; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index dbcfe43cf..76a5ce7b3 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -154,7 +154,6 @@ export class DocumentContentsView extends React.Component< // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews 'hideResizeHandles', 'hideTitle', - 'treeViewDoc', 'contentPointerEvents', 'radialMenu', 'LayoutTemplateString', diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index df3299eef..47705d53d 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -142,7 +142,7 @@ export class DocumentLinksButton extends React.Component undefined | { x: number; y: number; r: number; b: number }; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitContentsToBox property on a Document - ContainingCollectionView: Opt; - ContainingCollectionDoc: Opt; suppressSetHeight?: boolean; thumbShown?: () => boolean; setContentView?: (view: DocComponentView) => any; @@ -179,6 +177,7 @@ export interface DocumentViewSharedProps { ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; canEmbedOnDrag?: boolean; + treeViewDoc?: Doc; xPadding?: number; yPadding?: number; dropAction?: dropActionType; @@ -210,7 +209,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { hideOpenButton?: boolean; hideDeleteButton?: boolean; hideLinkAnchors?: boolean; - treeViewDoc?: Doc; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events isContentActive: () => boolean | undefined; // whether document contents should handle pointer events contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents @@ -429,7 +427,6 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); - if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { + if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Tree) { const existingOnClick = cm.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; @@ -1446,7 +1442,7 @@ export class DocumentView extends React.Component { scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale; docViewPathFunc = () => this.docViewPath; isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); - select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection); + select = (extendSelection: boolean) => SelectionManager.SelectView(this, extendSelection); NativeWidth = () => this.effectiveNativeWidth; NativeHeight = () => this.effectiveNativeHeight; PanelWidth = () => this.panelWidth; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 8d3534a5c..86779e0dd 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -32,6 +32,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields backgroundColor?: string; + treeViewDoc?: Doc; color?: string; fontSize?: number; height?: number; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 94434dce7..7ea6d42ff 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -61,8 +61,6 @@ export class KeyValuePair extends React.Component { searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, - ContainingCollectionView: undefined, - ContainingCollectionDoc: undefined, fieldKey: this.props.keyName, rootSelected: returnFalse, isSelected: returnFalse, diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index fcc5b6975..c58b5dd8c 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -271,14 +271,12 @@ export class LinkDocPreview extends React.Component { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} renderDepth={0} suppressSetHeight={true} PanelWidth={this.width} PanelHeight={this.height} pointerEvents={returnNone} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={returnFalse} ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size. bringToFront={returnFalse} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index db11a7776..e015024fd 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -305,8 +305,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent + renderDepth={this.props.renderDepth + 1}> <> {this.threed} {this.content} @@ -330,7 +329,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent + /> )} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 8eacfbc51..1a75a7e76 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -568,12 +568,12 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); - const layoutFrameNumber = Cast(selView.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber).backgroundColor ?? 'transparent'; } selectedViews.forEach(dv => { - const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed if (contentFrameNumber !== undefined) { CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color }); diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index c00ab6a7e..b31fc01ff 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -9,10 +9,9 @@ import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { ColorScheme } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; -import { DocumentView } from '../DocumentView'; +import { DocFocusOptions, DocumentView } from '../DocumentView'; import { FormattedTextBox } from './FormattedTextBox'; import React = require('react'); -import { SelectionManager } from '../../../util/SelectionManager'; export class DashDocView { dom: HTMLSpanElement; // container for label and value @@ -151,7 +150,7 @@ export class DashDocViewInternal extends React.Component { const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target + outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target onKeyDown = (e: any) => { e.stopPropagation(); @@ -212,8 +211,6 @@ export class DashDocViewInternal extends React.Component { docFilters={this.props.tbox?.props.docFilters} docRangeFilters={this.props.tbox?.props.docRangeFilters} searchFilterDocs={this.props.tbox?.props.searchFilterDocs} - ContainingCollectionView={this._textBox.props.ContainingCollectionView} - ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} /> ); diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index f23426bb3..c43206629 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -235,10 +235,7 @@ export class DashFieldViewInternal extends React.Component { - let container = this.props.tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } + let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement(); if (container) { const alias = Doc.MakeAlias(container.props.Document); alias._viewType = CollectionViewType.Time; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 677c4662b..bc2a5d797 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -541,7 +541,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' }); + const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true }); return this.addDocument(tanch) ? tanch : undefined; }; const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement(); @@ -956,7 +957,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const examinedNode = findAnchorNode(node, editor); - if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { + if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { nodes.push(examinedNode.node); !hadStart && (start = index + examinedNode.start); hadStart = true; @@ -971,9 +972,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent mark.type === editor.state.schema.marks.linkAnchor); @@ -987,7 +994,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) { + if ((ret.frag.size || (content?.length && content[0].type === this._editorView.state.schema.nodes.dashDoc) || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) { !options.instant && (this._focusSpeed = focusSpeed); let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { @@ -998,6 +1005,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this._focusSpeed = undefined), this._focusSpeed); setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000)); + return focusSpeed; + } else { + return this.props.focus(this.rootDoc, options); } } }; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 3589a9065..807a19771 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -2457,7 +2457,6 @@ export class PresBox extends ViewBoxBaseComponent() { {mode !== CollectionViewType.Invalid ? ( () { // Idea: this boolean will determine whether to automatically show the video when this preselement is selected. // @observable static showVideo: boolean = false; @computed get indexInPres() { - return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc); + return DocListCast(this.presBox?.[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) @computed get expandViewHeight() { return 100; @@ -51,11 +51,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { return this.presBoxView?.selectedArray; } @computed get presBoxView() { - const vpath = this.props.docViewPath(); - return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined; + return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox; } @computed get presBox() { - return this.props.ContainingCollectionDoc!; + return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc; } @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; @@ -110,14 +109,12 @@ export class PresElementBox extends ViewBoxBaseComponent() { docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} rootSelected={returnTrue} addDocument={returnFalse} removeDocument={returnFalse} fitContentsToBox={returnTrue} moveDocument={this.props.moveDocument!} - focus={DocUtils.DefaultFocus} + focus={emptyFunction} whenChildContentsActiveChanged={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} @@ -195,7 +192,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []); if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc); dragData.dropAction = 'move'; - dragData.treeViewDoc = this.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; + dragData.treeViewDoc = this.presBox?._viewType === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; dragData.moveDocument = this.props.moveDocument; const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { @@ -269,7 +266,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @undoBatch removeItem = action((e: React.MouseEvent) => { e.stopPropagation(); - if (this.indexInPres < (this.presBoxView?.itemIndex || 0)) { + if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) { this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1; } this.props.removeDocument?.(this.rootDoc); @@ -406,15 +403,15 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get toolbarWidth(): number { const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox); - let width: number = NumCast(this.presBox._width); + let width: number = NumCast(this.presBox?._width); if (presBoxDocView) width = presBoxDocView.props.PanelWidth(); if (width === 0) width = 300; return width; } @computed get presButtons() { - const presBox: Doc = this.presBox; //presBox - const presBoxColor: string = StrCast(presBox._backgroundColor); + const presBox = this.presBox; //presBox + const presBoxColor: string = StrCast(presBox?._backgroundColor); const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; const targetDoc: Doc = this.targetDoc; const activeItem: Doc = this.rootDoc; @@ -494,10 +491,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get mainItem() { const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false; - const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres; + const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres; const miniView: boolean = this.toolbarWidth <= 110; - const presBox: Doc = this.presBox; //presBox - const presBoxColor: string = StrCast(presBox._backgroundColor); + const presBox = this.presBox; //presBox + const presBoxColor: string = StrCast(presBox?._backgroundColor); const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; const activeItem: Doc = this.rootDoc; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index f2e9be61d..d63c25dbe 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -46,7 +46,7 @@ export class TopBar extends React.Component { return (
{Doc.ActiveDashboard ? ( - } isCircle={true} hoverStyle="gray" color={this.textColor} /> + } color={this.textColor} /> ) : (
dash logo @@ -55,18 +55,7 @@ export class TopBar extends React.Component {
)} {Doc.ActiveDashboard && ( -
); @@ -94,13 +83,8 @@ export class TopBar extends React.Component {
- setupMoveUpEvents( - {}, - e, - returnFalse, - emptyFunction, - action(e => { - const newAxes = this.props.axes; - if (newAxes.includes(col)) { - newAxes.splice(newAxes.indexOf(col), 1); - } else if (newAxes.length >= 1) { - newAxes[1] = col; - } else { - newAxes[0] = col; - } - this.props.selectAxes(newAxes); - }) - ) - }> - {col} - { + const downX = e.clientX; + const downY = e.clientY; + setupMoveUpEvents( + {}, + e, + e => { + const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!; + const targetCreator = (annotationOn: Doc | undefined) => { + const alias = Doc.MakeAlias(this.props.docView?.()!.rootDoc!); + alias._dataVizView = DataVizView.LINECHART; + alias._dataVizAxes = new List([col, col]); + alias.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; + return alias; + }; + if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { + DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, { + dragComplete: e => { + if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { + e.linkDocument.linkDisplay = true; + // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; + // e.annoDragData.linkSourceDoc.followLinkZoom = false; + } + }, + }); + return true; + } + return false; + }, + emptyFunction, + action(e => { + const newAxes = this.props.axes; + if (newAxes.includes(col)) { + newAxes.splice(newAxes.indexOf(col), 1); + } else if (newAxes.length >= 1) { + newAxes[1] = col; + } else { + newAxes[0] = col; + } + this.props.selectAxes(newAxes); + }) + ); + }}> + {col} +
{p[col]}{p[col]}