diff --git a/app.go b/app.go
index ec62a6a..fa020c7 100644
--- a/app.go
+++ b/app.go
@@ -2,7 +2,8 @@ package main
import (
"context"
- "encoding/json"
+ "fmt"
+ "path/filepath"
)
// App struct
@@ -21,22 +22,49 @@ func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
-func JsonResponse(data interface{}, err error) string {
- if err != nil {
- res, _ := json.Marshal(map[string]interface{}{"error": err.Error()})
- return string(res)
- }
- res, _ := json.Marshal(data)
- return string(res)
+func WrapError(err error) SteamLibraryMeta {
+ return SteamLibraryMeta{Error: err.Error()}
}
-func (a *App) GetScreenshotsDirs() string {
+type SteamLibraryMeta struct {
+ Error string `json:"error,omitempty"`
+ SteamDir string `json:"steamDir"`
+ UserDir string `json:"userDir"`
+ GameDirs []string `json:"gameDirs"`
+ ScreenshotsDirs []string `json:"screenshotsDirs"`
+ SyncDir string `json:"syncDir"`
+}
+
+func (a *App) GetSteamLibraryMeta() SteamLibraryMeta {
p, err := GetSteamDirectory()
if err != nil {
- return JsonResponse(nil, err)
+ return WrapError(err)
+ }
+ userDir, err := GetSteamUserDirectory()
+ if err != nil {
+ return WrapError(err)
+ }
+ fmt.Printf("User Dir: %s\n", userDir)
+ userId := filepath.Base(userDir)
+ gd, err := GetGameDirectories(userId)
+ if err != nil {
+ return WrapError(err)
+ }
+ syncDir, err := GetSyncDirectory()
+ if err != nil {
+ return WrapError(err)
+ }
+ screenshotsDirs, err := GetScreenshotsDirs()
+ if err != nil {
+ return WrapError(err)
+ }
+ out := SteamLibraryMeta{
+ SteamDir: p,
+ GameDirs: gd,
+ UserDir: userDir,
+ SyncDir: syncDir,
+ ScreenshotsDirs: screenshotsDirs,
}
- out := make(map[string]interface{})
- out["steamDir"] = p
- return JsonResponse(out, nil)
+ return out
}
diff --git a/dirs.go b/dirs.go
index ca29236..d562b7b 100644
--- a/dirs.go
+++ b/dirs.go
@@ -4,6 +4,8 @@ import (
"fmt"
"os"
"runtime"
+ "slices"
+ "strconv"
)
const (
@@ -42,24 +44,30 @@ func GetSteamDirectory() (string, error) {
return fmt.Sprintf(format, homedir), nil
}
-func GetSteamUserdataDirectory() (string, error) {
- steamDir, err := GetSteamDirectory()
+func GetSteamUserDirectory() (string, error) {
+ userDirs, err := GetUsersDirectories()
if err != nil {
return "", err
}
- return fmt.Sprintf("%s/userdata", steamDir), nil
+ if len(userDirs) == 0 {
+ return "", fmt.Errorf("Could not find any Steam users")
+ }
+ // TODO multiple users
+ return userDirs[0], nil
}
func GetSyncDirectory() (string, error) {
- dataDir, err := GetSteamUserdataDirectory()
+ userDir, err := GetSteamUserDirectory()
if err != nil {
return "", err
}
- return fmt.Sprintf("%s/760", dataDir), nil
+ return fmt.Sprintf("%s/760", userDir), nil
}
+var STEAM_INTERNAL_IDS = []string{"7", "760"}
+
// screenshots: /Users/chen/Library/Application\ Support/Steam/userdata/37889173/760/remote/1086940/screenshots
-func GetGameDirectories() ([]string, error) {
+func GetUsersDirectories() ([]string, error) {
steamDir, err := GetSteamDirectory()
if err != nil {
return nil, err
@@ -69,13 +77,76 @@ func GetGameDirectories() ([]string, error) {
if err != nil {
return nil, err
}
- var gameDirs []string
+ var userDirs []string
for _, entry := range entries {
if !entry.IsDir() {
continue
}
gameDir := fmt.Sprintf("%s/%s", steamUserData, entry.Name())
+ userDirs = append(userDirs, gameDir)
+ }
+ return userDirs, nil
+}
+
+func GetUserDirectory(userId string) (string, error) {
+ steamDir, err := GetSteamDirectory()
+ if err != nil {
+ return "", err
+ }
+ fmt.Printf("Get User Dir: %s/userdata/%s\n", steamDir, userId)
+ return fmt.Sprintf("%s/userdata/%s", steamDir, userId), nil
+}
+
+func GetGameDirectories(userId string) ([]string, error) {
+ userDir, err := GetUserDirectory(userId)
+ if err != nil {
+ return nil, err
+ }
+
+ entries, err := os.ReadDir(userDir)
+ if err != nil {
+ return nil, err
+ }
+
+ gameDirs := []string{}
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ continue
+ }
+ if slices.Contains(STEAM_INTERNAL_IDS, entry.Name()) {
+ continue
+ }
+ if _, err := strconv.Atoi(entry.Name()); err != nil {
+ continue
+ }
+ gameDir := fmt.Sprintf("%s/%s", userDir, entry.Name())
gameDirs = append(gameDirs, gameDir)
}
return gameDirs, nil
}
+
+func GetScreenshotsDirs() ([]string, error) {
+ syncDir, err := GetSyncDirectory()
+ if err != nil {
+ return nil, err
+ }
+ var dirs []string
+ remoteDir := fmt.Sprintf("%s/remote", syncDir)
+ entries, err := os.ReadDir(remoteDir)
+ if err != nil {
+ return nil, err
+ }
+ for _, entry := range entries {
+ fmt.Printf("Entry: %s\n", entry.Name())
+ if !entry.IsDir() {
+ continue
+ }
+ scrDir := fmt.Sprintf("%s/%s/screenshots", remoteDir, entry.Name())
+ fmt.Printf("Checking: %s\n", scrDir)
+ if _, err := os.Stat(scrDir); os.IsNotExist(err) {
+ continue
+ }
+ dirs = append(dirs, scrDir)
+ }
+ return dirs, nil
+}
diff --git a/frontend/index.html b/frontend/index.html
index db87bd0..18e7edb 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,13 +1,12 @@
-
+
-
-
-
+
+
+
stimvisor
-
-
-
-
-
+
+
+
+
+
-
diff --git a/frontend/package.json b/frontend/package.json
index ed67eb8..dc5ea3a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,8 +11,10 @@
"dependencies": {
"@fontsource-variable/nunito": "^5.1.0",
"@tanstack/react-query": "^5.59.15",
+ "clsx": "^2.1.1",
"react": "^18.3.1",
- "react-dom": "^18.3.1"
+ "react-dom": "^18.3.1",
+ "tailwind-merge": "^2.5.4"
},
"devDependencies": {
"@eslint/js": "^9.12.0",
diff --git a/frontend/package.json.md5 b/frontend/package.json.md5
index 619d3e1..886a7d6 100755
--- a/frontend/package.json.md5
+++ b/frontend/package.json.md5
@@ -1 +1 @@
-1e8656145625684f371b8ed4aff2036d
\ No newline at end of file
+a2816a52857b069ddfc7f6d2622f2c9d
\ No newline at end of file
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index d5bf0f7..1ff6d8b 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -14,12 +14,18 @@ importers:
'@tanstack/react-query':
specifier: ^5.59.15
version: 5.59.15(react@18.3.1)
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
react:
specifier: ^18.3.1
version: 18.3.1
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
+ tailwind-merge:
+ specifier: ^2.5.4
+ version: 2.5.4
devDependencies:
'@eslint/js':
specifier: ^9.12.0
@@ -725,6 +731,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@@ -1313,6 +1323,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ tailwind-merge@2.5.4:
+ resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==}
+
tailwindcss@3.4.14:
resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==}
engines: {node: '>=14.0.0'}
@@ -2057,6 +2070,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ clsx@2.1.1: {}
+
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
@@ -2618,6 +2633,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ tailwind-merge@2.5.4: {}
+
tailwindcss@3.4.14:
dependencies:
'@alloc/quick-lru': 5.2.0
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ce29eaa..f539b4b 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -5,11 +5,12 @@ import { useState } from 'react'
function App() {
const [queryClient] = useState(() => new QueryClient())
return (
-
-
-
-
-
+
+
+
)
}
diff --git a/frontend/src/assets/fonts/nunito-cyrillic-ext-wght-italic.woff2 b/frontend/src/assets/fonts/nunito-cyrillic-ext-wght-italic.woff2
new file mode 100644
index 0000000..eb82591
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-cyrillic-ext-wght-italic.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-cyrillic-ext-wght-normal.woff2 b/frontend/src/assets/fonts/nunito-cyrillic-ext-wght-normal.woff2
new file mode 100644
index 0000000..4e2a182
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-cyrillic-ext-wght-normal.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-cyrillic-wght-italic.woff2 b/frontend/src/assets/fonts/nunito-cyrillic-wght-italic.woff2
new file mode 100644
index 0000000..f4ca04d
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-cyrillic-wght-italic.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-cyrillic-wght-normal.woff2 b/frontend/src/assets/fonts/nunito-cyrillic-wght-normal.woff2
new file mode 100644
index 0000000..9807404
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-cyrillic-wght-normal.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-latin-ext-wght-italic.woff2 b/frontend/src/assets/fonts/nunito-latin-ext-wght-italic.woff2
new file mode 100644
index 0000000..7c388c8
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-latin-ext-wght-italic.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-latin-ext-wght-normal.woff2 b/frontend/src/assets/fonts/nunito-latin-ext-wght-normal.woff2
new file mode 100644
index 0000000..28fc6f2
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-latin-ext-wght-normal.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-latin-wght-italic.woff2 b/frontend/src/assets/fonts/nunito-latin-wght-italic.woff2
new file mode 100644
index 0000000..f730c64
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-latin-wght-italic.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-latin-wght-normal.woff2 b/frontend/src/assets/fonts/nunito-latin-wght-normal.woff2
new file mode 100644
index 0000000..042b9ab
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-latin-wght-normal.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
deleted file mode 100644
index 2f9cc59..0000000
Binary files a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ
diff --git a/frontend/src/assets/fonts/nunito-vietnamese-wght-italic.woff2 b/frontend/src/assets/fonts/nunito-vietnamese-wght-italic.woff2
new file mode 100644
index 0000000..ad1e41f
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-vietnamese-wght-italic.woff2 differ
diff --git a/frontend/src/assets/fonts/nunito-vietnamese-wght-normal.woff2 b/frontend/src/assets/fonts/nunito-vietnamese-wght-normal.woff2
new file mode 100644
index 0000000..af0abf2
Binary files /dev/null and b/frontend/src/assets/fonts/nunito-vietnamese-wght-normal.woff2 differ
diff --git a/frontend/src/common/api.ts b/frontend/src/common/api.ts
index 1bed33b..c19d258 100644
--- a/frontend/src/common/api.ts
+++ b/frontend/src/common/api.ts
@@ -7,17 +7,30 @@ type FullfilledUseQueryResult = Omit<
data: Exclude
}
+type WrappedUseQueryOptions = UseQueryOptions<
+ TQueryFnData,
+ TError,
+ TData
+> & { debug?: boolean }
+
export function useApi(
- promise: () => Promise,
- options: Partial> = {},
+ promise: () => Promise,
+ options: Partial> = {},
): FullfilledUseQueryResult {
const query = useQuery({
- queryFn: () =>
- promise()
- .then((result) => JSON.parse(result as string))
- .then((json) => (json.error ? Promise.reject(json.error) : Promise.resolve(json))),
+ queryFn: async () => {
+ if (options.debug) console.debug('useApi', promise)
+ const json = await promise()
+ if (options.debug) console.debug('useApi response:', json)
+ return await (isError(json) ? Promise.reject(json.error) : Promise.resolve(json))
+ },
queryKey: [],
...options,
})
return query as FullfilledUseQueryResult
}
+
+function isError(json: unknown): json is { error: unknown } {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return Boolean((json as any).error)
+}
diff --git a/frontend/src/common/types.ts b/frontend/src/common/types.ts
new file mode 100644
index 0000000..2d8bb47
--- /dev/null
+++ b/frontend/src/common/types.ts
@@ -0,0 +1 @@
+export type HtmlProps = JSX.IntrinsicElements[T]
diff --git a/frontend/src/common/utils.ts b/frontend/src/common/utils.ts
new file mode 100644
index 0000000..e25f6b2
--- /dev/null
+++ b/frontend/src/common/utils.ts
@@ -0,0 +1,5 @@
+import { twMerge } from 'tailwind-merge'
+import clsx, { ClassValue } from 'clsx'
+export function cn(...classes: ClassValue[]): string {
+ return twMerge(clsx(...classes))
+}
diff --git a/frontend/src/components/Sidebar/Sidebar.tsx b/frontend/src/components/Sidebar/Sidebar.tsx
index 9235d4e..36b5982 100644
--- a/frontend/src/components/Sidebar/Sidebar.tsx
+++ b/frontend/src/components/Sidebar/Sidebar.tsx
@@ -1,34 +1,38 @@
import { useApi } from '../../common/api'
-import { GetScreenshotsDirs } from '../../../wailsjs/go/main/App'
+import { GetSteamLibraryMeta } from '../../../wailsjs/go/main/App'
+import { HtmlProps } from '../../common/types'
+import React from 'react'
+import { cn } from '../../common/utils'
-export function Sidebar() {
- const { data } = useApi<{ steamDir: string }>(() => GetScreenshotsDirs(), { initialData: {} })
+export function Sidebar({ className, ...rest }: HtmlProps<'div'>) {
+ const { data } = useApi(() => GetSteamLibraryMeta(), { initialData: {} as never, debug: true })
const { steamDir } = data
return (
-
-
- Games
-
- Screenshots
-
- {[steamDir].map((item, i) => (
-
- {item}
-
- ))}
-
-
- Save Data
+
+
+
+ {JSON.stringify(data, null, 2)}
+
)
}
-function ListItem({ children }: React.PropsWithChildren