Skip to content

Commit

Permalink
Merge pull request #1103 from datopian/feat/portaljs-components-impro…
Browse files Browse the repository at this point in the history
…vements

Components API and docs improvements
Related to: #1089
  • Loading branch information
olayway authored Apr 17, 2024
2 parents de2c1e5 + 48cd812 commit 38dd710
Show file tree
Hide file tree
Showing 31 changed files with 547 additions and 467 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-chefs-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@portaljs/components': major
---

Components API tidying up and storybook docs improvements.
20 changes: 11 additions & 9 deletions packages/components/src/components/Catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ export function Catalog({
datasets,
facets,
}: {
datasets: any[];
datasets: {
_id: string | number;
metadata: { title: string; [k: string]: string | number };
url_path: string;
[k: string]: any;
}[];
facets: string[];
}) {
const [indexFilter, setIndexFilter] = useState('');
Expand Down Expand Up @@ -56,7 +61,7 @@ export function Catalog({
//Then check if the selectedValue for the given facet is included in the dataset metadata
.filter((dataset) => {
//Avoids a server rendering breakage
if (!watch() || Object.keys(watch()).length === 0) return true
if (!watch() || Object.keys(watch()).length === 0) return true;
//This will filter only the key pairs of the metadata values that were selected as facets
const datasetFacets = Object.entries(dataset.metadata).filter((entry) =>
facets.includes(entry[0])
Expand Down Expand Up @@ -86,9 +91,7 @@ export function Catalog({
className="p-2 ml-1 text-sm shadow border border-block"
{...register(elem[0] + '.selectedValue')}
>
<option value="">
Filter by {elem[0]}
</option>
<option value="">Filter by {elem[0]}</option>
{(elem[1] as { possibleValues: string[] }).possibleValues.map(
(val) => (
<option
Expand All @@ -102,10 +105,10 @@ export function Catalog({
)}
</select>
))}
<ul className='mb-5 pl-6 mt-5 list-disc'>
<ul className="mb-5 pl-6 mt-5 list-disc">
{filteredDatasets.map((dataset) => (
<li className='py-2' key={dataset._id}>
<a className='font-medium underline' href={dataset.url_path}>
<li className="py-2" key={dataset._id}>
<a className="font-medium underline" href={dataset.url_path}>
{dataset.metadata.title
? dataset.metadata.title
: dataset.url_path}
Expand All @@ -116,4 +119,3 @@ export function Catalog({
</>
);
}

6 changes: 4 additions & 2 deletions packages/components/src/components/Excel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { read, utils } from 'xlsx';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import { Data } from '../types/properties';

export type ExcelProps = {
url: string;
data: Required<Pick<Data, 'url'>>;
};

export function Excel({ url }: ExcelProps) {
export function Excel({ data }: ExcelProps) {
const url = data.url;
const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeSheetName, setActiveSheetName] = useState<string>();
const [workbook, setWorkbook] = useState<any>();
Expand Down
39 changes: 17 additions & 22 deletions packages/components/src/components/FlatUiTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Papa from 'papaparse';
import { Grid } from '@githubocto/flat-ui';
import LoadingSpinner from './LoadingSpinner';
import { Data } from '../types/properties';

const queryClient = new QueryClient();

Expand Down Expand Up @@ -36,64 +37,58 @@ export async function parseCsv(file: string, parsingConfig): Promise<any> {
}

export interface FlatUiTableProps {
url?: string;
data?: { [key: string]: number | string }[];
rawCsv?: string;
randomId?: number;
data: Data;
uniqueId?: number;
bytes: number;
parsingConfig: any;
}
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
uniqueId,
bytes = 5132288,
parsingConfig = {},
}) => {
const randomId = Math.random();
uniqueId = uniqueId ?? Math.random();
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<TableInner
bytes={bytes}
url={url}
data={data}
rawCsv={rawCsv}
randomId={randomId}
uniqueId={uniqueId}
parsingConfig={parsingConfig}
/>
</QueryClientProvider>
);
};

const TableInner: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
randomId,
uniqueId,
bytes,
parsingConfig,
}) => {
if (data) {
const url = data.url;
const csv = data.csv;
const values = data.values;

if (values) {
return (
<div className="w-full" style={{ height: '500px' }}>
<Grid data={data} />
<Grid data={values} />
</div>
);
}
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
['dataCsv', url, randomId],
['dataCsv', url, uniqueId],
() => getCsv(url as string, bytes),
{ enabled: !!url }
);
const { data: parsedData, isLoading: isParsing } = useQuery(
['dataPreview', csvString, randomId],
['dataPreview', csvString, uniqueId],
() =>
parseCsv(
rawCsv ? (rawCsv as string) : (csvString as string),
parsingConfig
),
{ enabled: rawCsv ? true : !!csvString }
parseCsv(csv ? (csv as string) : (csvString as string), parsingConfig),
{ enabled: csv ? true : !!csvString }
);
if (isParsing || isDownloadingCSV)
<div className="w-full flex justify-center items-center h-[500px]">
Expand Down
15 changes: 9 additions & 6 deletions packages/components/src/components/Iframe.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { CSSProperties } from "react";
import { CSSProperties } from 'react';
import { Data } from '../types/properties';

export interface IframeProps {
url: string;
data: Required<Pick<Data, 'url'>>;
style?: CSSProperties;
}

export function Iframe({
url, style
}: IframeProps) {
export function Iframe({ data, style }: IframeProps) {
const url = data.url;
return (
<iframe src={url} style={style ?? { width: `100%`, height: `100%` }}></iframe>
<iframe
src={url}
style={style ?? { width: `100%`, height: `100%` }}
></iframe>
);
}
33 changes: 15 additions & 18 deletions packages/components/src/components/LineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,33 @@ import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';
import { VegaLite } from './VegaLite';
import loadData from '../lib/loadData';
import { Data } from '../types/properties';

type AxisType = 'quantitative' | 'temporal';
type TimeUnit = 'year' | undefined; // or ...

export type LineChartProps = {
data: Array<Array<string | number>> | string | { x: string; y: number }[];
data: Omit<Data, 'csv'>;
title?: string;
xAxis?: string;
xAxis: string;
xAxisType?: AxisType;
xAxisTimeUnit: TimeUnit;
yAxis?: string;
xAxisTimeUnit?: TimeUnit;
yAxis: string;
yAxisType?: AxisType;
fullWidth?: boolean;
};

export function LineChart({
data = [],
fullWidth = false,
data,
title = '',
xAxis = 'x',
xAxis,
xAxisType = 'temporal',
xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes
yAxis = 'y',
yAxis,
yAxisType = 'quantitative',
}: LineChartProps) {
const url = data.url;
const values = data.values;
const [isLoading, setIsLoading] = useState<boolean>(false);

// By default, assumes data is an Array...
Expand Down Expand Up @@ -64,33 +66,28 @@ export function LineChart({
} as any;

useEffect(() => {
// If data is string, assume it's a URL
if (typeof data === 'string') {
if (url) {
setIsLoading(true);

// Manualy loading the data allows us to do other kinds
// of stuff later e.g. load a file partially
loadData(data).then((res: any) => {
loadData(url).then((res: any) => {
setSpecData({ values: res, format: { type: 'csv' } });
setIsLoading(false);
});
}
}, []);

var vegaData = {};
if (Array.isArray(data)) {
var dataObj;
dataObj = data.map((r) => {
return { x: r[0], y: r[1] };
});
vegaData = { table: dataObj };
if (values) {
vegaData = { table: values };
}

return isLoading ? (
<div className="w-full flex items-center justify-center w-[600px] h-[300px]">
<LoadingSpinner />
</div>
) : (
<VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />
<VegaLite data={vegaData} spec={spec} />
);
}
32 changes: 17 additions & 15 deletions packages/components/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CSSProperties, useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';
import loadData from '../lib/loadData';
import chroma from 'chroma-js';
import { GeospatialData } from '../types/properties';
import {
MapContainer,
TileLayer,
Expand All @@ -14,26 +15,25 @@ import * as L from 'leaflet';

export type MapProps = {
layers: {
data: string | GeoJSON.GeoJSON;
data: GeospatialData;
name: string;
colorScale?: {
starting: string;
ending: string;
};
tooltip?:
| {
propNames: string[];
}
| boolean;
_id?: number;
| {
propNames: string[];
}
| boolean;
}[];
title?: string;
center?: { latitude: number | undefined; longitude: number | undefined };
zoom?: number;
style?: CSSProperties;
autoZoomConfiguration?: {
layerName: string
}
layerName: string;
};
};

export function Map({
Expand All @@ -56,17 +56,19 @@ export function Map({

useEffect(() => {
const loadDataPromises = layers.map(async (layer) => {
const url = layer.data.url;
const geojson = layer.data.geojson;
let layerData: any;

if (typeof layer.data === 'string') {
if (url) {
// If "data" is string, assume it's a URL
setIsLoading(true);
layerData = await loadData(layer.data).then((res: any) => {
layerData = await loadData(url).then((res: any) => {
return JSON.parse(res);
});
} else {
// Else, expect raw GeoJSON
layerData = layer.data;
layerData = geojson;
}

if (layer.colorScale) {
Expand Down Expand Up @@ -111,23 +113,23 @@ export function Map({
// Create the title box
var info = new L.Control() as any;

info.onAdd = function() {
info.onAdd = function () {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};

info.update = function() {
info.update = function () {
this._div.innerHTML = `<h4 style="font-weight: 600; background: #f9f9f9; padding: 5px; border-radius: 5px; color: #464646;">${title}</h4>`;
};

if (title) info.addTo(map.target);
if(!autoZoomConfiguration) return;
if (!autoZoomConfiguration) return;

let layerToZoomBounds = L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0));

layers.forEach((layer) => {
if(layer.name === autoZoomConfiguration.layerName) {
if (layer.name === autoZoomConfiguration.layerName) {
const data = layersData.find(
(layerData) => layerData.name === layer.name
)?.data;
Expand Down
8 changes: 5 additions & 3 deletions packages/components/src/components/PdfViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
// Core viewer
import { Viewer, Worker, SpecialZoomLevel } from '@react-pdf-viewer/core';
import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout';
import { Data } from '../types/properties';

// Import styles
import '@react-pdf-viewer/core/lib/styles/index.css';
import '@react-pdf-viewer/default-layout/lib/styles/index.css';

export interface PdfViewerProps {
url: string;
data: Required<Pick<Data, 'url'>>;
layout: boolean;
parentClassName?: string;
}

export function PdfViewer({
url,
data,
layout = false,
parentClassName,
parentClassName = 'h-screen',
}: PdfViewerProps) {
const url = data.url;
const defaultLayoutPluginInstance = defaultLayoutPlugin();
return (
<Worker workerUrl="https://unpkg.com/[email protected]/build/pdf.worker.js">
Expand Down
Loading

0 comments on commit 38dd710

Please sign in to comment.