[standalone_app] Use React.Context instead of Recoil

This commit is contained in:
c-bata
2024-03-11 12:34:21 +09:00
parent 7c5ae62b4e
commit b8f013f4ad
13 changed files with 251 additions and 225 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
"description": "Dashboard for Optuna",
"main": "index.js",
"scripts": {
"fmt": "biome format --write .",
"fmt": "biome format --write . && biome check standalone_app --apply",
"lint": "npm run lint:eslint && npm run lint:biome",
"lint:eslint": "eslint . --ext .ts,.tsx --max-warnings 0",
"lint:biome": "biome format . && biome ci standalone_app",
+25 -63
View File
@@ -20,8 +20,7 @@
"plotly.js-dist-min": "^2.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.2",
"recoil": "^0.7.7"
"react-router-dom": "^6.22.3"
},
"devDependencies": {
"@types/plotly.js": "^2.29.2",
@@ -1325,9 +1324,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz",
"integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==",
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
"engines": {
"node": ">=14.0.0"
}
@@ -2577,11 +2576,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/hamt_plus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
"integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -3165,11 +3159,11 @@
}
},
"node_modules/react-router": {
"version": "6.22.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz",
"integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==",
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
"dependencies": {
"@remix-run/router": "1.15.2"
"@remix-run/router": "1.15.3"
},
"engines": {
"node": ">=14.0.0"
@@ -3179,12 +3173,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.22.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz",
"integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==",
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
"dependencies": {
"@remix-run/router": "1.15.2",
"react-router": "6.22.2"
"@remix-run/router": "1.15.3",
"react-router": "6.22.3"
},
"engines": {
"node": ">=14.0.0"
@@ -3221,25 +3215,6 @@
"node": ">= 10.13.0"
}
},
"node_modules/recoil": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
"integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
"dependencies": {
"hamt_plus": "1.0.2"
},
"peerDependencies": {
"react": ">=16.13.1"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@@ -4638,9 +4613,9 @@
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
},
"@remix-run/router": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz",
"integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q=="
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w=="
},
"@rollup/rollup-android-arm-eabi": {
"version": "4.12.1",
@@ -5586,11 +5561,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"hamt_plus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
"integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -6012,20 +5982,20 @@
"dev": true
},
"react-router": {
"version": "6.22.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz",
"integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==",
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
"requires": {
"@remix-run/router": "1.15.2"
"@remix-run/router": "1.15.3"
}
},
"react-router-dom": {
"version": "6.22.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz",
"integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==",
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
"requires": {
"@remix-run/router": "1.15.2",
"react-router": "6.22.2"
"@remix-run/router": "1.15.3",
"react-router": "6.22.3"
}
},
"react-transition-group": {
@@ -6048,14 +6018,6 @@
"resolve": "^1.20.0"
}
},
"recoil": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
"integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
"requires": {
"hamt_plus": "1.0.2"
}
},
"regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+1 -2
View File
@@ -32,7 +32,6 @@
"plotly.js-dist-min": "^2.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.2",
"recoil": "^0.7.7"
"react-router-dom": "^6.22.3"
}
}
+3 -3
View File
@@ -1,13 +1,13 @@
import React from "react"
import ReactDOM from "react-dom/client"
import { RecoilRoot } from "recoil"
import { App } from "./components/App"
import { StorageProvider } from "./components/StorageProvider"
import "./index.css"
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<RecoilRoot>
<StorageProvider>
<App />
</RecoilRoot>
</StorageProvider>
</React.StrictMode>
)
@@ -13,17 +13,15 @@ import React, {
MouseEventHandler,
useRef,
useState,
useContext,
} from "react"
import { useSetRecoilState } from "recoil"
import { loadJournalStorage } from "../journalStorage"
import { loadSQLite3Storage } from "../sqlite3"
import { studiesState } from "../state"
import { StorageContext, getStorage } from "./StorageProvider"
export const StorageLoader: FC = () => {
const theme = useTheme()
const [dragOver, setDragOver] = useState<boolean>(false)
const { setStorage } = useContext(StorageContext)
const setStudies = useSetRecoilState<Study[]>(studiesState)
const inputRef = useRef<HTMLInputElement>(null)
const loadStorageFromFile = (file: File): void => {
@@ -31,13 +29,8 @@ export const StorageLoader: FC = () => {
r.addEventListener("load", () => {
const arrayBuffer = r.result as ArrayBuffer | null
if (arrayBuffer !== null) {
const header = new Uint8Array(arrayBuffer, 0, 16)
const headerString = new TextDecoder().decode(header)
if (headerString === "SQLite format 3\u0000") {
loadSQLite3Storage(arrayBuffer, setStudies)
} else {
loadJournalStorage(arrayBuffer, setStudies)
}
const s = getStorage(arrayBuffer)
setStorage(s)
}
})
r.readAsArrayBuffer(file)
@@ -0,0 +1,31 @@
import React, { FC, createContext, useState } from "react"
import { JournalFileStorage } from "../journalStorage"
import { SQLite3Storage } from "../sqlite3"
export const StorageContext = createContext<{
storage: OptunaStorage | null
setStorage: (storage: OptunaStorage) => void
}>({
storage: null,
setStorage: () => {},
})
export const getStorage = (arrayBuffer: ArrayBuffer): OptunaStorage => {
const header = new Uint8Array(arrayBuffer, 0, 16)
const headerString = new TextDecoder().decode(header)
if (headerString === "SQLite format 3\u0000") {
return new SQLite3Storage(arrayBuffer)
}
return new JournalFileStorage(arrayBuffer)
}
export const StorageProvider: FC<{
children: React.ReactNode
}> = ({ children }) => {
const [storage, setStorage] = useState<OptunaStorage | null>(null)
return (
<StorageContext.Provider value={{ storage, setStorage }}>
{children}
</StorageContext.Provider>
)
}
+17 -11
View File
@@ -13,30 +13,36 @@ import {
useTheme,
} from "@mui/material"
import Grid2 from "@mui/material/Unstable_Grid2"
import React, { FC } from "react"
import React, { FC, useContext, useState, useEffect } from "react"
import { Link, useParams } from "react-router-dom"
import { useRecoilValue } from "recoil"
import { studiesState } from "../state"
import { PlotHistory } from "./PlotHistory"
import { PlotImportance } from "./PlotImportance"
import { PlotIntermediateValues } from "./PlotIntermediateValues"
import { StorageContext } from "./StorageProvider"
import { TrialTable } from "./TrialTable"
const useStudyValue = (idx: number): Study | null => {
const studies = useRecoilValue<Study[]>(studiesState)
return studies[idx] || null
}
export const StudyDetail: FC<{
toggleColorMode: () => void
}> = ({ toggleColorMode }) => {
const theme = useTheme()
const { idx } = useParams<{ idx: string }>()
const idxNumber = parseInt(idx || "", 10)
const study = useStudyValue(idxNumber)
const { storage } = useContext(StorageContext)
const [study, setStudy] = useState<Study | null>(null)
useEffect(() => {
const fetchStudy = async () => {
if (storage === null) {
return
}
const study = await storage.getStudy(idxNumber)
setStudy(study)
}
fetchStudy()
}, [storage, idxNumber])
return (
<div>
<>
<AppBar position="static">
<Container
sx={{
@@ -133,6 +139,6 @@ export const StudyDetail: FC<{
</Card>
</>
</Container>
</div>
</>
)
}
+23 -6
View File
@@ -19,23 +19,40 @@ import {
useTheme,
} from "@mui/material"
import { styled } from "@mui/system"
import React, { FC, useState, useMemo, useDeferredValue } from "react"
import React, {
FC,
useEffect,
useContext,
useState,
useMemo,
useDeferredValue,
} from "react"
import { Link } from "react-router-dom"
import { useRecoilValue } from "recoil"
import { studiesState } from "../state"
import { StorageLoader } from "./StorageLoader"
import { StorageContext } from "./StorageProvider"
export const StudyList: FC<{
toggleColorMode: () => void
}> = ({ toggleColorMode }) => {
const theme = useTheme()
const studies = useRecoilValue<Study[]>(studiesState)
const { storage } = useContext(StorageContext)
const [studies, setStudies] = useState<StudySummary[]>([])
const [_studyFilterText, setStudyFilterText] = useState<string>("")
const [sortBy, setSortBy] = useState<"id-asc" | "id-desc">("id-asc")
const studyFilterText = useDeferredValue(_studyFilterText)
useEffect(() => {
const fetchStudies = async () => {
if (storage === null) {
return
}
const studies = await storage.getStudies()
setStudies(studies)
}
fetchStudies()
}, [storage])
const filteredStudies = useMemo(() => {
const studyFilter = (row: Study): boolean => {
const studyFilter = (row: StudySummary): boolean => {
const keywords = studyFilterText.split(" ")
return !keywords.every((k) => {
if (k === "") {
@@ -44,7 +61,7 @@ export const StudyList: FC<{
return row.study_name.indexOf(k) >= 0
})
}
let filteredStudies: Study[] = studies.filter((s) => !studyFilter(s))
let filteredStudies: StudySummary[] = studies.filter((s) => !studyFilter(s))
if (sortBy === "id-desc") {
filteredStudies = filteredStudies.reverse()
}
+15 -8
View File
@@ -1,5 +1,3 @@
import { SetterOrUpdater } from "recoil"
// JournalStorage
enum JournalOperation {
CREATE_STUDY = 0,
@@ -329,10 +327,20 @@ class JournalStorage {
}
}
export const loadJournalStorage = (
arrayBuffer: ArrayBuffer,
setter: SetterOrUpdater<Study[]>
): void => {
export class JournalFileStorage implements OptunaStorage {
studies: Study[]
constructor(arrayBuffer: ArrayBuffer) {
this.studies = loadJournalStorage(arrayBuffer)
}
getStudies = async (): Promise<StudySummary[]> => {
return this.studies
}
getStudy = async (idx: number): Promise<Study | null> => {
return this.studies[idx] || null
}
}
export const loadJournalStorage = (arrayBuffer: ArrayBuffer): Study[] => {
const decoder = new TextDecoder("utf-8")
const logs = decoder.decode(arrayBuffer).split("\n")
@@ -383,6 +391,5 @@ export const loadJournalStorage = (
}
}
const studies = journalStorage.getStudies()
setter((prev) => [...prev, ...studies])
return journalStorage.getStudies()
}
+109 -88
View File
@@ -1,6 +1,5 @@
// @ts-ignore
import sqlite3InitModule from "@sqlite.org/sqlite-wasm"
import { SetterOrUpdater } from "recoil"
type SQLite3DB = {
exec(options: {
@@ -10,44 +9,57 @@ type SQLite3DB = {
}): void
}
export const loadSQLite3Storage = (
arrayBuffer: ArrayBuffer,
setter: SetterOrUpdater<Study[]>
): void => {
sqlite3InitModule({
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
print: (...args: any): void => {
console.log(args)
},
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
printErr: (...args: any): void => {
console.log(args)
},
// @ts-ignore
}).then((sqlite3) => {
const p = sqlite3.wasm.allocFromTypedArray(arrayBuffer)
const db = new sqlite3.oo1.DB()
const rc = sqlite3.capi.sqlite3_deserialize(
export class SQLite3Storage implements OptunaStorage {
db: Promise<SQLite3DB>
summaries_cache: StudySummary[] | null
constructor(arrayBuffer: ArrayBuffer) {
this.db = this.initDB(arrayBuffer)
this.summaries_cache = null
}
async initDB(arrayBuffer: ArrayBuffer): Promise<SQLite3DB> {
return sqlite3InitModule({
print: console.log,
printErr: console.log,
// @ts-ignore
db.pointer,
"main",
p,
arrayBuffer.byteLength,
arrayBuffer.byteLength,
sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE
)
db.checkRc(rc)
try {
const schemaVersion = getSchemaVersion(db)
if (!isSupportedSchema(schemaVersion)) {
return
}
const studies = getStudies(db, schemaVersion)
setter((prev) => [...prev, ...studies])
} finally {
db.close()
}).then((sqlite3) => {
const p = sqlite3.wasm.allocFromTypedArray(arrayBuffer)
const db = new sqlite3.oo1.DB()
const rc = sqlite3.capi.sqlite3_deserialize(
// @ts-ignore
db.pointer,
"main",
p,
arrayBuffer.byteLength,
arrayBuffer.byteLength,
sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE
)
db.checkRc(rc)
return db
})
}
getStudies = async (): Promise<StudySummary[]> => {
const db = await this.db
this.summaries_cache = getStudySummaries(db)
return this.summaries_cache
}
getStudy = async (idx: number): Promise<Study | null> => {
const db = await this.db
const schemaVersion = getSchemaVersion(db)
if (!isSupportedSchema(schemaVersion)) {
return null
}
})
if (this.summaries_cache === null) {
this.summaries_cache = getStudySummaries(db)
}
const summary = this.summaries_cache[idx]
if (summary === undefined) {
return null
}
return getStudy(db, schemaVersion, summary)
}
}
const getSchemaVersion = (db: SQLite3DB): string => {
@@ -84,8 +96,8 @@ const isGreaterSchemaVersion = (
return left > right
}
const getStudies = (db: SQLite3DB, schemaVersion: string): Study[] => {
const studies: Study[] = []
const getStudySummaries = (db: SQLite3DB): StudySummary[] => {
const summaries: StudySummary[] = []
db.exec({
sql:
"SELECT s.study_id, s.study_name, sd.direction, sd.objective" +
@@ -99,64 +111,73 @@ const getStudies = (db: SQLite3DB, schemaVersion: string): Study[] => {
vals[2] === "MINIMIZE" ? "minimize" : "maximize"
const objective = vals[3]
const trials = getTrials(db, studyId, schemaVersion)
const union_search_space: SearchSpaceItem[] = []
const union_user_attrs: AttributeSpec[] = []
let intersection_search_space: Set<SearchSpaceItem> = new Set()
for (const trial of trials) {
const userAttrs = getTrialUserAttributes(db, trial.trial_id)
for (const attr of userAttrs) {
if (union_user_attrs.findIndex((s) => s.key === attr.key) === -1) {
union_user_attrs.push({ key: attr.key, sortable: false })
}
}
const params = getTrialParams(db, trial.trial_id)
const param_names = new Set<string>()
for (const param of params) {
param_names.add(param.name)
if (
union_search_space.findIndex((s) => s.name === param.name) === -1
) {
union_search_space.push({ name: param.name })
}
}
if (intersection_search_space.size === 0) {
// biome-ignore lint/complexity/noForEach: <explanation>
param_names.forEach((s) => {
intersection_search_space.add({
name: s,
})
})
} else {
intersection_search_space = new Set(
Array.from(intersection_search_space).filter((s) =>
param_names.has(s.name)
)
)
}
trial.params = params
trial.user_attrs = userAttrs
}
if (objective === 0) {
studies.push({
summaries.push({
study_id: studyId,
study_name: studyName,
directions: [direction],
union_search_space: union_search_space,
intersection_search_space: Array.from(intersection_search_space),
union_user_attrs: union_user_attrs,
trials: trials,
})
return
}
const index = studies.findIndex((s) => s.study_id === studyId)
studies[index].directions.push(direction)
const index = summaries.findIndex((s) => s.study_id === studyId)
summaries[index].directions.push(direction)
},
})
return studies
return summaries
}
const getStudy = (
db: SQLite3DB,
schemaVersion: string,
summary: StudySummary
): Study => {
const study: Study = {
study_id: summary.study_id,
study_name: summary.study_name,
directions: summary.directions,
union_search_space: [],
intersection_search_space: [],
union_user_attrs: [],
trials: [],
}
let intersection_search_space: Set<SearchSpaceItem> = new Set()
study.trials = getTrials(db, summary.study_id, schemaVersion)
for (const trial of study.trials) {
const userAttrs = getTrialUserAttributes(db, trial.trial_id)
for (const attr of userAttrs) {
if (study.union_user_attrs.findIndex((s) => s.key === attr.key) === -1) {
study.union_user_attrs.push({ key: attr.key, sortable: false })
}
}
const params = getTrialParams(db, trial.trial_id)
const param_names = new Set<string>()
for (const param of params) {
param_names.add(param.name)
if (
study.union_search_space.findIndex((s) => s.name === param.name) === -1
) {
study.union_search_space.push({ name: param.name })
}
}
if (intersection_search_space.size === 0) {
// biome-ignore lint/complexity/noForEach: <explanation>
param_names.forEach((s) => {
intersection_search_space.add({ name: s })
})
} else {
intersection_search_space = new Set(
Array.from(intersection_search_space).filter((s) =>
param_names.has(s.name)
)
)
}
trial.params = params
trial.user_attrs = userAttrs
}
study.intersection_search_space = Array.from(intersection_search_space)
return study
}
const getTrials = (
-6
View File
@@ -1,6 +0,0 @@
import { atom } from "recoil"
export const studiesState = atom<Study[]>({
key: "studies",
default: [],
})
+11
View File
@@ -4,6 +4,11 @@ type TrialState = "Running" | "Complete" | "Pruned" | "Fail" | "Waiting"
type TrialStateFinished = "Complete" | "Fail" | "Pruned"
type StudyDirection = "maximize" | "minimize" | "not_set"
type OptunaStorage = {
getStudies: () => Promise<StudySummary[]>
getStudy: (idx: number) => Promise<Study | null>
}
type FloatDistribution = {
type: "FloatDistribution"
low: number
@@ -46,6 +51,12 @@ type AttributeSpec = {
sortable: boolean
}
type StudySummary = {
study_id: number
study_name: string
directions: StudyDirection[]
}
type Study = {
study_id: number
study_name: string
+10 -25
View File
@@ -1,22 +1,15 @@
import React, { FC, useEffect } from "react"
import React, { FC, useEffect, useContext } from "react"
import ReactDOM from "react-dom/client"
import { RecoilRoot, SetterOrUpdater, useSetRecoilState } from "recoil"
import { App } from "./components/App"
import {
StorageContext,
StorageProvider,
getStorage,
} from "./components/StorageProvider"
import "./index.css"
import { loadJournalStorage } from "./journalStorage"
import { loadSQLite3Storage } from "./sqlite3"
import { studiesState } from "./state"
export const AppWrapper: FC = () => {
const setStudies = useSetRecoilState<Study[]>(studiesState)
// TODO(c-bata): Fix the type annotation
const onceSetStudies: SetterOrUpdater<Study[]> = (
setter: (currVal: Study[]) => Study[]
): void => {
const studies = setter([])
setStudies(studies)
}
const { setStorage } = useContext(StorageContext)
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
@@ -27,8 +20,6 @@ export const AppWrapper: FC = () => {
let len: number
let bytes: Uint8Array
let arrayBuffer: ArrayBuffer
let header: Uint8Array
let headerString: string
switch (message.type) {
case "optunaStorage":
@@ -40,13 +31,7 @@ export const AppWrapper: FC = () => {
bytes[i] = binaryString.charCodeAt(i)
}
arrayBuffer = bytes.buffer
header = new Uint8Array(arrayBuffer, 0, 16)
headerString = new TextDecoder().decode(header)
if (headerString === "SQLite format 3\u0000") {
loadSQLite3Storage(arrayBuffer, onceSetStudies)
} else {
loadJournalStorage(arrayBuffer, onceSetStudies)
}
setStorage(getStorage(arrayBuffer))
break
}
})
@@ -56,8 +41,8 @@ export const AppWrapper: FC = () => {
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<RecoilRoot>
<StorageProvider>
<AppWrapper />
</RecoilRoot>
</StorageProvider>
</React.StrictMode>
)