Skip to content

Commit

Permalink
feat: capture url
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Mar 15, 2024
1 parent 3dbf957 commit ecbf833
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 17 deletions.
18 changes: 14 additions & 4 deletions src/dev/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,23 @@ export function devtools<
const app = new Hono()
app
.get('/', async (c) => {
const url = new URL(c.req.url)
const { origin } = new URL(c.req.url)
const baseUrl = `${origin}${devBasePath}`

let frameUrls: string[] = []
let initialData: Bootstrap['data'] = undefined
if (routes.length) {
frameUrls = getFrameUrls(url.origin, routes)
initialData = (await getInitialData(frameUrls[0])) as Bootstrap['data']
frameUrls = getFrameUrls(origin, routes)

let frameUrl = frameUrls[0]
const url = c.req.query('url')
if (url) {
const tmpUrl = `${origin}${url}`
if (url.startsWith('/')) frameUrl = tmpUrl
else if (frameUrls.includes(url)) frameUrl = url
}

initialData = (await getInitialData(frameUrl)) as Bootstrap['data']
}

let user: User | undefined = undefined
Expand Down Expand Up @@ -116,7 +126,7 @@ export function devtools<
<title>frog</title>

<script type="module">
{html`globalThis.__FROG_BASE_URL__ = '${c.req.url}'`}
{html`globalThis.__FROG_BASE_URL__ = '${baseUrl}'`}
</script>

<script
Expand Down
2 changes: 2 additions & 0 deletions ui/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare const __FROG_BASE_URL__: string
export const baseUrl = __FROG_BASE_URL__
10 changes: 4 additions & 6 deletions ui/src/hooks/useScrollLock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ type UseScrollLockReturnType = {
unlock: () => void
}

type OriginalStyle = {
overflow: CSSStyleDeclaration['overflow']
paddingRight: CSSStyleDeclaration['paddingRight']
}

export function useScrollLock(
parameters: UseScrollLockParameters = {},
): UseScrollLockReturnType {
const { autoLock = true, lockTarget, widthReflow = true } = parameters
const [isLocked, setIsLocked] = useState(false)
const target = useRef<HTMLElement | null>(null)
const originalStyle = useRef<OriginalStyle | null>(null)
const originalStyle = useRef<{
overflow: CSSStyleDeclaration['overflow']
paddingRight: CSSStyleDeclaration['paddingRight']
} | null>(null)

const lock = () => {
if (target.current) {
Expand Down
5 changes: 2 additions & 3 deletions ui/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { hc } from 'hono/client'

declare const __FROG_BASE_URL__: string
const apiUrl = `${__FROG_BASE_URL__}/api`
import { baseUrl } from '../constants.js'

type Route = import('../../../src/dev/api.js').ApiRoutes
export const client = hc<Route>(apiUrl)
export const client = hc<Route>(`${baseUrl}/api`)

export type Client = typeof client
14 changes: 11 additions & 3 deletions ui/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createStore } from 'zustand/vanilla'
import { subscribeWithSelector } from 'zustand/middleware'

import { Bootstrap, Data, User } from '../types/frog'
import { deepEqual } from '../utils/deepEqual'

export type State = {
dataKey: string
Expand All @@ -20,6 +21,7 @@ export type State = {
stack: string[]
user: User | null | undefined
tab: 'context' | 'meta-tags' | 'request' | 'state'
skipSaveStateToQueryHash: boolean
}

const initialState: State = {
Expand All @@ -38,12 +40,11 @@ const initialState: State = {
stack: [],
user: undefined,
tab: 'request',
skipSaveStateToQueryHash: false,
} satisfies State

export const store = createStore(subscribeWithSelector(() => initialState))

const hashKey = 'state'

export function hydrateStore(bootstrap: Bootstrap) {
const { data, frameUrls, user } = bootstrap

Expand Down Expand Up @@ -78,9 +79,10 @@ export function hydrateStore(bootstrap: Bootstrap) {
watchState()
}

export const hashKey = 'state'

function restoreState() {
try {
console.debug('[frog] restoring state...')
const state = location.hash.replace(`#${hashKey}/`, '').trim()
let restored = lz.decompressFromEncodedURIComponent(state)
// Fallback incase there is an extra level of decoding:
Expand All @@ -91,6 +93,7 @@ function restoreState() {
const parsed = JSON.parse(restored ?? 'null')
if (!parsed) return undefined

console.debug('[frog] restoring state...')
const logIndex =
parsed.logIndex === (parsed.logs?.length ?? 0) - 1 ? -1 : parsed.logIndex
console.debug('[frog] restored state.')
Expand All @@ -114,8 +117,13 @@ function watchState() {
tab: state.tab,
}),
(slice) => {
if (store.getState().skipSaveStateToQueryHash) return

const compressed = lz.compressToEncodedURIComponent(JSON.stringify(slice))
window.history.replaceState(null, '', `#${hashKey}/${compressed}`)
},
{
equalityFn: deepEqual,
},
)
}
21 changes: 20 additions & 1 deletion ui/src/utils/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type MouseEvent } from 'react'
import { Data } from '../types/frog.js'
import { client } from '../lib/api.js'
import { store } from '../lib/store.js'
import { baseUrl } from '../constants.js'

export async function handlePost(button: {
index: number
Expand Down Expand Up @@ -172,6 +173,8 @@ export async function handleForward() {
}

export async function handleReload(event: MouseEvent) {
store.setState((state) => ({ ...state, skipSaveStateToQueryHash: true }))

const { dataKey, dataMap, logs } = store.getState()
const data = dataMap[dataKey]
if (!data) return
Expand Down Expand Up @@ -208,11 +211,16 @@ export async function handleReload(event: MouseEvent) {
logs: [...state.logs, id],
logIndex: -1,
}))

store.setState((state) => ({ ...state, skipSaveStateToQueryHash: false }))
}

export async function handleSelectNewFrame(url: string) {
store.setState((state) => ({ ...state, skipSaveStateToQueryHash: true }))

const encodedUrl = encodeURIComponent(url)
const json = await client.frames[':url']
.$get({ param: { url: encodeURIComponent(url) } })
.$get({ param: { url: encodedUrl } })
.then((response) => response.json())

const id = json.id
Expand All @@ -227,4 +235,15 @@ export async function handleSelectNewFrame(url: string) {
stackIndex: 0,
tab: 'request',
}))

const frameUrl = new URL(url)
const nextUrl = new URL(baseUrl)
if (frameUrl.pathname !== '/') {
const params = new URLSearchParams(nextUrl.search)
params.set('url', frameUrl.pathname)
nextUrl.search = params.toString()
}

history.replaceState(null, '', nextUrl.toString())
store.setState((state) => ({ ...state, skipSaveStateToQueryHash: false }))
}
42 changes: 42 additions & 0 deletions ui/src/utils/deepEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/** Forked from https://github.com/epoberezkin/fast-deep-equal */
export function deepEqual(a: any, b: any) {
if (a === b) return true

if (a && b && typeof a === 'object' && typeof b === 'object') {
if (a.constructor !== b.constructor) return false

let length: number
let i: number

if (Array.isArray(a) && Array.isArray(b)) {
length = a.length
if (length !== b.length) return false
for (i = length; i-- !== 0; ) if (!deepEqual(a[i], b[i])) return false
return true
}

if (a.valueOf !== Object.prototype.valueOf)
return a.valueOf() === b.valueOf()
if (a.toString !== Object.prototype.toString)
return a.toString() === b.toString()

const keys = Object.keys(a)
length = keys.length
if (length !== Object.keys(b).length) return false

for (i = length; i-- !== 0; )
if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false

for (i = length; i-- !== 0; ) {
const key = keys[i]

if (key && !deepEqual(a[key], b[key])) return false
}

return true
}

// true if both NaN, false otherwise
// biome-ignore lint/suspicious/noSelfCompare: <explanation>
return a !== a && b !== b
}

0 comments on commit ecbf833

Please sign in to comment.