diff --git a/client/bun.lockb b/client/bun.lockb index 73c2ffc..bbbcc0f 100755 Binary files a/client/bun.lockb and b/client/bun.lockb differ diff --git a/client/package.json b/client/package.json index e6d5573..b84fe2d 100644 --- a/client/package.json +++ b/client/package.json @@ -17,29 +17,29 @@ "@types/bun": "latest", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.4.0", - "@typescript-eslint/parser": "^8.4.0", + "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/parser": "^8.5.0", "@vanilla-extract/css": "^1.15.5", "@vanilla-extract/vite-plugin": "^4.0.15", "@vitejs/plugin-react": "^4.3.1", "cspell": "^8.14.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-react": "^7.35.2", + "eslint-plugin-react": "^7.36.1", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.11", "prettier": "^3.3.3", - "typescript": "^5.5.4", - "vite": "^5.4.3", + "typescript": "^5.6.2", + "vite": "^5.4.5", "vite-plugin-minify": "^2.0.0" }, "dependencies": { "@icons-pack/react-simple-icons": "^10.0.0", - "lucide-react": "^0.439.0", + "lucide-react": "^0.441.0", "octokit": "^4.0.2", "react": "^18.3.1", "react-dom": "^18.3.1", - "tinybase": "^5.3.0-beta.2", - "tinywidgets": "^0.0.6" + "tinybase": "^5.3.0", + "tinywidgets": "^0.0.11" } } \ No newline at end of file diff --git a/client/public/favicon.svg b/client/public/favicon.svg index b721e38..e6c84f5 100644 --- a/client/public/favicon.svg +++ b/client/public/favicon.svg @@ -1,5 +1,5 @@ - + = Exclude; const STORE_ID = 'issues'; const TABLE_ID = 'issues'; +const PERSISTER_ID = 'issues'; + const TABLES_SCHEMA = { issues: { title: {type: 'string', default: ''}, @@ -28,8 +30,10 @@ const { useProvideStore, useCreatePersister, useCell, + useProvidePersister, useSetCellCallback, useSortedRowIds, + usePersisterStatus, } = UiReact as UiReact.WithSchemas; type TableIds = keyof typeof TABLES_SCHEMA; type CellIds = AsId< @@ -58,12 +62,16 @@ export const useIssuesSortedRowIds = ( ) => useSortedRowIds(TABLE_ID, cellId, descending, undefined, undefined, STORE_ID); +export const useIssuesPersisterStatus = () => usePersisterStatus(PERSISTER_ID); + export const IssuesStore = () => { const currentRepoId = useUiValue('repoId'); const issuesStore = useCreateStore( () => createStore().setTablesSchema(TABLES_SCHEMA), [currentRepoId], ); + useProvideStore(STORE_ID, issuesStore); + useCreatePersister( issuesStore, (issuesStore) => { @@ -82,7 +90,7 @@ export const IssuesStore = () => { [], ); - useCreatePersister( + const issuesPersister = useCreatePersister( issuesStore, (issuesStore) => { if (currentRepoId) { @@ -95,8 +103,8 @@ export const IssuesStore = () => { }, [], ); + useProvidePersister(PERSISTER_ID, issuesPersister); - useProvideStore(STORE_ID, issuesStore); return null; }; diff --git a/client/src/stores/RepoStore.tsx b/client/src/stores/RepoStore.tsx index 66fe27c..cbafce0 100644 --- a/client/src/stores/RepoStore.tsx +++ b/client/src/stores/RepoStore.tsx @@ -30,6 +30,8 @@ type RepoData = { const STORE_ID = 'repo'; +const PERSISTER_ID = 'repo'; + const VALUES_SCHEMA = { id: {type: 'string', default: ''}, owner: {type: 'string', default: ''}, @@ -52,19 +54,29 @@ const VALUES_SCHEMA = { visibility: {type: 'string', default: ''}, } as const; type Schemas = [NoTablesSchema, typeof VALUES_SCHEMA]; -const {useCreateStore, useProvideStore, useCreatePersister, useValue} = - UiReact as UiReact.WithSchemas; +const { + useCreateStore, + useProvideStore, + useCreatePersister, + usePersisterStatus, + useValue, + useProvidePersister, +} = UiReact as UiReact.WithSchemas; type ValueIds = keyof typeof VALUES_SCHEMA; export const useRepoValue = (valueId: ValueId) => useValue(valueId, STORE_ID); +export const useRepoPersisterStatus = () => usePersisterStatus(PERSISTER_ID); + export const RepoStore = () => { const currentRepoId = useUiValue('repoId'); const repoStore = useCreateStore( () => createStore().setValuesSchema(VALUES_SCHEMA), [currentRepoId], ); + useProvideStore(STORE_ID, repoStore); + useCreatePersister( repoStore, (repoStore) => { @@ -80,7 +92,7 @@ export const RepoStore = () => { [], ); - useCreatePersister( + const repoPersister = useCreatePersister( repoStore, (repoStore) => { if (repoStore) { @@ -88,11 +100,13 @@ export const RepoStore = () => { } }, [currentRepoId], - async (persister) => await persister?.load(), + async (persister) => { + await persister?.load(); + }, [], ); + useProvidePersister(PERSISTER_ID, repoPersister); - useProvideStore(STORE_ID, repoStore); return null; }; diff --git a/client/src/stores/ReposStore.tsx b/client/src/stores/ReposStore.tsx index 294c63c..2a4e4bc 100644 --- a/client/src/stores/ReposStore.tsx +++ b/client/src/stores/ReposStore.tsx @@ -33,6 +33,8 @@ const TABLE_ID = 'repos'; const INDEXES_ID = 'repos'; const INDEX_ID = 'reposByGroup'; +const PERSISTER_ID = 'repos'; + const TABLES_SCHEMA = { repos: { group: {type: 'string', default: ''}, @@ -53,8 +55,10 @@ const { useCreateStore, useProvideStore, useCreateIndexes, + useProvidePersister, useProvideIndexes, useSliceRowIds, + usePersisterStatus, useSliceIds, } = UiReact as UiReact.WithSchemas; type TableIds = keyof typeof TABLES_SCHEMA; @@ -72,10 +76,13 @@ export const useGroupIds = () => useSliceIds(INDEX_ID, INDEXES_ID); export const useGroupRepoIds = (group: string) => useSliceRowIds(INDEX_ID, group, INDEXES_ID); +export const useReposPersisterStatus = () => usePersisterStatus(PERSISTER_ID); + export const ReposStore = () => { const reposStore = useCreateStore(() => createStore().setTablesSchema(TABLES_SCHEMA), ); + useProvideStore(STORE_ID, reposStore); const reposSortCell = useSettingsValue('reposSortCell') as CellIds< typeof TABLE_ID @@ -95,6 +102,7 @@ export const ReposStore = () => { ), [reposSortCell], ); + useProvideIndexes(INDEXES_ID, reposIndexes!); useCreatePersister( reposStore, @@ -106,7 +114,7 @@ export const ReposStore = () => { }, ); - useCreatePersister( + const reposPersister = useCreatePersister( reposStore, (reposStore) => { return createGithubReposLoadingPersister(reposStore); @@ -117,9 +125,8 @@ export const ReposStore = () => { }, [], ); + useProvidePersister(PERSISTER_ID, reposPersister); - useProvideStore(STORE_ID, reposStore); - useProvideIndexes(INDEXES_ID, reposIndexes!); return null; }; diff --git a/client/src/ui/Repo/Issues/IssueList/index.tsx b/client/src/ui/Repo/Issues/IssueList/index.tsx index 750777c..b6848b3 100644 --- a/client/src/ui/Repo/Issues/IssueList/index.tsx +++ b/client/src/ui/Repo/Issues/IssueList/index.tsx @@ -2,21 +2,14 @@ import {IssueLink} from './IssueLink'; import {createElement} from '../../../../common'; -import {useIssuesSortedRowIds} from '../../../../stores/IssuesStore'; -import {useSettingsValue} from '../../../../stores/SettingsStore'; export const IssueList = ({ currentIssueId, + issueIds, }: { readonly currentIssueId: string; + readonly issueIds: string[]; }) => { - const issuesSortCell = useSettingsValue('issuesSortCell'); - const issuesSortAscending = issuesSortCell == 'title'; - const issueIds = useIssuesSortedRowIds( - issuesSortCell as any, - !issuesSortAscending, - ); - return issueIds.map((issueId) => ( { const repoId = useUiValue('repoId'); const issueId = useUiValue('issueId'); + + const issuesSortCell = useSettingsValue('issuesSortCell'); + const issuesSortAscending = issuesSortCell == 'title'; + const issueIds = useIssuesSortedRowIds( + issuesSortCell as any, + !issuesSortAscending, + ); + + const setIssueId = useSetUiValueCallback( + 'issueId', + (issueId: string) => issueId, + ); + useEffect(() => { + if (issueIds.length > 0 && (issueId == '' || !issueIds.includes(issueId))) { + setIssueId(issueIds[0]); + } + }, [issueIds, issueId, setIssueId]); + return ( { } > - + {useIssueCell(issueId, 'title') ? ( diff --git a/client/src/ui/Repo/RepoHeader/index.css.ts b/client/src/ui/Repo/RepoHeader/index.css.ts index 79faa41..ad98210 100644 --- a/client/src/ui/Repo/RepoHeader/index.css.ts +++ b/client/src/ui/Repo/RepoHeader/index.css.ts @@ -1,4 +1,4 @@ -import {large} from 'tinywidgets/media'; +import {large} from 'tinywidgets/utils'; import {style} from '@vanilla-extract/css'; export const repoHeader = style([ diff --git a/client/src/ui/Title/index.css.ts b/client/src/ui/Title/index.css.ts index bb76ecf..db2f853 100644 --- a/client/src/ui/Title/index.css.ts +++ b/client/src/ui/Title/index.css.ts @@ -1,9 +1,16 @@ -import {small} from 'tinywidgets/media'; +import {small} from 'tinywidgets/utils'; import {style} from '@vanilla-extract/css'; export const logo = style({ width: '2rem', height: '2rem', + transition: 'transform 1s', + transform: 'rotate(0deg)', +}); + +export const spinning = style({ + transition: 'transform 3s', + transform: 'rotate(360deg)', }); export const button = style({ diff --git a/client/src/ui/Title/index.tsx b/client/src/ui/Title/index.tsx index 6d653ff..d9b627a 100644 --- a/client/src/ui/Title/index.tsx +++ b/client/src/ui/Title/index.tsx @@ -2,14 +2,27 @@ /** @jsxFrag Fragment */ import {Fragment, createElement} from '../../common.ts'; -import {button, logo} from './index.css.ts'; +import {button, logo, spinning} from './index.css.ts'; import {Button} from 'tinywidgets'; import {CircleHelp} from 'lucide-react'; +import {classNames} from 'tinywidgets/utils'; +import {useIssuesPersisterStatus} from '../../stores/IssuesStore.tsx'; +import {useRepoPersisterStatus} from '../../stores/RepoStore.tsx'; +import {useReposPersisterStatus} from '../../stores/ReposStore.tsx'; export const Title = () => { + const persisterStatus = + useReposPersisterStatus() + + useRepoPersisterStatus() + + useIssuesPersisterStatus(); + return ( <> - TinyHub logo + TinyHub logo 0 && spinning)} + />

TinyHub