Skip to content

Commit 0f433eb

Browse files
committed
feat: read the code from hash
1 parent 4a0143a commit 0f433eb

File tree

7 files changed

+85
-9
lines changed

7 files changed

+85
-9
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@types/react-dom": "^18.3.1",
4343
"@vitejs/plugin-react": "^4.3.3",
4444
"codemirror": "^6.0.1",
45+
"fflate": "^0.8.2",
4546
"globals": "^15.11.0",
4647
"typescript": "~5.6.2",
4748
"typescript-eslint": "^8.11.0",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ReplProvider.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { type FC, type PropsWithChildren, useState } from "react"
1+
import { type FC, type PropsWithChildren, useEffect, useState } from "react"
22
import { DEFAULT_REPL_STATE, ReplContext, type ReplState } from "./store"
3+
import { deserialize, serialize } from "./utils"
34

45
interface Props extends PropsWithChildren {
56
config?: ReplState
@@ -13,8 +14,23 @@ export const ReplProvider: FC<Props> = ({ children, config = {} }) => {
1314

1415
const [state, setState] = useState<ReplState>(value)
1516

17+
const onChangeCode = (code: string) => {
18+
setState({ ...state, code })
19+
history.replaceState({}, "", serialize(code))
20+
}
21+
22+
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
23+
useEffect(() => {
24+
try {
25+
const code = deserialize(location.hash)
26+
setState({ ...state, code })
27+
} catch (error) {
28+
setState({ ...state, code: state.defaultCode })
29+
}
30+
}, [])
31+
1632
return (
17-
<ReplContext.Provider value={{ state, setState }}>
33+
<ReplContext.Provider value={{ state, setState, onChangeCode }}>
1834
{children}
1935
</ReplContext.Provider>
2036
)

src/components/Editor/index.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,12 @@ import { useContext } from "react"
44
import CodeMirror from "./CodeMirror"
55

66
const EditorContainer = () => {
7-
const { state, setState } = useContext(ReplContext)
7+
const { state, onChangeCode } = useContext(ReplContext)
88
const { code = "" } = state
99

10-
const onChange = (code: string) => {
11-
setState({ ...state, code })
12-
}
13-
1410
return (
1511
<div>
16-
<CodeMirror code={code} onChange={onChange} />
12+
<CodeMirror code={code} onChange={onChangeCode} />
1713
</div>
1814
)
1915
}

src/components/Output/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const Output = () => {
4040

4141
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
4242
useEffect(() => {
43-
transformOutput(code)
43+
if (code) transformOutput(code)
4444
}, [code])
4545

4646
return (

src/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ export const DEFAULT_REPL_STATE: ReplState = {
7777
export const ReplContext = createContext<{
7878
state: ReplState
7979
setState: Dispatch<ReplState>
80+
onChangeCode: (code: string) => void
8081
}>({
8182
state: DEFAULT_REPL_STATE,
8283
setState: () => {},
84+
onChangeCode: () => {},
8385
})
8486

8587
export const useReplStore = () => {

src/utils/index.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { strFromU8, strToU8, unzlibSync, zlibSync } from "fflate"
2+
import logger from "./logger"
3+
4+
// biome-ignore lint/complexity/noBannedTypes: <explanation>
5+
export function debounce(fn: Function, n = 100) {
6+
let handle: any
7+
return (...args: any[]) => {
8+
if (handle) clearTimeout(handle)
9+
handle = setTimeout(() => {
10+
fn(...args)
11+
}, n)
12+
}
13+
}
14+
15+
export function utoa(data: string): string {
16+
const buffer = strToU8(data)
17+
const zipped = zlibSync(buffer, { level: 9 })
18+
const binary = strFromU8(zipped, true)
19+
return btoa(binary)
20+
}
21+
22+
export function atou(base64: string): string {
23+
const binary = atob(base64)
24+
25+
// zlib header (x78), level 9 (xDA)
26+
if (binary.startsWith("\x78\xDA")) {
27+
const buffer = strToU8(binary, true)
28+
const unzipped = unzlibSync(buffer)
29+
return strFromU8(unzipped)
30+
}
31+
32+
// old unicode hacks for backward compatibility
33+
// https://base64.guru/developers/javascript/examples/unicode-strings
34+
return decodeURIComponent(escape(binary))
35+
}
36+
37+
export function deserialize(serializedState: string) {
38+
if (serializedState.startsWith("#"))
39+
serializedState = serializedState.slice(1)
40+
let saved: any
41+
try {
42+
saved = JSON.parse(atou(serializedState))
43+
} catch (err) {
44+
logger.error(err)
45+
throw err
46+
}
47+
48+
return saved
49+
}
50+
51+
export function serialize(code: string) {
52+
return `#${utoa(JSON.stringify(code))}`
53+
}

0 commit comments

Comments
 (0)