mirror of
https://github.com/NotAShelf/nix-evaluator-stats.git
synced 2026-04-27 12:25:20 +00:00
packages: move sharing logic from web to core; reuse in web
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I26cb6848615a5788f2196fc1675bc22d6a6a6964
This commit is contained in:
parent
8d7bd7bb05
commit
5482cd9df1
7 changed files with 481 additions and 1142 deletions
|
|
@ -16,5 +16,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lzutf8": "^0.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './parser';
|
export * from './parser';
|
||||||
|
export * from './share';
|
||||||
|
|
|
||||||
47
packages/core/src/share.ts
Normal file
47
packages/core/src/share.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import LZUTF8 from 'lzutf8';
|
||||||
|
|
||||||
|
export type ShareState =
|
||||||
|
| { type: 'analysis'; data: Record<string, unknown>; name: string }
|
||||||
|
| {
|
||||||
|
type: 'compare';
|
||||||
|
left: Record<string, unknown>;
|
||||||
|
right: Record<string, unknown>;
|
||||||
|
leftName: string;
|
||||||
|
rightName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function encodeShareUrl(state: ShareState): string {
|
||||||
|
const json = JSON.stringify(state);
|
||||||
|
const encoded = LZUTF8.compress(json, { outputEncoding: 'Base64' }) as string;
|
||||||
|
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeShareUrl(encoded: string): ShareState | null {
|
||||||
|
try {
|
||||||
|
let base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const padLength = (4 - (base64.length % 4)) % 4;
|
||||||
|
base64 += '='.repeat(padLength);
|
||||||
|
const json = LZUTF8.decompress(base64, {
|
||||||
|
inputEncoding: 'Base64',
|
||||||
|
outputEncoding: 'String',
|
||||||
|
}) as string | null;
|
||||||
|
if (!json) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const state = JSON.parse(json) as ShareState;
|
||||||
|
if (state.type === 'analysis') {
|
||||||
|
if (!state.data || !state.name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (state.type === 'compare') {
|
||||||
|
if (!state.left || !state.right || !state.leftName || !state.rightName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ns/core": "workspace:*",
|
"@ns/core": "workspace:*",
|
||||||
"@ns/ui-utils": "workspace:*",
|
"@ns/ui-utils": "workspace:*"
|
||||||
"lzutf8": "^0.6.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.5.2",
|
"@types/node": "^25.5.2",
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ const ComparisonView: Component<ComparisonViewProps> = props => {
|
||||||
setRightEntry(right);
|
setRightEntry(right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (props.initialLeftId !== null || props.initialRightId !== null) {
|
if (props.initialLeftId != null || props.initialRightId != null) {
|
||||||
props.onInitialSelectionUsed?.();
|
props.onInitialSelectionUsed?.();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,21 @@
|
||||||
import { createSignal, Show, For, onMount, createEffect, lazy } from 'solid-js';
|
import { createSignal, Show, For, onMount, createEffect, lazy } from 'solid-js';
|
||||||
import { render } from 'solid-js/web';
|
import { render } from 'solid-js/web';
|
||||||
import LZUTF8 from 'lzutf8';
|
|
||||||
import SaveIcon from 'lucide-solid/icons/save';
|
import SaveIcon from 'lucide-solid/icons/save';
|
||||||
import UploadIcon from 'lucide-solid/icons/upload';
|
import UploadIcon from 'lucide-solid/icons/upload';
|
||||||
import Trash2Icon from 'lucide-solid/icons/trash-2';
|
import Trash2Icon from 'lucide-solid/icons/trash-2';
|
||||||
import XIcon from 'lucide-solid/icons/x';
|
import XIcon from 'lucide-solid/icons/x';
|
||||||
import ShareIcon from 'lucide-solid/icons/link-2';
|
import ShareIcon from 'lucide-solid/icons/link-2';
|
||||||
import FileUpload from './components/FileUpload';
|
import FileUpload from './components/FileUpload';
|
||||||
import { StatsData, ComparisonEntry, parseStats } from '@ns/core';
|
import {
|
||||||
|
StatsData,
|
||||||
|
ComparisonEntry,
|
||||||
|
parseStats,
|
||||||
|
encodeShareUrl,
|
||||||
|
decodeShareUrl,
|
||||||
|
ShareState,
|
||||||
|
} from '@ns/core';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
type ShareState =
|
|
||||||
| { type: 'analysis'; data: Record<string, unknown>; name: string }
|
|
||||||
| {
|
|
||||||
type: 'compare';
|
|
||||||
left: Record<string, unknown>;
|
|
||||||
right: Record<string, unknown>;
|
|
||||||
leftName: string;
|
|
||||||
rightName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function encodeShareUrl(state: ShareState): string {
|
|
||||||
const json = JSON.stringify(state);
|
|
||||||
const encoded = LZUTF8.compress(json, { outputEncoding: 'Base64' }) as string;
|
|
||||||
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeShareUrl(encoded: string): ShareState | null {
|
|
||||||
try {
|
|
||||||
const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
const json = LZUTF8.decompress(base64, {
|
|
||||||
inputEncoding: 'Base64',
|
|
||||||
outputEncoding: 'String',
|
|
||||||
}) as string | null;
|
|
||||||
if (!json) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const state = JSON.parse(json) as ShareState;
|
|
||||||
if (state.type === 'analysis') {
|
|
||||||
if (!state.data || !state.name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else if (state.type === 'compare') {
|
|
||||||
if (!state.left || !state.right || !state.leftName || !state.rightName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function debounce<T extends (...args: Parameters<T>) => ReturnType<T>>(
|
function debounce<T extends (...args: Parameters<T>) => ReturnType<T>>(
|
||||||
fn: T,
|
fn: T,
|
||||||
delay: number,
|
delay: number,
|
||||||
|
|
@ -268,9 +230,14 @@ function App() {
|
||||||
const state: ShareState = { type: 'analysis', data: raw, name };
|
const state: ShareState = { type: 'analysis', data: raw, name };
|
||||||
const encoded = encodeShareUrl(state);
|
const encoded = encodeShareUrl(state);
|
||||||
const url = `${window.location.origin}${window.location.pathname}?share=${encoded}`;
|
const url = `${window.location.origin}${window.location.pathname}?share=${encoded}`;
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard
|
||||||
|
.writeText(url)
|
||||||
|
.then(() => {
|
||||||
setShowShareToast(true);
|
setShowShareToast(true);
|
||||||
setTimeout(() => setShowShareToast(false), 2000);
|
setTimeout(() => setShowShareToast(false), 2000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Failed to copy share URL:', err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -366,9 +333,14 @@ function App() {
|
||||||
};
|
};
|
||||||
const encoded = encodeShareUrl(state);
|
const encoded = encodeShareUrl(state);
|
||||||
const url = `${window.location.origin}${window.location.pathname}?share=${encoded}`;
|
const url = `${window.location.origin}${window.location.pathname}?share=${encoded}`;
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard
|
||||||
|
.writeText(url)
|
||||||
|
.then(() => {
|
||||||
setShowShareToast(true);
|
setShowShareToast(true);
|
||||||
setTimeout(() => setShowShareToast(false), 2000);
|
setTimeout(() => setShowShareToast(false), 2000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Failed to copy share URL:', err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
1487
pnpm-lock.yaml
generated
1487
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue