[simple-example][m] - remove unused components
This commit is contained in:
@@ -22,7 +22,7 @@ PROJECT_NAME=<app-name>
|
|||||||
- This project uses the github api, which for anonymous users will cap at 50 requests per hour, so you might want to get a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) and add it to your .env file like so
|
- This project uses the github api, which for anonymous users will cap at 50 requests per hour, so you might want to get a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) and add it to your .env file like so
|
||||||
|
|
||||||
```
|
```
|
||||||
PAT_TOKEN=<github token>
|
GITHUB_PAT=<github token>
|
||||||
```
|
```
|
||||||
|
|
||||||
- Edit the file `datasets.json` to your liking, some examples can be found inside this [repo](https://github.com/datasets)
|
- Edit the file `datasets.json` to your liking, some examples can be found inside this [repo](https://github.com/datasets)
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import FrictionlessViewFactory from "./drd/FrictionlessView";
|
|
||||||
import Table from "./drd/Table";
|
|
||||||
|
|
||||||
/* eslint import/no-default-export: off */
|
|
||||||
function DatapackageLayout({ children, project, excerpt }) {
|
|
||||||
const { metadata } = project;
|
|
||||||
|
|
||||||
const title = metadata.title;
|
|
||||||
const resources = metadata.resources;
|
|
||||||
const views = metadata.views;
|
|
||||||
|
|
||||||
const FrictionlessView = FrictionlessViewFactory({ views, resources });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article className="docs prose text-primary dark:text-primary-dark dark:prose-invert prose-headings:font-headings prose-a:break-words mx-auto p-6">
|
|
||||||
<header>
|
|
||||||
{title && <h1 className="mb-4">{title}</h1>}
|
|
||||||
<a
|
|
||||||
className="font-semibold mb-4"
|
|
||||||
target="_blank"
|
|
||||||
href={project.github_repo}
|
|
||||||
>
|
|
||||||
@{project.owner} / {project.name}
|
|
||||||
</a>
|
|
||||||
{excerpt && <p className="text-md">{excerpt}</p>}
|
|
||||||
</header>
|
|
||||||
<section className="mt-10">
|
|
||||||
{views.map((view, i) => {
|
|
||||||
return (
|
|
||||||
<div key={`visualization-${i}`}>
|
|
||||||
<FrictionlessView viewId={i} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</section>
|
|
||||||
<section className="mt-10">
|
|
||||||
<h2>Data files</h2>
|
|
||||||
<table className="table-auto">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>File</th>
|
|
||||||
<th>Title</th>
|
|
||||||
<th>Format</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{resources.map((r) => {
|
|
||||||
return (
|
|
||||||
<tr key={`resources-list-${r.name}`}>
|
|
||||||
<td>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href={`https://github.com/${project.owner}/${project.name}/blob/main/${r.path}`}
|
|
||||||
>
|
|
||||||
{r.path}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>{r.title}</td>
|
|
||||||
<td>{r.format.toUpperCase()}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{resources.slice(0, 5).map((resource) => {
|
|
||||||
return (
|
|
||||||
<div key={`resource-preview-${resource.name}`} className="mt-10">
|
|
||||||
<h3>{resource.title || resource.name || resource.path}</h3>
|
|
||||||
<Table url={resource.path} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</section>
|
|
||||||
<hr />
|
|
||||||
<section>
|
|
||||||
<h2>Read me</h2>
|
|
||||||
{children}
|
|
||||||
</section>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MDLayout({ children, layout, ...props }) {
|
|
||||||
return <DatapackageLayout project={props.project} excerpt={props.excerpt}>{children}</DatapackageLayout>;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { MDXRemote } from "next-mdx-remote";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { Mermaid } from "@flowershow/core";
|
|
||||||
|
|
||||||
import FrictionlessViewFactory from "./FrictionlessView";
|
|
||||||
|
|
||||||
// Custom components/renderers to pass to MDX.
|
|
||||||
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
|
|
||||||
// to handle import statements. Instead, you must include components in scope
|
|
||||||
// here.
|
|
||||||
const components = {
|
|
||||||
Table: dynamic(() => import("./Table")),
|
|
||||||
mermaid: Mermaid,
|
|
||||||
// Excel: dynamic(() => import('../components/Excel')),
|
|
||||||
// TODO: try and make these dynamic ...
|
|
||||||
Vega: dynamic(() => import("./Vega")),
|
|
||||||
VegaLite: dynamic(() => import("./VegaLite")),
|
|
||||||
LineChart: dynamic(() => import("./LineChart")),
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
export default function DRD({
|
|
||||||
source,
|
|
||||||
frictionless = {
|
|
||||||
views: [],
|
|
||||||
resources: [],
|
|
||||||
},
|
|
||||||
}: {
|
|
||||||
source: any;
|
|
||||||
frictionless?: any;
|
|
||||||
}) {
|
|
||||||
// dynamic() can't be used inside of React rendering
|
|
||||||
// as it needs to be marked in the top level of the
|
|
||||||
// module for preloading to work
|
|
||||||
components.FrictionlessView = FrictionlessViewFactory({
|
|
||||||
views: frictionless.views,
|
|
||||||
resources: frictionless.resources,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <MDXRemote {...source} components={components} />;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
const DebouncedInput = ({
|
|
||||||
value: initialValue,
|
|
||||||
onChange,
|
|
||||||
debounce = 500,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const [value, setValue] = useState(initialValue);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(initialValue);
|
|
||||||
}, [initialValue]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
onChange(value);
|
|
||||||
}, debounce);
|
|
||||||
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
{...props}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => setValue(e.target.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DebouncedInput;
|
|
||||||
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// FrictionlessView is a factory because we have to
|
|
||||||
// set the views and resources lists before using it
|
|
||||||
|
|
||||||
import { convertSimpleToVegaLite } from "../../lib/viewSpecConversion";
|
|
||||||
import VegaLite from "./VegaLite";
|
|
||||||
|
|
||||||
export default function FrictionlessViewFactory({
|
|
||||||
views = [],
|
|
||||||
resources = [],
|
|
||||||
}): ({
|
|
||||||
viewId,
|
|
||||||
fullWidth,
|
|
||||||
}: {
|
|
||||||
viewId: number;
|
|
||||||
fullWidth?: boolean;
|
|
||||||
}) => JSX.Element {
|
|
||||||
return ({ viewId, fullWidth = false }) => {
|
|
||||||
if (!(viewId in views)) {
|
|
||||||
console.error(`View ${viewId} not found`);
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
const view = views[viewId];
|
|
||||||
|
|
||||||
let resource;
|
|
||||||
if (resources.length > 1) {
|
|
||||||
resource = resources.find((r) => r.name === view.resourceName);
|
|
||||||
} else {
|
|
||||||
resource = resources[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resource) {
|
|
||||||
console.error(`Resource not found for view id ${viewId}`);
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let vegaSpec;
|
|
||||||
switch (view.specType) {
|
|
||||||
case "simple":
|
|
||||||
vegaSpec = convertSimpleToVegaLite(view, resource);
|
|
||||||
break;
|
|
||||||
// ... other conversions
|
|
||||||
}
|
|
||||||
|
|
||||||
vegaSpec.data = { url: resource.path };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VegaLite
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
spec={vegaSpec}
|
|
||||||
actions={{ editor: false }}
|
|
||||||
downloadFileName={resource.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { VegaLite } from "react-vega";
|
|
||||||
|
|
||||||
export default function LineChart({
|
|
||||||
data = [],
|
|
||||||
fullWidth = false,
|
|
||||||
title = "",
|
|
||||||
}) {
|
|
||||||
var tmp = data;
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
tmp = data.map((r, i) => {
|
|
||||||
return { x: r[0], y: r[1] };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const vegaData = { table: tmp };
|
|
||||||
const spec = {
|
|
||||||
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
|
|
||||||
title,
|
|
||||||
width: "container" as "container",
|
|
||||||
height: 300,
|
|
||||||
mark: {
|
|
||||||
type: "line" as "line",
|
|
||||||
color: "black",
|
|
||||||
strokeWidth: 1,
|
|
||||||
tooltip: true,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
name: "table",
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
grid: {
|
|
||||||
type: "interval" as "interval",
|
|
||||||
bind: "scales",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
encoding: {
|
|
||||||
x: {
|
|
||||||
field: "x",
|
|
||||||
timeUnit: "year",
|
|
||||||
type: "temporal" as "temporal",
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
field: "y",
|
|
||||||
type: "quantitative" as "temporal",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return <VegaLite data={vegaData} spec={spec} />;
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
import {
|
|
||||||
createColumnHelper,
|
|
||||||
FilterFn,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ArrowDownIcon,
|
|
||||||
ArrowUpIcon,
|
|
||||||
ChevronDoubleLeftIcon,
|
|
||||||
ChevronDoubleRightIcon,
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
import loadUrlProxied from "../../lib/loadUrlProxied";
|
|
||||||
import parseCsv from "../../lib/parseCsv";
|
|
||||||
import DebouncedInput from "./DebouncedInput";
|
|
||||||
|
|
||||||
const Table = ({
|
|
||||||
data: ogData = [],
|
|
||||||
cols: ogCols = [],
|
|
||||||
csv = "",
|
|
||||||
url = "",
|
|
||||||
}) => {
|
|
||||||
if (csv) {
|
|
||||||
const out = parseCsv(csv);
|
|
||||||
ogData = out.rows;
|
|
||||||
ogCols = out.fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [data, setData] = React.useState(ogData);
|
|
||||||
const [cols, setCols] = React.useState(ogCols);
|
|
||||||
const [error, setError] = React.useState(""); // TODO: add error handling
|
|
||||||
|
|
||||||
const tableCols = useMemo(() => {
|
|
||||||
const columnHelper = createColumnHelper();
|
|
||||||
return cols.map((c) =>
|
|
||||||
columnHelper.accessor(c.key, {
|
|
||||||
header: () => c.name,
|
|
||||||
cell: (info) => info.getValue(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [data, cols]);
|
|
||||||
|
|
||||||
const [globalFilter, setGlobalFilter] = useState("");
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data,
|
|
||||||
columns: tableCols,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
state: {
|
|
||||||
globalFilter,
|
|
||||||
},
|
|
||||||
globalFilterFn: globalFilterFn,
|
|
||||||
onGlobalFilterChange: setGlobalFilter,
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (url) {
|
|
||||||
loadUrlProxied(url).then((data) => {
|
|
||||||
const { rows, fields } = parseCsv(data);
|
|
||||||
setData(rows);
|
|
||||||
setCols(fields);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [url]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<DebouncedInput
|
|
||||||
value={globalFilter ?? ""}
|
|
||||||
onChange={(value) => setGlobalFilter(String(value))}
|
|
||||||
className="p-2 text-sm shadow border border-block"
|
|
||||||
placeholder="Search all columns..."
|
|
||||||
/>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
{table.getHeaderGroups().map((hg) => (
|
|
||||||
<tr key={hg.id}>
|
|
||||||
{hg.headers.map((h) => (
|
|
||||||
<th key={h.id}>
|
|
||||||
<div
|
|
||||||
{...{
|
|
||||||
className: h.column.getCanSort()
|
|
||||||
? "cursor-pointer select-none"
|
|
||||||
: "",
|
|
||||||
onClick: h.column.getToggleSortingHandler(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{flexRender(h.column.columnDef.header, h.getContext())}
|
|
||||||
{{
|
|
||||||
asc: (
|
|
||||||
<ArrowUpIcon className="inline-block ml-2 h-4 w-4" />
|
|
||||||
),
|
|
||||||
desc: (
|
|
||||||
<ArrowDownIcon className="inline-block ml-2 h-4 w-4" />
|
|
||||||
),
|
|
||||||
}[h.column.getIsSorted() as string] ?? (
|
|
||||||
<div className="inline-block ml-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{table.getRowModel().rows.map((r) => (
|
|
||||||
<tr key={r.id}>
|
|
||||||
{r.getVisibleCells().map((c) => (
|
|
||||||
<td key={c.id}>
|
|
||||||
{flexRender(c.column.columnDef.cell, c.getContext())}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div className="flex gap-2 items-center justify-center">
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanPreviousPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.setPageIndex(0)}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<ChevronDoubleLeftIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanPreviousPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.previousPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
</button>
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<div>Page</div>
|
|
||||||
<strong>
|
|
||||||
{table.getState().pagination.pageIndex + 1} of{" "}
|
|
||||||
{table.getPageCount()}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanNextPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`w-6 h-6 ${
|
|
||||||
!table.getCanNextPage() ? "opacity-25" : "opacity-100"
|
|
||||||
}`}
|
|
||||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<ChevronDoubleRightIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const globalFilterFn: FilterFn<any> = (row, columnId, filterValue: string) => {
|
|
||||||
const search = filterValue.toLowerCase();
|
|
||||||
|
|
||||||
let value = row.getValue(columnId) as string;
|
|
||||||
if (typeof value === "number") value = String(value);
|
|
||||||
|
|
||||||
return value?.toLowerCase().includes(search);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Table;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { Vega as VegaOg } from "react-vega";
|
|
||||||
export default function Vega(props) {
|
|
||||||
return <VegaOg className="w-full" {...props} />;
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { VegaLite as VegaOg } from "react-vega";
|
|
||||||
export default function Vega(props) {
|
|
||||||
return <VegaOg className="w-full" {...props} />;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user