Compare commits

...

1 Commits

Author SHA1 Message Date
orangecoding
87771655a8 adding new dashboard view. Muchas wow 2025-12-14 12:23:59 +01:00
31 changed files with 688 additions and 1047 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 402 KiB

View File

@@ -6,7 +6,6 @@
import { notificationAdapterRouter } from './routes/notificationAdapterRouter.js';
import { authInterceptor, cookieSession, adminInterceptor } from './security.js';
import { generalSettingsRouter } from './routes/generalSettingsRoute.js';
import { analyticsRouter } from './routes/analyticsRouter.js';
import { providerRouter } from './routes/providerRouter.js';
import { versionRouter } from './routes/versionRouter.js';
import { loginRouter } from './routes/loginRoute.js';
@@ -22,6 +21,7 @@ import logger from '../services/logger.js';
import { listingsRouter } from './routes/listingsRouter.js';
import { getSettings } from '../services/storage/settingsStorage.js';
import { featureRouter } from './routes/featureRouter.js';
import { dashboardRouter } from './routes/dashboardRouter.js';
const service = restana();
const staticService = files(path.join(getDirName(), '../ui/public'));
const PORT = (await getSettings()).port || 9998;
@@ -33,19 +33,21 @@ service.use('/api/admin', authInterceptor());
service.use('/api/jobs', authInterceptor());
service.use('/api/version', authInterceptor());
service.use('/api/listings', authInterceptor());
service.use('/api/dashboard', authInterceptor());
service.use('/api/features', authInterceptor());
// /admin can only be accessed when user is having admin permissions
service.use('/api/admin', adminInterceptor());
service.use('/api/jobs/notificationAdapter', notificationAdapterRouter);
service.use('/api/admin/generalSettings', generalSettingsRouter);
service.use('/api/jobs/provider', providerRouter);
service.use('/api/jobs/insights', analyticsRouter);
service.use('/api/admin/users', userRouter);
service.use('/api/version', versionRouter);
service.use('/api/jobs', jobRouter);
service.use('/api/login', loginRouter);
service.use('/api/listings', listingsRouter);
service.use('/api/features', featureRouter);
service.use('/api/dashboard', dashboardRouter);
//this route is unsecured intentionally as it is being queried from the login page
service.use('/api/demo', demoRouter);

View File

@@ -1,15 +0,0 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import restana from 'restana';
import * as listingStorage from '../../services/storage/listingsStorage.js';
const service = restana();
const analyticsRouter = service.newRouter();
analyticsRouter.get('/:jobId', async (req, res) => {
const { jobId } = req.params;
res.body = listingStorage.getListingProviderDataForAnalytics(jobId) || {};
res.send();
});
export { analyticsRouter };

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import restana from 'restana';
import * as jobStorage from '../../services/storage/jobStorage.js';
import * as userStorage from '../../services/storage/userStorage.js';
import { getListingsKpisForJobIds, getProviderDistributionForJobIds } from '../../services/storage/listingsStorage.js';
import { getSettings } from '../../services/storage/settingsStorage.js';
const service = restana();
export const dashboardRouter = service.newRouter();
function isAdmin(req) {
const user = req.session?.currentUser ? userStorage.getUser(req.session.currentUser) : null;
return !!user?.isAdmin;
}
function getAccessibleJobs(req) {
const currentUser = req.session.currentUser;
const admin = isAdmin(req);
return jobStorage
.getJobs()
.filter((job) => admin || job.userId === currentUser || job.shared_with_user.includes(currentUser));
}
function cap(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
dashboardRouter.get('/', async (req, res) => {
const jobs = getAccessibleJobs(req);
const settings = await getSettings();
// KPIs
const totalJobs = jobs.length;
const totalListings = jobs.reduce((sum, j) => sum + (j.numberOfFoundListings || 0), 0);
const jobIds = jobs.map((j) => j.id);
const { numberOfActiveListings, avgPriceOfListings } = getListingsKpisForJobIds(jobIds);
// Build Pie data in a simple shape the frontend can consume directly
// Shape: { labels: string[], values: number[] } with values as percentages
const providerPieRaw = getProviderDistributionForJobIds(jobIds);
const providerPie = Array.isArray(providerPieRaw)
? {
labels: providerPieRaw.map((p) => cap(p.type)),
values: providerPieRaw.map((p) => Number(p.value) || 0),
}
: providerPieRaw && typeof providerPieRaw === 'object'
? {
labels: Array.isArray(providerPieRaw.labels) ? providerPieRaw.labels : [],
values: Array.isArray(providerPieRaw.values) ? providerPieRaw.values : [],
}
: { labels: [], values: [] };
res.body = {
general: {
interval: settings.interval,
lastRun: settings.lastRun || null,
nextRun: settings.lastRun == null ? 0 : settings.lastRun + settings.interval * 60000,
},
kpis: {
totalJobs,
totalListings,
numberOfActiveListings,
avgPriceOfListings,
},
pie: providerPie,
};
res.send();
});

View File

@@ -9,7 +9,6 @@ import * as userStorage from '../../services/storage/userStorage.js';
import { isAdmin } from '../security.js';
import logger from '../../services/logger.js';
import { bus } from '../../services/events/event-bus.js';
import { getSettings } from '../../services/storage/settingsStorage.js';
const service = restana();
const jobRouter = service.newRouter();
@@ -48,15 +47,6 @@ jobRouter.get('/', async (req, res) => {
res.send();
});
jobRouter.get('/processingTimes', async (req, res) => {
const settings = await getSettings();
res.body = {
interval: settings.interval,
lastRun: settings.lastRun || null,
};
res.send();
});
jobRouter.post('/startAll', async (req, res) => {
bus.emit('jobs:runAll');
res.send();

View File

@@ -7,40 +7,6 @@ import { nullOrEmpty } from '../../utils.js';
import SqliteConnection from './SqliteConnection.js';
import { nanoid } from 'nanoid';
/**
* Build analytics data for a given job by grouping all listings by provider and
* mapping each listing hash to its creation timestamp.
*
* SQL shape:
* SELECT json_group_object(provider, json_object(hash, created_at)) AS result
* FROM listings WHERE job_id = @jobId;
*
* The resulting object has the shape:
* {
* providerA: { "<hash1>": <created_at_ms>, "<hash2>": <created_at_ms>, ... },
* providerB: { ... }
* }
*
* @param {string} jobId - ID of the job whose listings should be aggregated.
* @returns {Record<string, Record<string, number>>} Object grouped by provider mapping listing-hash -> created_at epoch ms.
*/
export const getListingProviderDataForAnalytics = (jobId) => {
const row = SqliteConnection.query(
`SELECT COALESCE(
json_group_object(provider, json(provider_map)),
json('{}')
) AS result
FROM (SELECT provider,
json_group_object(hash, created_at) AS provider_map
FROM listings
WHERE job_id = @jobId
GROUP BY provider);`,
{ jobId },
);
return row?.length > 0 ? JSON.parse(row[0].result) : {};
};
/**
* Return a list of known listing hashes for a given job and provider.
* Useful to de-duplicate before inserting new listings.
@@ -59,6 +25,89 @@ export const getKnownListingHashesForJobAndProvider = (jobId, providerId) => {
).map((r) => r.hash);
};
/**
* Compute KPI aggregates for a given set of job IDs from the listings table.
*
* - numberOfActiveListings: count of listings where is_active = 1
* - avgPriceOfListings: average of numeric price, rounded to nearest integer
*
* When no jobIds are provided, returns zeros.
*
* @param {string[]} jobIds
* @returns {{ numberOfActiveListings: number, avgPriceOfListings: number }}
*/
export const getListingsKpisForJobIds = (jobIds = []) => {
if (!Array.isArray(jobIds) || jobIds.length === 0) {
return { numberOfActiveListings: 0, avgPriceOfListings: 0 };
}
const placeholders = jobIds.map(() => '?').join(',');
const row =
SqliteConnection.query(
`SELECT
SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) AS activeCount,
AVG(price) AS avgPrice
FROM listings
WHERE job_id IN (${placeholders})`,
jobIds,
)[0] || {};
return {
numberOfActiveListings: Number(row.activeCount || 0),
avgPriceOfListings: row?.avgPrice == null ? 0 : Math.round(Number(row.avgPrice)),
};
};
/**
* Compute distribution of listings by provider for the given set of job IDs.
* Returns data ready for the pie chart component with fields `type` and `value` (percentage).
*
* Example return:
* [ { type: 'immoscout', value: 62 }, { type: 'immowelt', value: 38 } ]
*
* When no jobIds are provided or no listings exist, returns empty array.
*
* @param {string[]} jobIds
* @returns {{ type: string, value: number }[]}
*/
export const getProviderDistributionForJobIds = (jobIds = []) => {
if (!Array.isArray(jobIds) || jobIds.length === 0) {
return [];
}
const placeholders = jobIds.map(() => '?').join(',');
const rows = SqliteConnection.query(
`SELECT provider, COUNT(*) AS cnt
FROM listings
WHERE job_id IN (${placeholders})
GROUP BY provider
ORDER BY cnt DESC`,
jobIds,
);
const total = rows.reduce((acc, r) => acc + Number(r.cnt || 0), 0);
if (total === 0) return [];
// Map counts to integer percentage values (0-100). Ensure sum is ~100 by rounding.
const percentages = rows.map((r) => ({
type: r.provider,
value: Math.round((Number(r.cnt) / total) * 100),
}));
// Adjust rounding drift to keep sum at 100 (optional minor correction)
const drift = 100 - percentages.reduce((s, p) => s + p.value, 0);
if (drift !== 0 && percentages.length > 0) {
// apply drift to the largest slice to keep UX simple
let maxIdx = 0;
for (let i = 1; i < percentages.length; i++) {
if (percentages[i].value > percentages[maxIdx].value) maxIdx = i;
}
percentages[maxIdx].value = Math.max(0, percentages[maxIdx].value + drift);
}
return percentages;
};
/**
* Return a list of listing that either are active or have an unknown status
* to constantly check if they are still online

View File

@@ -1,6 +1,6 @@
{
"name": "fredy",
"version": "16.0.1",
"version": "16.1.0",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": {
"prepare": "husky",
@@ -62,12 +62,10 @@
"@douyinfe/semi-icons": "^2.89.0",
"@douyinfe/semi-ui": "2.89.0",
"@sendgrid/mail": "8.1.6",
"@visactor/react-vchart": "^2.0.10",
"@visactor/vchart": "^2.0.10",
"@visactor/vchart-semi-theme": "^1.12.2",
"@vitejs/plugin-react": "5.1.2",
"better-sqlite3": "^12.5.0",
"body-parser": "2.2.1",
"chart.js": "^4.5.1",
"cheerio": "^1.1.2",
"cookie-session": "2.1.1",
"handlebars": "4.7.8",
@@ -78,11 +76,12 @@
"node-mailjet": "6.0.11",
"p-throttle": "^8.1.0",
"package-up": "^5.0.0",
"puppeteer": "^24.32.1",
"puppeteer": "^24.33.0",
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2",
"query-string": "9.3.1",
"react": "18.3.1",
"react-chartjs-2": "^5.3.1",
"react-dom": "18.3.1",
"react-router": "7.10.1",
"react-router-dom": "7.10.1",
@@ -100,7 +99,7 @@
"@babel/preset-env": "7.28.5",
"@babel/preset-react": "7.28.5",
"chai": "6.2.1",
"eslint": "9.39.1",
"eslint": "9.39.2",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-react": "7.37.5",
"esmock": "2.7.3",

View File

@@ -10,7 +10,6 @@ import PermissionAwareRoute from './components/permission/PermissionAwareRoute';
import GeneralSettings from './views/generalSettings/GeneralSettings';
import JobMutation from './views/jobs/mutation/JobMutation';
import UserMutator from './views/user/mutation/UserMutator';
import JobInsight from './views/jobs/insights/JobInsight.jsx';
import { useActions, useSelector } from './services/state/store';
import { Routes, Route, Navigate } from 'react-router-dom';
import Login from './views/login/Login';
@@ -25,8 +24,8 @@ import Listings from './views/listings/Listings.jsx';
import Navigation from './components/navigation/Navigation.jsx';
import { Layout } from '@douyinfe/semi-ui';
import FredyFooter from './components/footer/FredyFooter.jsx';
import ProcessingTimes from './views/jobs/ProcessingTimes.jsx';
import WatchlistManagement from './views/listings/management/WatchlistManagement.jsx';
import Dashboard from './views/dashboard/Dashboard.jsx';
export default function FredyApp() {
const actions = useActions();
@@ -34,7 +33,6 @@ export default function FredyApp() {
const currentUser = useSelector((state) => state.user.currentUser);
const versionUpdate = useSelector((state) => state.versionUpdate.versionUpdate);
const settings = useSelector((state) => state.generalSettings.settings);
const processingTimes = useSelector((state) => state.jobs.processingTimes);
useEffect(() => {
async function init() {
@@ -43,7 +41,6 @@ export default function FredyApp() {
await actions.features.getFeatures();
await actions.provider.getProvider();
await actions.jobs.getJobs();
await actions.jobs.getProcessingTimes();
await actions.jobs.getSharableUserList();
await actions.notificationAdapter.getAdapter();
await actions.generalSettings.getGeneralSettings();
@@ -88,14 +85,13 @@ export default function FredyApp() {
</>
)}
{settings.analyticsEnabled === null && !settings.demoMode && <TrackingModal />}
{processingTimes != null && <ProcessingTimes processingTimes={processingTimes} />}
<Divider />
<div className="app__content">
<Routes>
<Route path="/403" element={<InsufficientPermission />} />
<Route path="/jobs/new" element={<JobMutation />} />
<Route path="/jobs/edit/:jobId" element={<JobMutation />} />
<Route path="/jobs/insights/:jobId" element={<JobInsight />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/jobs" element={<Jobs />} />
<Route path="/listings" element={<Listings />} />
<Route path="/watchlistManagement" element={<WatchlistManagement />} />
@@ -134,7 +130,7 @@ export default function FredyApp() {
}
/>
<Route path="/" element={<Navigate to="/jobs" replace />} />
<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>
</div>
</Content>

View File

@@ -9,17 +9,12 @@ import { HashRouter } from 'react-router-dom';
import { createRoot } from 'react-dom/client';
import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
import { LocaleProvider } from '@douyinfe/semi-ui';
import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
import App from './App';
import './Index.less';
const container = document.getElementById('fredy');
const root = createRoot(container);
initVChartSemiTheme({
defaultMode: 'dark',
});
root.render(
<HashRouter>
<LocaleProvider locale={en_US}>

BIN
ui/src/assets/heart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,23 @@
.chartCard {
/* Use provided background with slight transparency and a brighter mix */
background: color-mix(in oklab, var(--card-bg, rgb(70 72 78)) 20%, white 80%);
border-radius: .6rem;
border: 1px solid color-mix(in oklab, var(--card-bg, rgb(70 72 78)) 35%, white 65%);
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
/* Ensure base text has strong contrast */
color: var(--semi-color-text-0);
/* Semi Card header/title styling */
.semi-card-header .semi-card-header-title {
/* Derive a tinted title color with stronger contrast towards black */
color: color-mix(in oklab, var(--card-bg, rgb(70 72 78)) 60%, black 40%);
font-weight: 600;
}
&__no__data {
display: grid;
place-items: center;
height: 14rem;
opacity: .7;
}
}

View File

@@ -0,0 +1,92 @@
@import "DashboardCardColors.less";
.color-variant(@bg, @border, @text) {
background-color: @bg;
border: 1px solid @border;
color: @text;
}
.dashboard-card {
box-sizing: border-box;
padding: .8rem;
border-radius: .5rem;
border-width: 1px;
font-weight: 600;
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
/* Make all KPI boxes the same size regardless of content/font */
width: 100%;
max-width: none;
height: 10rem;
display: flex;
flex-direction: column;
&.blue {
.color-variant(@color-blue-bg, @color-blue-border, @color-blue-text);
}
&.orange {
.color-variant(@color-orange-bg, @color-orange-border, @color-orange-text);
}
&.green {
.color-variant(@color-green-bg, @color-green-border, @color-green-text);
}
&.purple {
.color-variant(@color-purple-bg, @color-purple-border, @color-purple-text);
}
&.gray {
.color-variant(@color-gray-bg, @color-gray-border, @color-gray-text);
}
&__header {
display: flex;
align-items: center;
gap: .6rem;
/* Keep header from growing content height */
min-height: 2rem;
overflow: hidden;
}
&__icon {
border-radius: .6rem;
display: grid;
place-items: center;
}
&__title {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__content {
margin-top: .4rem;
font-size: .7rem;
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
}
&__value {
margin: 0;
font-size: 1.5rem;
line-height: 1.1;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__desc {
opacity: .8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

View File

@@ -0,0 +1,19 @@
@color-blue-bg: rgba(0, 123, 255, 0.24);
@color-blue-border: #1E40AFFF;
@color-blue-text: #60a5fa;
@color-orange-bg: rgba(250, 91, 5, 0.12);
@color-orange-border: #d33601;
@color-orange-text: #FB923CFF;
@color-green-bg: rgba(38, 250, 5, 0.12);
@color-green-border: #00c316;
@color-green-text: #33f308;
@color-purple-bg: rgba(91, 3, 218, 0.38);
@color-purple-border: #7500c3;
@color-purple-text: #b15fff;
@color-gray-bg: rgba(110, 110, 110, 0.38);
@color-gray-border: #807f7f;
@color-gray-text: #bab9b9;

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import './DashboardCard.less';
export default function KpiCard({
title,
icon,
value,
valueFontSize = '1.5rem',
description,
color = 'gray',
children,
}) {
return (
<div className={`dashboard-card ${color}`}>
<div className="dashboard-card__header">
<div className="dashboard-card__icon">{icon}</div>
<div className="dashboard-card__title">
<span>{title}</span>
</div>
</div>
<div className="dashboard-card__content">
<p className="dashboard-card__value" style={{ fontSize: valueFontSize }}>
{value}
{children}
</p>
{description && <span className="dashboard-card__desc">{description}</span>}
</div>
</div>
);
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { Pie } from 'react-chartjs-2';
import { Chart as ChartJS, ArcElement, Tooltip, Legend, Title as ChartTitle } from 'chart.js';
import './ChartCard.less';
ChartJS.register(ArcElement, Tooltip, Legend, ChartTitle);
export default function PieChartCard({ data = [] }) {
const { labels, values } = React.useMemo(() => {
if (data && typeof data === 'object' && !Array.isArray(data)) {
const lbls = Array.isArray(data.labels) ? data.labels : [];
const vals = Array.isArray(data.values)
? data.values.map((v) => (Number.isFinite(Number(v)) ? Number(v) : 0))
: [];
return { labels: lbls, values: vals };
}
if (Array.isArray(data)) {
const lbls = data.map((d) => d?.type ?? 'Unknown');
const vals = data.map((d) => {
const v = Number(d?.value);
return Number.isFinite(v) ? v : 0;
});
return { labels: lbls, values: vals };
}
return { labels: [], values: [] };
}, [data]);
const palette = React.useMemo(
() => [
'#4e79a7',
'#f28e2b',
'#e15759',
'#76b7b2',
'#59a14f',
'#edc948',
'#b07aa1',
'#ff9da7',
'#9c755f',
'#bab0ab',
],
[],
);
const chartData = React.useMemo(
() => ({
labels,
datasets: [
{
data: values,
backgroundColor: labels.map((_, i) => palette[i % palette.length]),
borderColor: labels.map((_, i) => palette[i % palette.length]),
borderWidth: 1,
},
],
}),
[labels, values, palette],
);
const options = React.useMemo(
() => ({
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'right',
labels: {
color: () => '#fff',
},
},
title: { display: false },
tooltip: {
callbacks: {
label: (ctx) => {
const label = ctx.label || '';
const val = ctx.parsed !== undefined ? ctx.parsed : ctx.raw;
return `${label}: ${val}%`;
},
},
},
},
}),
[],
);
const isEmpty = !labels || labels.length === 0 || !values || values.length === 0;
return (
<>{isEmpty ? <div className="chartCard__no__data">No Data</div> : <Pie data={chartData} options={options} />}</>
);
}

View File

@@ -1,9 +1,10 @@
.navigate {
&__logout_Button {
&__footer {
align-items: center;
justify-content: center;
flex-direction: column;
gap: 0.5rem;
width: 100%;
display: flex;
}
}

View File

@@ -3,26 +3,34 @@
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { Nav } from '@douyinfe/semi-ui';
import { IconStar, IconSetting, IconTerminal } from '@douyinfe/semi-icons';
import React, { useEffect, useState } from 'react';
import { Button, Nav } from '@douyinfe/semi-ui';
import { IconStar, IconSetting, IconTerminal, IconHistogram, IconSidebar } from '@douyinfe/semi-icons';
import logoWhite from '../../assets/logo_white.png';
import heart from '../../assets/heart.png';
import Logout from '../logout/Logout.jsx';
import { useLocation, useNavigate } from 'react-router-dom';
import './Navigate.less';
import { useScreenWidth } from '../../hooks/screenWidth.js';
import { useFeature } from '../../hooks/featureHook.js';
import { useScreenWidth } from '../../hooks/screenWidth.js';
export default function Navigation({ isAdmin }) {
const navigate = useNavigate();
const location = useLocation();
const width = useScreenWidth();
const collapsed = width <= 850;
const [collapsed, setCollapsed] = useState(width <= 850);
const watchlistFeature = useFeature('WATCHLIST_MANAGEMENT') || false;
useEffect(() => {
if (width <= 850) {
setCollapsed(true);
}
}, [width]);
const items = [
{ itemKey: '/dashboard', text: 'Dashboard', icon: <IconHistogram /> },
{ itemKey: '/jobs', text: 'Jobs', icon: <IconTerminal /> },
{ itemKey: '/listings', text: 'Listings', icon: <IconStar /> },
];
@@ -51,18 +59,21 @@ export default function Navigation({ isAdmin }) {
return (
<Nav
style={{ height: '100%', width: collapsed ? '' : '13.2rem' }}
style={{ height: '100%' }}
items={items}
isCollapsed={collapsed}
selectedKeys={[parsePathName(location.pathname)]}
onSelect={(key) => {
navigate(key.itemKey);
}}
header={<img src={logoWhite} width="180" alt="Fredy Logo" />}
header={<img src={collapsed ? heart : logoWhite} width={collapsed ? '80' : '160'} alt="Fredy Logo" />}
footer={
<div className="navigate__logout_Button">
<Nav.Footer className="navigate__footer">
<Logout text={!collapsed} />
</div>
<Button icon={<IconSidebar />} onClick={() => setCollapsed(!collapsed)}>
{!collapsed && 'Collapse'}
</Button>
</Nav.Footer>
}
/>
);

View File

@@ -8,14 +8,16 @@ import { Card } from '@douyinfe/semi-ui';
import './SegmentParts.less';
export const SegmentPart = ({ name, Icon = null, children, helpText }) => {
export const SegmentPart = ({ name, Icon = null, children, helpText = null }) => {
const { Meta } = Card;
return (
<Card
className="segmentParts"
title={
<Meta title={name} description={helpText} avatar={Icon == null ? null : <Icon size="extra-extra-small" />} />
(helpText || name) && (
<Meta title={name} description={helpText} avatar={Icon == null ? null : <Icon size="extra-extra-small" />} />
)
}
>
{children}

View File

@@ -1,6 +1,6 @@
.segmentParts {
border: 1px solid #323232 !important;
border-radius: 5px !important;
border-radius: .9rem !important;
color: rgba(var(--semi-grey-8), 1);
background: rgb(53, 54, 60);
margin: 2rem;

View File

@@ -6,7 +6,7 @@
import React from 'react';
import { Button, Empty, Table, Switch, Popover } from '@douyinfe/semi-ui';
import { IconAlertTriangle, IconDelete, IconDescend2, IconEdit, IconHistogram } from '@douyinfe/semi-icons';
import { IconAlertTriangle, IconDelete, IconDescend2, IconEdit } from '@douyinfe/semi-icons';
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
import './JobTable.less';
@@ -21,14 +21,7 @@ const empty = (
const getPopoverContent = (text) => <article className="jobPopoverContent">{text}</article>;
export default function JobTable({
jobs = {},
onJobRemoval,
onJobStatusChanged,
onJobEdit,
onJobInsight,
onListingRemoval,
} = {}) {
export default function JobTable({ jobs = {}, onJobRemoval, onJobStatusChanged, onJobEdit, onListingRemoval } = {}) {
return (
<Table
pagination={false}
@@ -98,14 +91,6 @@ export default function JobTable({
render: (_, job) => {
return (
<div className="interactions">
<Popover content={getPopoverContent('Job Insights')}>
<Button
type="primary"
icon={<IconHistogram />}
disabled={job.isOnlyShared}
onClick={() => onJobInsight(job.id)}
/>
</Popover>
<Popover content={getPopoverContent('Edit a Job')}>
<Button
type="secondary"

View File

@@ -33,6 +33,16 @@ export const useFredyState = create(
(set) => {
// Async actions that directly set state (no separate reducer concept)
const effects = {
dashboard: {
async getDashboard() {
try {
const response = await xhrGet('/api/dashboard');
set((state) => ({ dashboard: { ...state.dashboard, data: response.json } }));
} catch (Exception) {
console.error('Error while trying to get resource for /api/dashboard. Error:', Exception);
}
},
},
notificationAdapter: {
async getAdapter() {
try {
@@ -90,27 +100,6 @@ export const useFredyState = create(
console.error(`Error while trying to get resource for api/jobs. Error:`, Exception);
}
},
async getProcessingTimes() {
try {
const response = await xhrGet('/api/jobs/processingTimes');
set((state) => ({ jobs: { ...state.jobs, processingTimes: Object.freeze(response.json) } }));
} catch (Exception) {
console.error(`Error while trying to get resource for api/processingTimes. Error:`, Exception);
}
},
async getInsightDataForJob(jobId) {
try {
const response = await xhrGet(`/api/jobs/insights/${jobId}`);
set((state) => ({
jobs: {
...state.jobs,
insights: { ...state.jobs.insights, [jobId]: Object.freeze(response.json) },
},
}));
} catch (Exception) {
console.error(`Error while trying to get resource for api/jobs/insights. Error:`, Exception);
}
},
},
user: {
async getUsers() {
@@ -185,6 +174,7 @@ export const useFredyState = create(
// Initial state
const initial = {
dashboard: { data: null },
notificationAdapter: [],
listingsTable: {
totalNumber: 0,
@@ -196,12 +186,13 @@ export const useFredyState = create(
demoMode: { demoMode: false },
versionUpdate: {},
provider: [],
jobs: { jobs: [], insights: {}, processingTimes: {}, shareableUserList: [] },
jobs: { jobs: [], shareableUserList: [] },
user: { users: [], currentUser: null },
};
// Expose actions by grouping them per slice
const actions = {
dashboard: { ...effects.dashboard },
notificationAdapter: { ...effects.notificationAdapter },
generalSettings: { ...effects.generalSettings },
demoMode: { ...effects.demoMode },

View File

@@ -0,0 +1,156 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { Button, Col, Row, Toast } from '@douyinfe/semi-ui';
import {
IconTerminal,
IconStar,
IconClock,
IconDoubleChevronLeft,
IconDoubleChevronRight,
IconStarStroked,
IconNoteMoney,
IconSearch,
IconPlayCircle,
} from '@douyinfe/semi-icons';
import { useSelector, useActions } from '../../services/state/store';
import KpiCard from '../../components/cards/KpiCard.jsx';
import PieChartCard from '../../components/cards/PieChartCard.jsx';
import Headline from '../../components/headline/Headline.jsx';
import './Dashboard.less';
import { SegmentPart } from '../../components/segment/SegmentPart.jsx';
import { xhrPost } from '../../services/xhr.js';
import { format } from '../../services/time/timeService.js';
export default function Dashboard() {
const actions = useActions();
const dashboard = useSelector((state) => state.dashboard.data);
React.useEffect(() => {
actions.dashboard.getDashboard();
}, []);
const kpis = dashboard?.kpis || { totalJobs: 0, totalListings: 0, providersUsed: 0 };
const pieData = dashboard?.pie || [];
return (
<div className="dashboard">
<Headline text="Dashboard" size={3} />
<Row gutter={16} className="dashboard__row">
<Col span={12} xs={24} sm={24} md={24} lg={24} xl={12}>
<SegmentPart name="General" Icon={IconTerminal}>
<Row gutter={16} className="dashboard__row">
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Search Interval"
value={`${dashboard?.general?.interval} min`}
icon={<IconClock />}
description="Time interval for job execution"
/>
</Col>
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Last Search"
valueFontSize="14px"
value={
dashboard?.general?.lastRun == null || dashboard?.general?.lastRun === 0
? '---'
: format(dashboard?.general?.lastRun)
}
icon={<IconDoubleChevronLeft />}
description="Last execution timestamp"
/>
</Col>
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Next Search"
value={
dashboard?.general?.nextRun == null || dashboard?.general?.nextRun === 0
? '---'
: format(dashboard?.general?.nextRun)
}
valueFontSize="14px"
icon={<IconDoubleChevronRight />}
description="Next execution timestamp"
/>
</Col>
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard title="Search Now" icon={<IconSearch />} description="Run a search now">
<Button
size="small"
style={{ marginTop: '.2rem' }}
icon={<IconPlayCircle />}
aria-label="Start now"
onClick={async () => {
try {
await xhrPost('/api/jobs/startAll', null);
Toast.success('Successfully triggered Fredy search.');
} catch {
Toast.error('Failed to trigger search');
}
}}
>
Search now
</Button>
</KpiCard>
</Col>
</Row>
</SegmentPart>
</Col>
<Col span={12} xs={24} sm={24} md={24} lg={24} xl={12}>
<SegmentPart name="Overview" Icon={IconStar}>
<Row gutter={16} className="dashboard__row">
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Jobs"
color="blue"
value={!kpis.totalJobs ? '---' : kpis.totalJobs}
icon={<IconTerminal />}
description="Total number of jobs"
/>
</Col>
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Listings"
color="orange"
value={!kpis.totalListings ? '---' : kpis.totalListings}
icon={<IconStarStroked />}
description="Total listings found"
/>
</Col>
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Active Listings"
color="green"
value={!kpis.numberOfActiveListings ? '---' : kpis.numberOfActiveListings}
icon={<IconStar />}
description="Total active listings"
/>
</Col>
<Col span={12} xs={24} sm={12} md={12} lg={12} xl={12}>
<KpiCard
title="Avg. Price"
color="purple"
value={`${!kpis.avgPriceOfListings ? '---' : kpis.avgPriceOfListings} EUR`}
icon={<IconNoteMoney />}
description="Avg. Price of listings"
/>
</Col>
</Row>
</SegmentPart>
</Col>
</Row>
<SegmentPart name="Provider Insights" Icon={IconStar} helpText="Percentage of found listings over all providers">
<PieChartCard title="Jobs per Provider" data={pieData} isLoading={false} />
</SegmentPart>
</div>
);
}
Dashboard.displayName = 'Dashboard';

View File

@@ -0,0 +1,11 @@
.dashboard {
&__row {
margin-bottom: 1rem;
/* Ensure grid items wrap to next line on narrow screens */
flex-wrap: wrap;
/* Vertical gap of 1rem between wrapped grid items (no px) */
.semi-col {
margin-bottom: 1rem;
}
}
}

View File

@@ -66,7 +66,6 @@ export default function Jobs() {
onJobRemoval={onJobRemoval}
onListingRemoval={onListingRemoval}
onJobStatusChanged={onJobStatusChanged}
onJobInsight={(jobId) => navigate(`/jobs/insights/${jobId}`)}
onJobEdit={(jobId) => navigate(`/jobs/edit/${jobId}`)}
/>
</div>

View File

@@ -1,102 +0,0 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { format } from '../../services/time/timeService';
import { Button, Card, Col, Row, Toast } from '@douyinfe/semi-ui';
import {
IconClock,
IconDoubleChevronLeft,
IconDoubleChevronRight,
IconPlayCircle,
IconSearch,
} from '@douyinfe/semi-icons';
import { xhrPost } from '../../services/xhr.js';
import './ProsessingTimes.less';
import { useScreenWidth } from '../../hooks/screenWidth.js';
function InfoCard({ title, value, icon }) {
const { Meta } = Card;
return (
<div
style={{
margin: '1rem',
background: 'rgb(53, 54, 60)',
borderRadius: '.3rem',
padding: '1rem',
minHeight: '3rem',
}}
>
<Meta title={title} description={value} avatar={icon} />
</div>
);
}
export default function ProcessingTimes({ processingTimes = {} }) {
if (Object.keys(processingTimes).length === 0) {
return null;
}
const width = useScreenWidth();
const invisible = width <= 1180;
if (invisible) {
return null;
}
return (
<Row>
<Col span={6}>
<InfoCard
title="Search Interval"
value={`${processingTimes.interval} min`}
icon={<IconClock style={{ color: 'rgba(var(--semi-grey-4), 1)' }} />}
/>
</Col>
{processingTimes.lastRun && (
<>
<Col span={6}>
<InfoCard
title="Last search"
icon={<IconDoubleChevronLeft style={{ color: 'rgba(var(--semi-grey-4), 1)' }} />}
value={format(processingTimes.lastRun)}
/>
</Col>
<Col span={6}>
<InfoCard
title="Next search"
icon={<IconDoubleChevronRight style={{ color: 'rgba(var(--semi-grey-4), 1)' }} />}
value={format(processingTimes.lastRun + processingTimes.interval * 60000)}
/>
</Col>
</>
)}
<Col span={6}>
<InfoCard
title="Search Now"
icon={<IconSearch style={{ color: 'rgba(var(--semi-grey-4), 1)' }} />}
value={
<Button
size="small"
style={{ marginTop: '.2rem' }}
icon={<IconPlayCircle />}
aria-label="Start now"
onClick={async () => {
try {
await xhrPost('/api/jobs/startAll', null);
Toast.success('Successfully triggered Fredy search.');
} catch {
Toast.error('Failed to trigger search');
}
}}
>
Search now
</Button>
}
/>
</Col>
</Row>
);
}

View File

@@ -1,5 +0,0 @@
.processingTimes {
display: flex;
gap: 1rem;
justify-content: space-between;
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { roundToHour } from '../../../services/time/timeService';
import Headline from '../../../components/headline/Headline';
import { useActions, useSelector } from '../../../services/state/store';
import { useParams } from 'react-router-dom';
import Linechart from './Linechart';
const JobInsight = function JobInsight() {
const actions = useActions();
const insights = useSelector((state) => state.jobs.insights);
const jobs = useSelector((state) => state.jobs.jobs);
const params = useParams();
React.useEffect(() => {
actions.jobs.getInsightDataForJob(params.jobId);
actions.jobs.getJobs();
}, []);
const getData = () => {
const data = insights[params.jobId] || {};
const providers = Object.keys(data);
const countsByProvider = {};
const allTimes = new Set();
const cap = (s) => (s ? s[0].toUpperCase() + s.slice(1) : 'Unknown');
providers.forEach((key) => {
const providerName = cap(key);
const tmpTimeObj = {};
Object.values(data[key] || {}).forEach((listingTs) => {
const time = roundToHour(listingTs);
tmpTimeObj[time] = tmpTimeObj[time] == null ? 1 : tmpTimeObj[time] + 1;
allTimes.add(time);
});
countsByProvider[providerName] = tmpTimeObj;
});
const sortedTimes = Array.from(allTimes).sort((a, b) => a - b);
const result = [];
providers.forEach((key) => {
const providerName = cap(key);
const bucket = countsByProvider[providerName] || {};
sortedTimes.forEach((t) => {
result.push({
listings: new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
}).format(new Date(parseInt(t))),
listingsNumber: bucket[t] || 0, // y value
provider: providerName, // series key
});
});
});
return result;
};
const getJobName = () => {
const job = jobs.find((job) => job.id === params.jobId);
if (job == null) {
return 'unknown';
} else {
return job.name;
}
};
return (
<div>
<Headline text={`Insights into Job: ${getJobName()}`} />
<Linechart isLoading={false} series={getData()} />
</div>
);
};
export default JobInsight;

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) 2025 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import Placeholder from '../../../components/placeholder/Placeholder';
import { VChart } from '@visactor/react-vchart';
import './Linechart.less';
const commonSpec = {
type: 'line',
xField: 'listings',
yField: 'listingsNumber',
seriesField: 'provider',
legends: { visible: true },
line: {
style: {
lineWidth: 2,
},
},
point: {
visible: false,
},
axes: [
{
orient: 'bottom',
field: 'listings',
zero: false,
},
],
};
const Linechart = function Linechart({ title, series, isLoading = false }) {
return (
<Placeholder ready={!isLoading} rows={6}>
{series == null || series.length === 0 ? (
<div className="linechart__no__data">No Data for selected timeframe :-/</div>
) : (
<VChart
spec={{
...commonSpec,
title: {
visible: true,
text: title,
},
data: { values: series },
}}
/>
)}
</Placeholder>
);
};
export default Linechart;

View File

@@ -1,15 +0,0 @@
.linechart {
&__no__data {
width: 100%;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
color: #06dcfff2;
flex-direction: column;
&__height {
height: 30.7rem;
}
}
}

View File

@@ -52,7 +52,7 @@ export default function Login() {
Toast.success('Login successful!');
await actions.user.getCurrentUser();
navigate('/jobs');
navigate('/dashboard');
};
return (

683
yarn.lock
View File

@@ -1280,10 +1280,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@9.39.1":
version "9.39.1"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.1.tgz#0dd59c3a9f40e3f1882975c321470969243e0164"
integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==
"@eslint/js@9.39.2":
version "9.39.2"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599"
integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==
"@eslint/object-schema@^2.1.7":
version "2.1.7"
@@ -1392,6 +1392,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@kurkle/color@^0.3.0":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.4.tgz#4d4ff677e1609214fc71c580125ddddd86abcabf"
integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==
"@mdx-js/mdx@^3.0.1":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.0.tgz#10235cab8ad7d356c262e8c21c68df5850a97dc3"
@@ -1452,84 +1457,6 @@
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"
integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==
"@resvg/resvg-js-android-arm-eabi@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz#49dc9722f95096f8aff70186deae8e148d60dce5"
integrity sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==
"@resvg/resvg-js-android-arm64@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz#fdb7c9362ea27a228831d047cfd1ea240daed780"
integrity sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==
"@resvg/resvg-js-darwin-arm64@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz#5ef093f1276149340b9f5a5c487717c4de87bee7"
integrity sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==
"@resvg/resvg-js-darwin-x64@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz#18dd758184f4877be1f6ac80d2f37999581c998c"
integrity sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==
"@resvg/resvg-js-linux-arm-gnueabihf@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz#eee8f8166763aacc935a53e223e7b9c31b10c94a"
integrity sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==
"@resvg/resvg-js-linux-arm64-gnu@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz#826569e1ee08f07879ce32850822ede2ee1192ef"
integrity sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==
"@resvg/resvg-js-linux-arm64-musl@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz#faf63d873666078ca74604454d40e4fecc22beb4"
integrity sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==
"@resvg/resvg-js-linux-x64-gnu@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz#37b4c16c488aac3834a069c3725f017285d94b1e"
integrity sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==
"@resvg/resvg-js-linux-x64-musl@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz#6099cf57c958832549b0671991246e06bb44df27"
integrity sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==
"@resvg/resvg-js-win32-arm64-msvc@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz#10c9472a20198a218d8c3b6d8fbb3fcba213b670"
integrity sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==
"@resvg/resvg-js-win32-ia32-msvc@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz#bfc1d37ed485d72ca5717138b99ba40788fa2720"
integrity sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==
"@resvg/resvg-js-win32-x64-msvc@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz#83cae82dfe27121f85492eb4dc03f6fe33d8a703"
integrity sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==
"@resvg/resvg-js@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js/-/resvg-js-2.4.1.tgz#07e9cd57f3c925a5fb075a313a0640fc74cba4a2"
integrity sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==
optionalDependencies:
"@resvg/resvg-js-android-arm-eabi" "2.4.1"
"@resvg/resvg-js-android-arm64" "2.4.1"
"@resvg/resvg-js-darwin-arm64" "2.4.1"
"@resvg/resvg-js-darwin-x64" "2.4.1"
"@resvg/resvg-js-linux-arm-gnueabihf" "2.4.1"
"@resvg/resvg-js-linux-arm64-gnu" "2.4.1"
"@resvg/resvg-js-linux-arm64-musl" "2.4.1"
"@resvg/resvg-js-linux-x64-gnu" "2.4.1"
"@resvg/resvg-js-linux-x64-musl" "2.4.1"
"@resvg/resvg-js-win32-arm64-msvc" "2.4.1"
"@resvg/resvg-js-win32-ia32-msvc" "2.4.1"
"@resvg/resvg-js-win32-x64-msvc" "2.4.1"
"@rolldown/pluginutils@1.0.0-beta.53":
version "1.0.0-beta.53"
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz#c57a5234ae122671aff6fe72e673a7ed90f03f87"
@@ -1746,64 +1673,6 @@
resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c"
integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==
"@turf/boolean-clockwise@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz#34573ecc18f900080f00e4ff364631a8b1135794"
integrity sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/invariant" "^6.5.0"
"@turf/clone@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/clone/-/clone-6.5.0.tgz#895860573881ae10a02dfff95f274388b1cda51a"
integrity sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/flatten@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/flatten/-/flatten-6.5.0.tgz#0bd26161f4f1759bbad6ba9485e8ee65f3fa72a7"
integrity sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/meta" "^6.5.0"
"@turf/helpers@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-6.5.0.tgz#f79af094bd6b8ce7ed2bd3e089a8493ee6cae82e"
integrity sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==
"@turf/invariant@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-6.5.0.tgz#970afc988023e39c7ccab2341bd06979ddc7463f"
integrity sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/meta@^3.7.5":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-3.14.0.tgz#8d3050c1a0f44bf406a633b6bd28c510f7bcee27"
integrity sha512-OtXqLQuR9hlQ/HkAF/OdzRea7E0eZK1ay8y8CBXkoO2R6v34CsDrWYLMSo0ZzMsaQDpKo76NPP2GGo+PyG1cSg==
"@turf/meta@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-6.5.0.tgz#b725c3653c9f432133eaa04d3421f7e51e0418ca"
integrity sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/rewind@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/rewind/-/rewind-6.5.0.tgz#bc0088f8ec56f00c8eacd902bbe51e3786cb73a0"
integrity sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==
dependencies:
"@turf/boolean-clockwise" "^6.5.0"
"@turf/clone" "^6.5.0"
"@turf/helpers" "^6.5.0"
"@turf/invariant" "^6.5.0"
"@turf/meta" "^6.5.0"
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -1937,156 +1806,6 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
"@visactor/react-vchart@^2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@visactor/react-vchart/-/react-vchart-2.0.10.tgz#103e8f555a56a6f134bd2f5136480b3912153617"
integrity sha512-OGpSBT7kutZZKSw3HlxivsmeqRZ6GEOXAxt20+hcZyeH34yqRHklksJYS6ET9E9uivbfrGzIAPniC4iizR8lhQ==
dependencies:
"@visactor/vchart" "2.0.10"
"@visactor/vchart-extension" "2.0.10"
"@visactor/vrender-core" "~1.0.30"
"@visactor/vrender-kits" "~1.0.30"
"@visactor/vutils" "~1.0.12"
react-is "^18.2.0"
"@visactor/vchart-extension@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@visactor/vchart-extension/-/vchart-extension-2.0.10.tgz#1fab69e983ba4a743f2b1a7a53ef5cd3f820cfb4"
integrity sha512-D/WDodDPtDFtMRXYEm9GNC/Qthu4WkMXMR4hS43dmqA2GYOcAB5/O3CfUFwm+efwBh2EEh46O3STEam20444og==
dependencies:
"@visactor/vchart" "2.0.10"
"@visactor/vdataset" "~1.0.12"
"@visactor/vlayouts" "~1.0.12"
"@visactor/vrender-animate" "~1.0.30"
"@visactor/vrender-components" "~1.0.30"
"@visactor/vrender-core" "~1.0.30"
"@visactor/vrender-kits" "~1.0.30"
"@visactor/vutils" "~1.0.12"
"@visactor/vchart-semi-theme@^1.12.2":
version "1.12.2"
resolved "https://registry.yarnpkg.com/@visactor/vchart-semi-theme/-/vchart-semi-theme-1.12.2.tgz#3d26da8d470d334e2888a97354634f73e6aba7c7"
integrity sha512-COFOcVdbI470JZJUKp/X+i/+WYHji/Fr/EX444R8rtTHiJp3KTxl5fRvUDoYDDgD2duSQjVQEXUquh7jCZhTnQ==
dependencies:
"@visactor/vchart-theme-utils" "1.12.2"
"@visactor/vchart-theme-utils@1.12.2":
version "1.12.2"
resolved "https://registry.yarnpkg.com/@visactor/vchart-theme-utils/-/vchart-theme-utils-1.12.2.tgz#bad0035e79dabbe80890bbd6196668551a12c874"
integrity sha512-PkgSAivtUZukCWVUGCXxKcbTzI/oMj1Ky22VYcVs/KM4VFmmCywU2xjBBe1du0LUey6CAKB7bMlj5bL2jctG0A==
"@visactor/vchart@2.0.10", "@visactor/vchart@^2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@visactor/vchart/-/vchart-2.0.10.tgz#a357cfe8ab33c59d743c902920e9e7489268dd89"
integrity sha512-YKOdrU08CVB7851UaxPWRC2+9B0zydzwps2Kdpm4fa8t/KCAn/tfL1/jLdhoTu5brk5UI68w414MX8OsjVosSQ==
dependencies:
"@visactor/vdataset" "~1.0.12"
"@visactor/vlayouts" "~1.0.12"
"@visactor/vrender-animate" "~1.0.30"
"@visactor/vrender-components" "~1.0.30"
"@visactor/vrender-core" "~1.0.30"
"@visactor/vrender-kits" "~1.0.30"
"@visactor/vscale" "~1.0.12"
"@visactor/vutils" "~1.0.12"
"@visactor/vutils-extension" "2.0.10"
"@visactor/vdataset@~1.0.12":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@visactor/vdataset/-/vdataset-1.0.16.tgz#ff452acf5c64040843275187f49ca5891def35de"
integrity sha512-YfD74IyhZuMQrXvgFGFHPC1YaALK/TTtvn6vojZno+NDlTuZBTlJWU52ykaZTHcAFyHeEKp9WjxgJcp0XaqHjg==
dependencies:
"@turf/flatten" "^6.5.0"
"@turf/helpers" "^6.5.0"
"@turf/rewind" "^6.5.0"
"@visactor/vutils" "1.0.16"
d3-dsv "^2.0.0"
d3-geo "^1.12.1"
d3-hexbin "^0.2.2"
d3-hierarchy "^3.1.1"
eventemitter3 "^4.0.7"
geobuf "^3.0.1"
geojson-dissolve "^3.1.0"
path-browserify "^1.0.1"
pbf "^3.2.1"
point-at-length "^1.1.0"
simple-statistics "^7.7.3"
simplify-geojson "^1.0.4"
topojson-client "^3.1.0"
"@visactor/vlayouts@~1.0.12":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@visactor/vlayouts/-/vlayouts-1.0.16.tgz#6c66edeb0d1c9b721f55f3c5420ec480ff6a20cb"
integrity sha512-O9q/7j6xUlAylEaJGbntQf6g6Eas/fYlEpMNfKE5uoeXWB3YwGdyVITLGpZHmpBehRd6qGPlCvlJK61ksca8uw==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/invariant" "^6.5.0"
"@visactor/vscale" "1.0.16"
"@visactor/vutils" "1.0.16"
eventemitter3 "^4.0.7"
"@visactor/vrender-animate@1.0.31", "@visactor/vrender-animate@~1.0.30":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@visactor/vrender-animate/-/vrender-animate-1.0.31.tgz#4f25203a2073eecbb05dc20af4718b45107bdd12"
integrity sha512-9xA9B8JihlsEfBziFUHdUGUizh6xRk07lejUk4f0+qGmGwMPvz32btszs2gw1eF9J6FVmBhxJdZILzcXz4ha0Q==
dependencies:
"@visactor/vrender-core" "1.0.31"
"@visactor/vutils" "~1.0.12"
"@visactor/vrender-components@~1.0.30":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@visactor/vrender-components/-/vrender-components-1.0.31.tgz#76445c52150e6e4c6f2e23a0afc19afdfe4cc0af"
integrity sha512-kB+ZdqCnfcmoLHleGPml/NmqgXViC0Vqk/64XzQzXd5fvFqCrgS1G6mpq+VrZjZhJx3gxCTQoct6q2qXN1aYIw==
dependencies:
"@visactor/vrender-animate" "1.0.31"
"@visactor/vrender-core" "1.0.31"
"@visactor/vrender-kits" "1.0.31"
"@visactor/vscale" "~1.0.12"
"@visactor/vutils" "~1.0.12"
"@visactor/vrender-core@1.0.31", "@visactor/vrender-core@~1.0.30":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@visactor/vrender-core/-/vrender-core-1.0.31.tgz#995551e4d519dfd0d71dbd27aa1112ff4e1125c3"
integrity sha512-4tzN2M5GfI7612IHRiDqUetAjd3J3Ns5gHQxQvmMxismdw7UTrlB8PgnWx9djTYwoxZnhNX0MpPGz9CKgbb7RA==
dependencies:
"@visactor/vutils" "~1.0.12"
color-convert "2.0.1"
"@visactor/vrender-kits@1.0.31", "@visactor/vrender-kits@~1.0.30":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@visactor/vrender-kits/-/vrender-kits-1.0.31.tgz#3c57cd84b48208fe6c7a19f4f7f05ba60bca4409"
integrity sha512-ZS1vslveNfK6MrkL0tMIzQWT9G/q+P7201nNA55YM89N1Tzpzm0X55YHQiIx9TnWTWQijMlqkz1PR5n3Xh+Hjg==
dependencies:
"@resvg/resvg-js" "2.4.1"
"@visactor/vrender-core" "1.0.31"
"@visactor/vutils" "~1.0.12"
gifuct-js "2.1.2"
lottie-web "^5.12.2"
roughjs "4.6.6"
"@visactor/vscale@1.0.16", "@visactor/vscale@~1.0.12":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@visactor/vscale/-/vscale-1.0.16.tgz#22fcda9933e9a4a8faee4d9459eb1d36b27dad3c"
integrity sha512-K7+NEhz1FiDOYx7gztTrbzYolQ8LFoQIK66HImEPvlf/HhGb1JCsAqf/04EwXSjvHzNhjg4wZ5c43BYltUWbBg==
dependencies:
"@visactor/vutils" "1.0.16"
"@visactor/vutils-extension@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@visactor/vutils-extension/-/vutils-extension-2.0.10.tgz#512a65286cef0307bbddab06628da538021a043f"
integrity sha512-XKps9vwm3rLLuP/oY9CHP1RZMmnXroT1l1yi+Ny+CD6NUaZpe0dlAcXgtsS6j0NztUA9KmIHol7Rnv5hhFz/iA==
dependencies:
"@visactor/vdataset" "~1.0.12"
"@visactor/vutils" "~1.0.12"
"@visactor/vutils@1.0.16", "@visactor/vutils@~1.0.12":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@visactor/vutils/-/vutils-1.0.16.tgz#352d0a6fdf9d255d6c4a0c2f88385fe8812d3611"
integrity sha512-imqX+4FSLK9L6GDPA4OQRqevZLUpHHbND1uZRWoWVcIByj2OgqlXciu1HZV3gKNe91yytOmhAun9+b6R9ugZow==
dependencies:
"@turf/helpers" "^6.5.0"
"@turf/invariant" "^6.5.0"
eventemitter3 "^4.0.7"
"@vitejs/plugin-react@5.1.2":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz#46f47be184c05a18839cb8705d79578b469ac6eb"
@@ -2099,11 +1818,6 @@
"@types/babel__core" "^7.20.5"
react-refresh "^0.18.0"
abs-svg-path@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf"
integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@@ -2198,11 +1912,6 @@ array-includes@^3.1.6, array-includes@^3.1.8:
is-string "^1.1.1"
math-intrinsics "^1.1.0"
array-source@0.0:
version "0.0.4"
resolved "https://registry.yarnpkg.com/array-source/-/array-source-0.0.4.tgz#a525df4a84b1376d27c677cd426a97c3882f8aca"
integrity sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==
array.prototype.findlast@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904"
@@ -2494,11 +2203,6 @@ buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@@ -2591,6 +2295,13 @@ character-reference-invalid@^2.0.0:
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
chart.js@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.5.1.tgz#19dd1a9a386a3f6397691672231cb5fc9c052c35"
integrity sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==
dependencies:
"@kurkle/color" "^0.3.0"
cheerio-select@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
@@ -2705,7 +2416,7 @@ collapse-white-space@^2.0.0:
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca"
integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==
color-convert@2.0.1, color-convert@^2.0.1:
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
@@ -2734,11 +2445,6 @@ comma-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
commander@2:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^14.0.2:
version "14.0.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.2.tgz#b71fd37fe4069e4c3c7c13925252ada4eba14e8e"
@@ -2754,25 +2460,6 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concat-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.0.2"
typedarray "^0.0.6"
concat-stream@~1.4.1:
version "1.4.11"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.4.11.tgz#1dc9f666f2621da9c618b1e7f8f3b2ff70b5f76f"
integrity sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==
dependencies:
inherits "~2.0.1"
readable-stream "~1.1.9"
typedarray "~0.0.5"
content-type@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
@@ -2825,11 +2512,6 @@ core-js-compat@^3.43.0:
dependencies:
browserslist "^4.25.3"
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cosmiconfig@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d"
@@ -2870,37 +2552,6 @@ css-what@^6.1.0:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea"
integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==
d3-array@1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
d3-dsv@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-2.0.0.tgz#b37b194b6df42da513a120d913ad1be22b5fe7c5"
integrity sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==
dependencies:
commander "2"
iconv-lite "0.4"
rw "1"
d3-geo@^1.12.1:
version "1.12.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f"
integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==
dependencies:
d3-array "1"
d3-hexbin@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/d3-hexbin/-/d3-hexbin-0.2.2.tgz#9c5837dacfd471ab05337a9e91ef10bfc4f98831"
integrity sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==
d3-hierarchy@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
data-uri-to-buffer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
@@ -3477,10 +3128,10 @@ eslint-visitor-keys@^4.2.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@9.39.1:
version "9.39.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.1.tgz#be8bf7c6de77dcc4252b5a8dcb31c2efff74a6e5"
integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==
eslint@9.39.2:
version "9.39.2"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c"
integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.1"
@@ -3488,7 +3139,7 @@ eslint@9.39.1:
"@eslint/config-helpers" "^0.4.2"
"@eslint/core" "^0.17.0"
"@eslint/eslintrc" "^3.3.1"
"@eslint/js" "9.39.1"
"@eslint/js" "9.39.2"
"@eslint/plugin-kit" "^0.4.1"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
@@ -3624,11 +3275,6 @@ etag@^1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
eventemitter3@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
eventemitter3@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
@@ -3707,13 +3353,6 @@ file-entry-cache@^8.0.0:
dependencies:
flat-cache "^4.0.0"
file-source@0.6:
version "0.6.1"
resolved "https://registry.yarnpkg.com/file-source/-/file-source-0.6.1.tgz#ae189d4993766b865a77f83adcf9b9a504cd37dc"
integrity sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==
dependencies:
stream-source "0.3"
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@@ -3873,39 +3512,6 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
geobuf@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/geobuf/-/geobuf-3.0.2.tgz#9b4debb57ce72ca2bef027223f3098c4f3975cd3"
integrity sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg==
dependencies:
concat-stream "^2.0.0"
pbf "^3.2.1"
shapefile "~0.6.6"
geojson-dissolve@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/geojson-dissolve/-/geojson-dissolve-3.1.0.tgz#86823271680a1a381f3e72f02648bbcc710ecda1"
integrity sha512-JXHfn+A3tU392HA703gJbjmuHaQOAE/C1KzbELCczFRFux+GdY6zt1nKb1VMBHp4LWeE7gUY2ql+g06vJqhiwQ==
dependencies:
"@turf/meta" "^3.7.5"
geojson-flatten "^0.2.1"
geojson-linestring-dissolve "0.0.1"
topojson-client "^3.0.0"
topojson-server "^3.0.0"
geojson-flatten@^0.2.1:
version "0.2.4"
resolved "https://registry.yarnpkg.com/geojson-flatten/-/geojson-flatten-0.2.4.tgz#8f3396f31a0f5b747e39c9e6a14088f43ba4ecfb"
integrity sha512-LiX6Jmot8adiIdZ/fthbcKKPOfWjTQchX/ggHnwMZ2e4b0I243N1ANUos0LvnzepTEsj0+D4fIJ5bKhBrWnAHA==
dependencies:
get-stdin "^6.0.0"
minimist "1.2.0"
geojson-linestring-dissolve@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/geojson-linestring-dissolve/-/geojson-linestring-dissolve-0.0.1.tgz#08ab8cdf386e919d35a0eb38fa1c550e020838db"
integrity sha512-Y8I2/Ea28R/Xeki7msBcpMvJL2TaPfaPKP8xqueJfQ9/jEhps+iOJxOR2XCBGgVb12Z6XnDb1CMbaPfLepsLaw==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@@ -3945,11 +3551,6 @@ get-proto@^1.0.0, get-proto@^1.0.1:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
@@ -3975,13 +3576,6 @@ get-uri@^6.0.1:
data-uri-to-buffer "^6.0.2"
debug "^4.3.4"
gifuct-js@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/gifuct-js/-/gifuct-js-2.1.2.tgz#06152437ba30ec914db8398bd838bd0fbc8a6ecd"
integrity sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==
dependencies:
js-binary-schema-parser "^2.0.3"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
@@ -4048,11 +3642,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
hachure-fill@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/hachure-fill/-/hachure-fill-0.5.2.tgz#d19bc4cc8750a5962b47fb1300557a85fcf934cc"
integrity sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==
handlebars@4.7.8:
version "4.7.8"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
@@ -4228,13 +3817,6 @@ husky@9.1.7:
resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d"
integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==
iconv-lite@0.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.6.3, iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@@ -4249,7 +3831,7 @@ iconv-lite@^0.7.0, iconv-lite@~0.7.0:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@^1.1.12, ieee754@^1.1.13:
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -4290,7 +3872,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.4:
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -4595,11 +4177,6 @@ is-what@^3.14.1:
resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
isarray@0.0.1, isarray@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
@@ -4636,11 +4213,6 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
js-binary-schema-parser@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz#3d7848748e8586e63b34e8911b643f59cfb6396e"
integrity sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -5577,16 +5149,6 @@ minimatch@^9.0.4, minimatch@^9.0.5:
dependencies:
brace-expansion "^2.0.1"
minimist@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==
minimist@1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
@@ -5955,11 +5517,6 @@ parse-node-version@^1.0.1:
resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
parse-svg-path@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==
parse5-htmlparser2-tree-adapter@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b"
@@ -5987,16 +5544,6 @@ parseurl@^1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-data-parser@0.1.0, path-data-parser@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c"
integrity sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -6025,22 +5572,6 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-source@0.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/path-source/-/path-source-0.1.3.tgz#03907c595480aa2596a15a901c44f745736e7a73"
integrity sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==
dependencies:
array-source "0.0"
file-source "0.6"
pbf@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.3.0.tgz#1790f3d99118333cc7f498de816028a346ef367f"
integrity sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==
dependencies:
ieee754 "^1.1.12"
resolve-protobuf-schema "^2.1.0"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@@ -6071,28 +5602,6 @@ pify@^4.0.1:
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
point-at-length@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/point-at-length/-/point-at-length-1.1.0.tgz#0ad72ebd0980d7f5a1ab12296c055f9eb6b30e57"
integrity sha512-nNHDk9rNEh/91o2Y8kHLzBLNpLf80RYd2gCun9ss+V0ytRSf6XhryBTx071fesktjbachRmGuUbId+JQmzhRXw==
dependencies:
abs-svg-path "~0.1.1"
isarray "~0.0.1"
parse-svg-path "~0.1.1"
points-on-curve@0.2.0, points-on-curve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1"
integrity sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==
points-on-path@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/points-on-path/-/points-on-path-0.2.1.tgz#553202b5424c53bed37135b318858eacff85dd52"
integrity sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==
dependencies:
path-data-parser "0.1.0"
points-on-curve "0.2.0"
possible-typed-array-names@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
@@ -6313,11 +5822,6 @@ prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, pros
prosemirror-state "^1.0.0"
prosemirror-transform "^1.1.0"
protocol-buffers-schema@^3.3.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
proxy-agent@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d"
@@ -6365,10 +5869,10 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
puppeteer-core@24.32.1:
version "24.32.1"
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.32.1.tgz#def7d96620d3460cb36a6e0dcd79cb897c19af30"
integrity sha512-GdWTOgy3RqaW6Etgx93ydlVJ4FBJ6TmhMksG5W7v4uawKAzLHNj33k4kBQ1SFZ9NvoXNjhdQuIQ+uik2kWnarA==
puppeteer-core@24.33.0:
version "24.33.0"
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.33.0.tgz#c3e6adee5fd5dd77895d4cc14e0d17461048917a"
integrity sha512-tPTxVg+Qdj/8av4cy6szv3GlhxeOoNhiiMZ955fjxQyvPQE/6DjCa6ZyF/x0WJrlgBZtaLSP8TQgJb7FdLDXXA==
dependencies:
"@puppeteer/browsers" "2.11.0"
chromium-bidi "11.0.0"
@@ -6425,16 +5929,16 @@ puppeteer-extra@^3.3.6:
debug "^4.1.1"
deepmerge "^4.2.2"
puppeteer@^24.32.1:
version "24.32.1"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.32.1.tgz#d11e204138750eeada834fdd6746ffba30733655"
integrity sha512-wa8vGswFjH1iCyG6bGGydIYssEBluXixbMibK4x2x6/lIAuR87gF+c+Jjzom2Wiw/dDOtuki89VBurRWrgYaUA==
puppeteer@^24.33.0:
version "24.33.0"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.33.0.tgz#5d0aba25f0038db2a3a996deb8c157d1842e8a21"
integrity sha512-nl3wsAztq5F8zybn4Tk41OCnYIzFIzGC6AN0WcF2KCUnWenajvRRPgBmS6LvNUV2HEeIzT2zRZHH0TgVxLDKew==
dependencies:
"@puppeteer/browsers" "2.11.0"
chromium-bidi "11.0.0"
cosmiconfig "^9.0.0"
devtools-protocol "0.0.1534754"
puppeteer-core "24.32.1"
puppeteer-core "24.33.0"
typed-query-selector "^2.12.0"
qs@^6.14.0:
@@ -6485,6 +5989,11 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-chartjs-2@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz#2b29995ce8b07f5c95c6ea3696838569e88453aa"
integrity sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==
react-dom@18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
@@ -6506,11 +6015,6 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-refresh@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
@@ -6554,7 +6058,7 @@ react@18.3.1:
dependencies:
loose-envify "^1.1.0"
readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0:
readable-stream@^3.1.1, readable-stream@^3.4.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@@ -6563,16 +6067,6 @@ readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~1.1.9:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readdirp@^4.0.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d"
@@ -6768,13 +6262,6 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-protobuf-schema@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758"
integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==
dependencies:
protocol-buffers-schema "^3.3.1"
resolve@^1.1.6, resolve@^1.22.10:
version "1.22.10"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
@@ -6854,21 +6341,6 @@ rope-sequence@^1.3.0:
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425"
integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==
roughjs@4.6.6:
version "4.6.6"
resolved "https://registry.yarnpkg.com/roughjs/-/roughjs-4.6.6.tgz#1059f49a5e0c80dee541a005b20cc322b222158b"
integrity sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==
dependencies:
hachure-fill "^0.5.2"
path-data-parser "^0.1.0"
points-on-curve "^0.2.0"
points-on-path "^0.2.1"
rw@1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
safe-array-concat@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3"
@@ -6902,7 +6374,7 @@ safe-regex-test@^1.1.0:
es-errors "^1.3.0"
is-regex "^1.2.1"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -7031,18 +6503,6 @@ shallow-clone@^0.1.2:
lazy-cache "^0.2.3"
mixin-object "^2.0.1"
shapefile@~0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/shapefile/-/shapefile-0.6.6.tgz#6fee152b9fb2b1c85f690285b692fb68c95a5f4f"
integrity sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==
dependencies:
array-source "0.0"
commander "2"
path-source "0.1"
slice-source "0.4"
stream-source "0.3"
text-encoding "^0.6.4"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -7123,11 +6583,6 @@ simple-get@^4.0.0:
once "^1.3.1"
simple-concat "^1.0.0"
simple-statistics@^7.7.3:
version "7.8.8"
resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.8.tgz#8acf4f78ba5a111e7c8424ffda2ac32fe353198c"
integrity sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w==
simple-update-notifier@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
@@ -7135,20 +6590,6 @@ simple-update-notifier@^2.0.0:
dependencies:
semver "^7.5.3"
simplify-geojson@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/simplify-geojson/-/simplify-geojson-1.0.5.tgz#3497d61497d323105297dc2a9d4e54c1b2926279"
integrity sha512-02l1W4UipP5ivNVq6kX15mAzCRIV1oI3tz0FUEyOsNiv1ltuFDjbNhO+nbv/xhbDEtKqWLYuzpWhUsJrjR/ypA==
dependencies:
concat-stream "~1.4.1"
minimist "1.2.6"
simplify-geometry "0.0.2"
simplify-geometry@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/simplify-geometry/-/simplify-geometry-0.0.2.tgz#63797e676eae96835ace02bcd27d3e1af544f49c"
integrity sha512-ZEyrplkqgCqDlL7V8GbbYgTLlcnNF+MWWUdy8s8ZeJru50bnI71rDew/I+HG36QS2mPOYAq1ZjwNXxHJ8XOVBw==
slack@11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/slack/-/slack-11.0.2.tgz#30f68527c5d1712b7faa3141db7716f89ac6e911"
@@ -7164,11 +6605,6 @@ slice-ansi@^7.1.0:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
slice-source@0.4:
version "0.4.1"
resolved "https://registry.yarnpkg.com/slice-source/-/slice-source-0.4.1.tgz#40a57ac03c6668b5da200e05378e000bf2a61d79"
integrity sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
@@ -7234,11 +6670,6 @@ stop-iteration-iterator@^1.1.0:
es-errors "^1.3.0"
internal-slot "^1.1.0"
stream-source@0.3:
version "0.3.5"
resolved "https://registry.yarnpkg.com/stream-source/-/stream-source-0.3.5.tgz#b97f52d0f8ea566db071db679b985403a31e0340"
integrity sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==
streamx@^2.15.0, streamx@^2.21.0:
version "2.22.1"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.1.tgz#c97cbb0ce18da4f4db5a971dc9ab68ff5dc7f5a5"
@@ -7364,11 +6795,6 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
stringify-entities@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3"
@@ -7496,11 +6922,6 @@ text-decoder@^1.1.0:
dependencies:
b4a "^1.6.4"
text-encoding@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
integrity sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==
tiny-json-http@^7.0.2:
version "7.5.1"
resolved "https://registry.yarnpkg.com/tiny-json-http/-/tiny-json-http-7.5.1.tgz#82efaa190c3edf6f5f2d906a9e88f792d38f8532"
@@ -7526,20 +6947,6 @@ toidentifier@1.0.1, toidentifier@~1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
topojson-client@^3.0.0, topojson-client@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99"
integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==
dependencies:
commander "2"
topojson-server@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/topojson-server/-/topojson-server-3.0.1.tgz#d2b3ec095b6732299be76a48406111b3201a34f5"
integrity sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==
dependencies:
commander "2"
touch@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694"
@@ -7645,16 +7052,6 @@ typed-query-selector@^2.12.0:
resolved "https://registry.yarnpkg.com/typed-query-selector/-/typed-query-selector-2.12.0.tgz#92b65dbc0a42655fccf4aeb1a08b1dddce8af5f2"
integrity sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
typedarray@~0.0.5:
version "0.0.7"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.7.tgz#799207136a37f3b3efb8c66c40010d032714dc73"
integrity sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==
uc.micro@^2.0.0, uc.micro@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"