Skip to content

Commit c6306b3

Browse files
committed
feat: Generate sharable link for code-explorer
1 parent 278de76 commit c6306b3

File tree

4 files changed

+87
-33
lines changed

4 files changed

+87
-33
lines changed

src/App.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,50 @@
1-
import React, { useEffect, useLayoutEffect } from 'react';
1+
import { useLayoutEffect } from 'react';
22
import './App.css';
33
import { Navbar } from './components/navbar';
44
import { useExplorer } from './hooks/use-explorer';
55
import { tools } from './lib/tools';
66
import { Editor } from './components/editor';
77
import { ToolSelector } from './components/tool-selector';
8-
import { ThemeProvider, useTheme } from './components/theme-provider';
8+
import { ThemeProvider } from './components/theme-provider';
9+
import { decodeFromBase64 } from './lib/utils';
910

1011
function App() {
11-
const { theme } = useTheme();
12-
const { language, tool, JSCode, setJSCode, JSONCode, setJSONCode, jsonMode} = useExplorer();
12+
const { setTool, language, tool, JSCode, setJSCode, JSONCode, setJSONCode, setLanguage, setParser,
13+
setSourceType, setEsVersion, setIsJSX, setJsonMode, setWrap, setAstViewMode, setScopeViewMode,
14+
setPathViewMode, setPathIndexes, setPathIndex } = useExplorer();
1315
const activeTool = tools.find(({ value }) => value === tool) ?? tools[0];
14-
16+
17+
useLayoutEffect(() => {
18+
const getUrlState = () => {
19+
try {
20+
const urlState = JSON.parse(decodeFromBase64(window.location.hash.replace(/^#/u, "")));
21+
if (urlState?.state) {
22+
const { tool, JSCode, JSONCode, language, parser, sourceType, esVersion, isJSX,
23+
jsonMode, wrap, astViewMode, scopeViewMode, pathViewMode, pathIndexes, pathIndex } = urlState.state;
24+
25+
setTool(tool);
26+
setJSCode(JSCode);
27+
setJSONCode(JSONCode);
28+
setLanguage(language);
29+
setParser(parser);
30+
setSourceType(sourceType);
31+
setEsVersion(esVersion);
32+
setIsJSX(isJSX);
33+
setJsonMode(jsonMode);
34+
setWrap(wrap);
35+
setAstViewMode(astViewMode);
36+
setScopeViewMode(scopeViewMode);
37+
setPathViewMode(pathViewMode);
38+
setPathIndexes(pathIndexes);
39+
setPathIndex(pathIndex);
40+
}
41+
} catch {
42+
console.error('error while parsing');
43+
}
44+
};
45+
getUrlState();
46+
}, [])
47+
1548
return (
1649
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
1750
<div className="antialiased touch-manipulation font-sans">
@@ -22,9 +55,10 @@ function App() {
2255
<Editor
2356
className="h-[30dvh] sm:h-full"
2457
language={language}
25-
value={language === 'javascript' ? JSCode: JSONCode}
58+
value={language === 'javascript' ? JSCode : JSONCode}
2659
onChange={(value) => {
27-
language === 'javascript' ? setJSCode(value ?? '') : setJSONCode(value ?? '') }}
60+
language === 'javascript' ? setJSCode(value ?? '') : setJSONCode(value ?? '')
61+
}}
2862
/>
2963
<div className="bg-foreground/5 pb-8 overflow-auto h-[70dvh] sm:h-full relative flex flex-col">
3064
<div className="flex sm:items-center flex-col sm:flex-row justify-between p-4 gap-2 z-10">

src/hooks/use-explorer.ts

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { create } from 'zustand';
22
import { devtools, persist } from 'zustand/middleware';
33
import type { Options } from 'espree';
4-
import {defaultJavascriptCode, defaultJSONCode} from '../lib/const'
4+
import { defaultJavascriptCode, defaultJSONCode } from '../lib/const'
5+
import { storeState } from '../lib/utils';
56
export type SourceType = Exclude<Options['sourceType'], undefined>;
67
export type Version = Exclude<Options['ecmaVersion'], undefined>;
78

@@ -22,7 +23,7 @@ type ExplorerState = {
2223
setParser: (parser: string) => void;
2324

2425
sourceType: SourceType;
25-
setSourceType: (sourceType: string) => void;
26+
setSourceType: (sourceType: SourceType) => void;
2627

2728
esVersion: Version;
2829
setEsVersion: (esVersion: string) => void;
@@ -53,62 +54,65 @@ type ExplorerState = {
5354

5455
};
5556

57+
const createSetter = <T extends keyof ExplorerState>(
58+
key: T,
59+
set: (state: Partial<ExplorerState>) => void
60+
) => (value: ExplorerState[T]) => {
61+
set({ [key]: value });
62+
storeState();
63+
};
64+
5665
export const useExplorer = create<ExplorerState>()(
5766
devtools(
5867
persist(
5968
(set) => ({
6069
tool: 'ast',
61-
setTool: (tool) => set({ tool }),
70+
setTool: createSetter('tool', set),
6271

6372
JSCode: defaultJavascriptCode,
64-
setJSCode: (JSCode) => set({ JSCode }),
73+
setJSCode: createSetter('JSCode', set),
6574

6675
JSONCode: defaultJSONCode,
67-
setJSONCode: (JSONCode) => set({ JSONCode }),
76+
setJSONCode: createSetter('JSONCode', set),
6877

6978
language: 'javascript',
70-
setLanguage: (language) => set({ language }),
79+
setLanguage: createSetter('language', set),
7180

7281
parser: 'espree',
73-
setParser: (parser) => set({ parser }),
82+
setParser: createSetter('parser', set),
7483

7584
sourceType: 'module',
76-
setSourceType: (sourceType) =>
77-
set({ sourceType: sourceType as SourceType }),
85+
setSourceType: createSetter('sourceType', set),
7886

7987
esVersion: 'latest',
80-
setEsVersion: (esVersion) =>
81-
set({
82-
esVersion:
83-
esVersion === 'latest'
84-
? 'latest'
85-
: (Number(esVersion) as Options['ecmaVersion']),
86-
}),
88+
setEsVersion: (esVersion) => {
89+
const version = esVersion === 'latest' ? 'latest' : Number(esVersion);
90+
createSetter('esVersion', set)(version as Version);
91+
},
8792

8893
isJSX: true,
89-
setIsJSX: (isJSX) => set({ isJSX }),
94+
setIsJSX: createSetter('isJSX', set),
9095

9196
jsonMode: 'jsonc',
92-
setJsonMode: (mode) => set({ jsonMode: mode }),
97+
setJsonMode: createSetter('jsonMode', set),
9398

9499
wrap: true,
95-
setWrap: (wrap) => set({ wrap }),
100+
setWrap: createSetter('wrap', set),
96101

97102
astViewMode: 'json',
98-
setAstViewMode: (mode) => set({ astViewMode: mode }),
103+
setAstViewMode: createSetter('astViewMode', set),
99104

100105
scopeViewMode: 'flat',
101-
setScopeViewMode: (mode) => set({ scopeViewMode: mode }),
106+
setScopeViewMode: createSetter('scopeViewMode', set),
102107

103108
pathViewMode: 'code',
104-
setPathViewMode: (mode) => set({ pathViewMode: mode }),
109+
setPathViewMode: createSetter('pathViewMode', set),
105110

106111
pathIndexes: 1,
107-
setPathIndexes: (indexes) => set({ pathIndexes: indexes }),
112+
setPathIndexes: createSetter('pathIndexes', set),
108113

109114
pathIndex: 0,
110-
setPathIndex: (index) => set({ pathIndex: index }),
111-
115+
setPathIndex: createSetter('pathIndex', set),
112116
}),
113117
{
114118
name: 'eslint-explorer',

src/lib/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,19 @@ export const cn = (...inputs: ClassValue[]): string => twMerge(clsx(inputs));
66

77
export const capitalize = (substring: string): string =>
88
substring.charAt(0).toUpperCase() + substring.slice(1);
9+
10+
export const
11+
encodeToBase64 = (text: string) => {
12+
return window.btoa(unescape(encodeURIComponent(text)));
13+
};
14+
15+
export const decodeFromBase64 = (base64: any) => {
16+
return decodeURIComponent(escape(window.atob(base64)));
17+
}
18+
19+
export const storeState = () => {
20+
const serializedState = window.localStorage.getItem("eslint-explorer");
21+
const url = new URL(window.location.href);
22+
url.hash = encodeToBase64(serializedState || '');
23+
history.replaceState(null, '', url);
24+
};

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "es5",
3+
"target": "es6",
44
"lib": [
55
"dom",
66
"dom.iterable",

0 commit comments

Comments
 (0)