Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
import { Menu } from './navigation/Navigation'
import { mobileAtom, settingsAtom } from './settings/settings-atoms'
import { lightThemes } from './settings/settings-types'
import { freshlyImportedCodeAtom } from './store/import-atoms'
import { importedCodeAtom } from './store/import-atoms'
import { currentProjectAtom } from './store/project-atoms'
import { screenWidthAtom } from './store/window-atoms'
import { save } from './utils/SaveToFile'

/** Returns true if the browser wants dark mode */
function isBrowserDefaultDark() {

Check warning on line 25 in client/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint

'isBrowserDefaultDark' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 25 in client/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint

'isBrowserDefaultDark' is defined but never used. Allowed unused vars must match /^_/u
return window.matchMedia('(prefers-color-scheme: dark)').matches
}

Expand All @@ -37,7 +37,7 @@
const [, setScreenWidth] = useAtom(screenWidthAtom)
const [project] = useAtom(currentProjectAtom)
const [code, setCode] = useAtom(codeAtom)
const [freshlyImportedCode] = useAtom(freshlyImportedCodeAtom)
const [importedCode] = useAtom(importedCodeAtom)

const model = editor?.getModel()

Expand Down Expand Up @@ -207,12 +207,12 @@
leanMonacoEditor.dispose()
_leanMonaco.dispose()
}
}, [infoviewRef, editorRef, options, project, settings])

Check warning on line 210 in client/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint

React Hook useEffect has missing dependencies: 'code', 'mobile', and 'setCode'. Either include them or remove the dependency array

/** Set editor content to the code loaded from the URL */
useEffect(() => {
if (freshlyImportedCode && model) model.setValue(freshlyImportedCode)
}, [freshlyImportedCode, model])
if (importedCode && model) model.setValue(importedCode)
}, [importedCode, model])

// Disable monaco context menu outside the editor
useEffect(() => {
Expand Down
48 changes: 27 additions & 21 deletions client/src/editor/code-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,55 @@ import { atom } from 'jotai'
import LZString from 'lz-string'

import { settingsAtom } from '../settings/settings-atoms'
import { freshlyImportedCodeAtom, importedCodeAtom, importUrlAtom } from '../store/import-atoms'
import { importedCodeAtom, importUrlAtom, importUrlBaseAtom } from '../store/import-atoms'
import { urlArgsAtom, urlArgsStableAtom } from '../store/url-atoms'
import { fixedEncodeURIComponent } from '../utils/UrlParsing'

/** Atom which represents the editor content and synchronises it with the url hash. */
export const codeAtom = atom(
(get) => {
const urlArgs = get(urlArgsStableAtom)
if (urlArgs.code) {
return decodeURIComponent(urlArgs.code)
if (urlArgs.url) {
return get(importedCodeAtom)
} else if (urlArgs.code) {
return urlArgs.code
}
if (urlArgs.codez) {
return LZString.decompressFromBase64(urlArgs.codez)
} else {
return ''
}
return get(freshlyImportedCodeAtom)
},
(get, set, code: string) => {
const urlArgs = get(urlArgsAtom)
if (urlArgs.url) {
// store the import URL so we can display it later again
set(importUrlBaseAtom, urlArgs.url)
}
if (code.length == 0) {
// delete all url arguments if there is no code
set(urlArgsAtom, { ...urlArgs, url: undefined, code: undefined, codez: undefined })
return
}
if (urlArgs.url) {
// store freshly imported code and the URL so we can compare later
const freshlyImportedCode = get(freshlyImportedCodeAtom)
set(importedCodeAtom, freshlyImportedCode)
set(importUrlAtom, urlArgs.url)
} else {
// if the code matches previously imported code, display the URL instead
const importedCode = get(importedCodeAtom)
if (importedCode !== undefined && importedCode === code) {
const importUrl = get(importUrlAtom)
set(urlArgsAtom, { ...urlArgs, url: importUrl, code: undefined, codez: undefined })
return
}
}
const compress = get(settingsAtom).compress
if (compress) {
const importedCode = get(importedCodeAtom)
const url = get(importUrlAtom) ?? ''
if (code == importedCode) {
set(urlArgsAtom, {
...urlArgs,
url: fixedEncodeURIComponent(url),
code: undefined,
codez: undefined,
})
} else if (get(settingsAtom).compress) {
// LZ padds the string with trailing `=`, which mess up the argument parsing
// and aren't needed for LZ encoding, so we remove them.
const compressedCode = LZString.compressToBase64(code).replace(/=*$/, '')
set(urlArgsAtom, { ...urlArgs, url: undefined, code: undefined, codez: compressedCode })
set(urlArgsAtom, {
...urlArgs,
url: undefined,
code: undefined,
codez: fixedEncodeURIComponent(compressedCode),
})
} else {
const encodedCode = fixedEncodeURIComponent(code)
set(urlArgsAtom, { ...urlArgs, url: undefined, code: encodedCode, codez: undefined })
Expand Down
5 changes: 2 additions & 3 deletions client/src/settings/SettingsPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useState } from 'react'

import { Popup } from '../navigation/Popup'
import { shallowEqualSubset } from '../utils/shallowEqual'
import { applySettingsAtom, settingsAtom } from './settings-atoms'
import { settingsAtom } from './settings-atoms'
import type { MobileValues, Theme } from './settings-types'
import { defaultSettings, Settings } from './settings-types'

Expand All @@ -19,8 +19,7 @@ export function SettingsPopup({
handleClose: () => void
closeNav: () => void
}) {
const [settings, setSettings] = useAtom(settingsAtom)
const [, applySettings] = useAtom(applySettingsAtom)
const [settings, applySettings] = useAtom(settingsAtom)
const [newSettings, setNewSettings] = useState<Settings>(settings)

function updateSetting<K extends keyof Settings>(key: K, value: Settings[K]) {
Expand Down
52 changes: 26 additions & 26 deletions client/src/settings/settings-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,35 @@ const settingsBaseAtom = atom<Settings>({ saved: false, inUrl: false, ...default
* - current (local) state (base)
* - default values (base)
*/
export const settingsAtom = atom((get) => {
const base = get(settingsBaseAtom)
const store = cleanObject(get(settingsStoreAtom))
const url = cleanObject(get(settingsUrlStableAtom))
return {
...base,
...store,
...url,
saved: Object.entries(store).length > 0,
inUrl: Object.entries(url).length > 0,
} as Settings
})

/** Set the new settings, and write them to browser storage or URL if desired */
export const applySettingsAtom = atom(null, (get, set, val: Settings) => {
const { saved, inUrl, ...settingsToStore } = val
export const settingsAtom = atom(
(get) => {
const base = get(settingsBaseAtom)
const store = cleanObject(get(settingsStoreAtom))
const url = cleanObject(get(settingsUrlStableAtom))
return {
...base,
...store,
...url,
saved: Object.entries(store).length > 0,
inUrl: Object.entries(url).length > 0,
} as Settings
},
(get, set, val: Settings) => {
const { saved, inUrl, ...settingsToStore } = val

set(settingsBaseAtom, val)
set(settingsBaseAtom, val)

if (saved) {
set(settingsStoreAtom, settingsToStore)
} else {
localStorage.removeItem('lean4web:settings')
}
if (saved) {
set(settingsStoreAtom, settingsToStore)
} else {
localStorage.removeItem('lean4web:settings')
}

const newSearchParams = inUrl ? encodeSettingsToURL(settingsToStore) : new URLSearchParams()
const location = get(locationAtom)
set(locationAtom, { ...location, searchParams: newSearchParams })
})
const newSearchParams = inUrl ? encodeSettingsToURL(settingsToStore) : new URLSearchParams()
const location = get(locationAtom)
set(locationAtom, { ...location, searchParams: newSearchParams })
},
)

/** Indicates whether mobile layout should be used */
export const mobileAtom = atom((get) => {
Expand Down
51 changes: 32 additions & 19 deletions client/src/store/import-atoms.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { atom } from 'jotai'
import { atomWithQuery } from 'jotai-tanstack-query'

import { lookupUrl } from '../utils/UrlParsing'
import { fixedEncodeURIComponent, lookupUrl } from '../utils/UrlParsing'
import { currentProjectAtom } from './project-atoms'
import { urlArgsAtom, urlArgsStableAtom } from './url-atoms'

/**
Expand All @@ -10,19 +11,29 @@ import { urlArgsAtom, urlArgsStableAtom } from './url-atoms'
* This is needed for the comparison which puts the URL back into the location hash if
* the current code matches the one from the import-URL.
*/
export const importUrlAtom = atom<string>()
export const importUrlBaseAtom = atom<string>()

/**
* Stores the imported code.
*
* This is needed for the comparison which puts the URL back into the location hash if
* the current code matches the one from the import-URL.
*
*/
export const importedCodeAtom = atom<string>()
export const importUrlAtom = atom(
(get) => get(urlArgsStableAtom).url ?? get(importUrlBaseAtom),
(get, set, url: string) => {
const urlArgs = get(urlArgsStableAtom)
set(importUrlBaseAtom, url)
set(urlArgsAtom, {
...urlArgs,
url: fixedEncodeURIComponent(url),
code: undefined,
codez: undefined,
})
},
)

/** Query to fetch the code from the import URL */
const freshlyImportedCodeQueryAtom = atomWithQuery((get) => {
const url = get(urlArgsStableAtom).url
const importedCodeQueryAtom = atomWithQuery((get) => {
const url = get(importUrlAtom)
return {
queryKey: ['importedCode', url],
queryFn: async () => {
Expand All @@ -31,13 +42,19 @@ const freshlyImportedCodeQueryAtom = atomWithQuery((get) => {
const code = res.ok ? await res.text() : `Error: failed to load code from ${url}`
return code
},
enabled: url !== undefined,
enabled: url != undefined,
keepPreviousData: true,
}
})

export const freshlyImportedCodeAtom = atom((get) => {
const { data } = get(freshlyImportedCodeQueryAtom)
/**
* Stores the imported code.
*
* This is needed for the comparison which puts the URL back into the location hash if
* the current code matches the one from the import-URL.
*/
export const importedCodeAtom = atom((get) => {
const { data } = get(importedCodeQueryAtom)
return data
})

Expand All @@ -49,13 +66,9 @@ export const freshlyImportedCodeAtom = atom((get) => {
export const setImportUrlAndProjectAtom = atom(
null,
(get, set, val: { url: string; project?: string }) => {
const urlArgs = get(urlArgsStableAtom)
set(urlArgsAtom, {
...urlArgs,
url: val.url,
project: val.project ?? urlArgs.project,
code: undefined,
codez: undefined,
})
set(importUrlAtom, val.url) // TODO: should there be some decoding of the input?
if (val.project) {
set(currentProjectAtom, val.project)
}
},
)
2 changes: 1 addition & 1 deletion client/src/store/project-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { atomWithQuery } from 'jotai-tanstack-query'
import { LeanWebProject } from '../api/project-types'
import { urlArgsAtom, urlArgsStableAtom } from './url-atoms'

const projectsQueryAtom = atomWithQuery<LeanWebProject[]>((get) => ({
const projectsQueryAtom = atomWithQuery<LeanWebProject[]>(() => ({
queryKey: ['projects'],
queryFn: async () => {
const res = await fetch(`/api/projects`)
Expand Down
2 changes: 1 addition & 1 deletion client/src/store/url-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ export const urlArgsAtom = atom(
)

// Prevent updates unless there is a value change
export const urlArgsStableAtom = selectAtom(urlArgsAtom, (settings) => settings, shallowEqual)
export const urlArgsStableAtom = selectAtom(urlArgsAtom, (args) => args, shallowEqual)
1 change: 1 addition & 0 deletions client/src/store/url-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ export function parseArgs(hash: string): UrlArgs {
.split('&')
.map((s) => s.split('='))
.filter((x) => x[0])
.map(([key, val]) => [key, decodeURIComponent(val)])
return Object.fromEntries(args)
}
4 changes: 2 additions & 2 deletions doc/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ graph TD;
%% setter
importUrlAtom -.-> urlArgsAtom;
codeAtom -.-> urlArgsAtom;
codeAtom -.-> importUrlBaseAtom;
settingsAtom -.-> locationAtom;

setImportUrlAndProjectAtom[/setImportUrlAndProjectAtom/]
importUrlAtom <-.-> setImportUrlAndProjectAtom
currentProjectAtom <-.-> setImportUrlAndProjectAtom

%% Styles

linkStyle 16 stroke: red;
linkStyle 17 stroke: red;
linkStyle 18 stroke: red;
Expand All @@ -94,6 +94,7 @@ graph TD;
linkStyle 23 stroke: red;
linkStyle 24 stroke: red;
linkStyle 25 stroke: red;
linkStyle 26 stroke: red;

classDef query fill:#d0ebff,stroke:#1c7ed6,stroke-width:2px;
classDef storage fill:#d3f9d8,stroke:#2b8a3e,stroke-width:2px;
Expand All @@ -112,5 +113,4 @@ graph TD;

%% Setter
class setImportUrlAndProjectAtom setter;
class applySettingsAtom setter;
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading