Skip to content

Commit c74874a

Browse files
committed
feat(editor-standalone): load project from query
1 parent 7c33278 commit c74874a

9 files changed

Lines changed: 114 additions & 5 deletions

File tree

apps/editor-standalone/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"test:screen:update": "pnpm run test:screen --update-snapshots"
1515
},
1616
"imports": {
17-
"#screens/*": "./src/screens/*/index.ts"
17+
"#screens/*": "./src/screens/*/index.ts",
18+
"#shared/lib/*": "./src/shared/lib/*/index.ts"
1819
},
1920
"dependencies": {
2021
"@effector/next": "catalog:",
@@ -24,6 +25,7 @@
2425
"effector": "catalog:",
2526
"effector-react": "catalog:",
2627
"effector-storage": "catalog:",
28+
"nanoid": "catalog:",
2729
"next": "catalog:",
2830
"patronum": "catalog:",
2931
"react": "catalog:",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createFactory } from '@withease/factories'
2+
import { sample } from 'effector'
3+
import { createGate } from 'effector-react'
4+
import { not, readonly } from 'patronum'
5+
6+
export type EditorGate = ReturnType<typeof createEditorGate>
7+
8+
export const createEditorGate = createFactory(() => {
9+
const Gate = createGate<{}>()
10+
11+
const $isOpened = Gate.status
12+
13+
const opened = sample({ clock: $isOpened, filter: $isOpened })
14+
const closed = sample({ clock: $isOpened, filter: not($isOpened) })
15+
16+
return {
17+
$isOpened: readonly($isOpened),
18+
Gate,
19+
closed,
20+
opened,
21+
}
22+
})
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { defaultProject } from './default-project'
1+
export * as model from './screen'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Project } from '@repo/editor'
2+
import { createFactory } from '@withease/factories'
3+
import { createEvent, createStore, sample } from 'effector'
4+
import { persist as persistInQuery } from 'effector-storage/query'
5+
import { once, readonly } from 'patronum'
6+
import { base64Url } from '#shared/lib/base64-url'
7+
import { defaultProject } from './default-project'
8+
import type { EditorGate } from './gate'
9+
10+
export type ProjectSerializerStore = ReturnType<typeof createProjectSerializer>
11+
12+
type Params = {
13+
gate: EditorGate
14+
}
15+
16+
export const createProjectSerializer = createFactory((params: Params) => {
17+
const { gate } = params
18+
19+
const $loadedProject = createStore<Project>(defaultProject)
20+
21+
const userChangedProject = createEvent<Project>()
22+
const storageUpdated = createEvent<Project | null>()
23+
24+
persistInQuery({
25+
key: 'project',
26+
source: userChangedProject,
27+
pickup: sample({
28+
clock: gate.opened,
29+
// TODO: maybe find another way to fix this
30+
filter: () => !process.env.STORYBOOK,
31+
}),
32+
target: storageUpdated,
33+
serialize: obj => base64Url.encode(JSON.stringify(obj)),
34+
deserialize: query => JSON.parse(base64Url.decode(query)) as Project,
35+
})
36+
37+
sample({
38+
clock: once(storageUpdated),
39+
filter: project => project !== null,
40+
target: $loadedProject,
41+
})
42+
43+
return {
44+
$loadedProject: readonly($loadedProject),
45+
userChangedProject,
46+
}
47+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { invoke } from '@withease/factories'
2+
import { createEditorGate } from './gate'
3+
import { createProjectSerializer } from './project-serializer'
4+
5+
const gate = invoke(createEditorGate)
6+
const serializer = invoke(createProjectSerializer, { gate })
7+
8+
const { Gate } = gate
9+
const { $loadedProject, userChangedProject } = serializer
10+
11+
export { $loadedProject, Gate, userChangedProject }
Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
'use client'
22

33
import { Editor } from '@repo/editor'
4-
import type { FC } from 'react'
5-
import { defaultProject } from '../model'
4+
import { useUnit } from 'effector-react'
5+
import { nanoid } from 'nanoid'
6+
import { type FC, useMemo } from 'react'
7+
import { model } from '../model'
68
import { EditorHeader } from './editor-header'
79

810
export const EditorScreen: FC = () => {
11+
const { loadedProject, onSerialized } = useUnit({
12+
loadedProject: model.$loadedProject,
13+
onSerialized: model.userChangedProject,
14+
})
15+
16+
// remount Editor when a new project is loaded
17+
const editorKey = useMemo(() => loadedProject && nanoid(), [loadedProject])
18+
919
return (
1020
<div className="flex h-screen w-screen flex-col">
21+
<model.Gate />
1122
<EditorHeader />
12-
<Editor initialProject={defaultProject} />
23+
<Editor key={editorKey} initialProject={loadedProject} onSerialized={onSerialized} />
1324
</div>
1425
)
1526
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const encode = (str: string) => {
2+
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(Number(`0x${p1}`))))
3+
}
4+
5+
export const decode = (str: string) => {
6+
return decodeURIComponent(
7+
atob(str)
8+
.split('')
9+
.map(c => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
10+
.join(''),
11+
)
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * as base64Url from './base64-url'

pnpm-lock.yaml

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

0 commit comments

Comments
 (0)