Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
64ab8f6
init
kotru21 Dec 17, 2025
1db5f79
Enhance settings, layout, and file display customization
kotru21 Dec 17, 2025
595ecd8
Add layout sync, theme, and performance improvements
kotru21 Dec 18, 2025
8ae957b
Add confirm dialog feature and improve keyboard handling
kotru21 Dec 18, 2025
c7bb8b3
Refactor settings and file explorer behaviors
kotru21 Dec 18, 2025
dbd254f
Update dependencies to latest versions
kotru21 Dec 18, 2025
de37077
Refactor resizable panel components and update usage
kotru21 Dec 18, 2025
f74c316
Revert "Refactor resizable panel components and update usage"
kotru21 Dec 18, 2025
479b7ec
Revert "Update dependencies to latest versions"
kotru21 Dec 18, 2025
f4ffe6a
Add column header toggle for simple list and improve layout sync
kotru21 Dec 19, 2025
92a9136
Refactor state selectors, add CI, and improve a11y tests
kotru21 Dec 19, 2025
1c0b6b8
refactor(layout): reduce getState usage in initLayoutSync; subscribe …
kotru21 Dec 19, 2025
67c5197
refactor(file-explorer): replace getState() uses with selectors; add …
kotru21 Dec 19, 2025
88ced6f
refactor(toolbar|command-palette): use store selectors instead of get…
kotru21 Dec 19, 2025
44651d5
refactor(view-mode): avoid useSettingsStore.getState() inside store; …
kotru21 Dec 19, 2025
9adc807
refactor(layout): minimize module-level getState reads; defer debounc…
kotru21 Dec 19, 2025
840e508
refactor(toast): implement convenience helpers without calling useToa…
kotru21 Dec 19, 2025
49b98d8
refactor(layout): use setState for applying layout updates; update te…
kotru21 Dec 19, 2025
55d68ee
Refactor store usage to use selector hooks
kotru21 Dec 19, 2025
718cee5
Refactor file explorer handler tests and improve state usage
kotru21 Dec 19, 2025
32e41bf
chore(lint): enforce FSD import boundaries via Biome noRestrictedImports
kotru21 Dec 20, 2025
4508771
Refactor TabBar controls and update file explorer props
kotru21 Dec 20, 2025
3b43466
Refactor FileExplorer UI into modular components
kotru21 Dec 20, 2025
9c2b764
refactor(file-explorer): split view into subcomponents and add tests …
kotru21 Dec 20, 2025
d831a80
chore(tauri): add typed client wrapper and tests; centralize unwrapRe…
kotru21 Dec 20, 2025
64e0c2a
refactor(tauri): migrate callers from commands.* to tauriClient.* whe…
kotru21 Dec 20, 2025
98ed15c
refactor(tauri): migrate remaining direct commands to tauriClient; mo…
kotru21 Dec 20, 2025
3bfec4e
fix(tauri): remove explicit any casts, type search options in client
kotru21 Dec 20, 2025
6a0ca6f
Refactor imports and improve test type definitions
kotru21 Dec 20, 2025
833b022
feat(perf): add withPerf helper and apply to readDirectory; add tests
kotru21 Dec 20, 2025
8b8f044
feat(perf): add withPerfSync/markPerf; apply to FileExplorer, Virtual…
kotru21 Dec 20, 2025
3d54324
Add env-based toggle for performance logging
kotru21 Dec 20, 2025
96f6968
Improve file row and popover UI, add Playwright tests
kotru21 Dec 20, 2025
a0b9a86
Persist sidebar section state and clean up comments
kotru21 Dec 20, 2025
379fa36
Add Tauri thumbnail generation and LQIP support
kotru21 Dec 20, 2025
abc4749
sort fix
kotru21 Dec 20, 2025
e44c833
Refactor dev logging to use utility functions
kotru21 Dec 21, 2025
b1b7573
Refactor file explorer logic and add preview panel hooks
kotru21 Dec 21, 2025
04a05d4
Add tests and improve settings for file display and performance
kotru21 Dec 22, 2025
01b632f
Update preview.rs
kotru21 Dec 22, 2025
d008a3b
Improve slider selection in performance settings test
kotru21 Dec 22, 2025
0f44bf5
Update ci.yml
kotru21 Dec 22, 2025
3ad9fb9
Update perf.test.ts
kotru21 Dec 22, 2025
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
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
build:
runs-on: ubuntu-latest
env:
VITE_USE_PERF_LOGS: 'false'
USE_PERF_LOGS: 'false'
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Check import cycles (madge)
run: npm run check:imports

- name: Run lint (biome)
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Run tests and coverage
run: npm run test:coverage

- name: Upload coverage
uses: actions/upload-artifact@v6
with:
name: coverage-report
path: coverage
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dist
dist-ssr
*.local
coverage/
test-results/

# Editor directories and files
.vscode/*
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
![React](https://img.shields.io/badge/React-19.2-61dafb)
![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178c6)

[![CI](https://github.com/<kotru21>/<FileManagerTauri>/actions/workflows/ci.yml/badge.svg)](https://github.com/<kotru21>/<FileManagerTauri>/actions)

## Возможности

- 📁 Навигация по файловой системе с историей (назад/вперёд)
Expand Down
56 changes: 55 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"noStaticElementInteractions": "off",
"useKeyWithClickEvents": "off",
"useSemanticElements": "off"
}
},
"style": {}
}
},
"overrides": [
Expand All @@ -56,6 +57,59 @@
"afterAll"
]
}
},
{
"includes": ["**/src/entities/**"],
"linter": {
"rules": {
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"patterns": [
{
"group": [
"@/features/*",
"src/features/*",
"./src/features/*",
"*src/features/*"
],
"message": "Importing from higher FSD layers into 'entities' is forbidden. Move logic to features or pass values via props/shared adapter."
},
{
"group": ["@/widgets/*", "src/widgets/*", "./src/widgets/*", "*src/widgets/*"],
"message": "Importing from widgets into entities is forbidden."
},
{
"group": ["@/pages/*", "src/pages/*", "./src/pages/*", "*src/pages/*"],
"message": "Importing from pages into entities is forbidden."
}
]
}
}
}
}
}
},
{
"includes": ["**/src/features/**"],
"linter": {
"rules": {
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"patterns": [
{
"group": ["@/widgets/*", "src/widgets/*", "./src/widgets/*", "*src/widgets/*"],
"message": "Importing from widgets into features is forbidden. Use shared adapters or move logic."
}
]
}
}
}
}
}
}
],
"javascript": {
Expand Down
23 changes: 23 additions & 0 deletions e2e/file-row-hover.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Page } from "@playwright/test"
import { expect, test } from "@playwright/test"

// NOTE: Use DEV_SERVER_URL or default; requires dev server running

test.describe("FileRow hover & cursor", () => {
test("hover shows actions and cursor is pointer", async ({ page }: { page: Page }) => {
const base = process.env.DEV_SERVER_URL ?? "http://localhost:5173"
await page.goto(base)

const row = page.locator('[data-testid^="file-row-"]').first()
await expect(row).toBeVisible()

await row.hover()
const cursor = await row.evaluate((el: HTMLElement) => getComputedStyle(el).cursor)
expect(cursor).toBe("pointer")

const actions = row.locator(".mr-2").first()
await expect(actions).toBeVisible()
const actionOpacity = await actions.evaluate((el: HTMLElement) => getComputedStyle(el).opacity)
expect(Number(actionOpacity)).toBeGreaterThan(0)
})
})
44 changes: 44 additions & 0 deletions e2e/recent-folders-hover.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Page } from "@playwright/test"
import { expect, test } from "@playwright/test"

// NOTE: Use DEV_SERVER_URL or default; ensure dev server and recent folders exist

test.describe("RecentFolders hover & cursor", () => {
test("hover shows remove button and cursor is pointer", async ({ page }: { page: Page }) => {
const base = process.env.DEV_SERVER_URL ?? "http://localhost:5173"

// Hydrate recent-folders store for deterministic test data
await page.addInitScript(() => {
try {
const key = "recent-folders"
const payload = {
state: { folders: [{ path: "/one", name: "One", lastVisited: Date.now() }] },
}
localStorage.setItem(key, JSON.stringify(payload))
} catch {
/* ignore */
}
})

await page.goto(base)

await page.waitForSelector("text=Недавние", { state: "visible" })

const folder = page.locator('[aria-label^="Open "]').first()
await expect(folder).toBeVisible()

await folder.hover()
const cursor = await folder.evaluate((el: HTMLElement) => getComputedStyle(el).cursor)
expect(cursor).toBe("pointer")

const aria = await folder.getAttribute("aria-label")
const name = aria?.replace(/^Open\s*/, "") ?? ""
const removeBtn = page.locator(`[aria-label="Remove ${name}"]`).first()
if ((await removeBtn.count()) > 0) {
const opacity = await removeBtn.evaluate((el: HTMLElement) => getComputedStyle(el).opacity)
expect(Number(opacity)).toBeGreaterThan(0)
} else {
test.skip(true, "No remove button found for the folder (no recent items?)")
}
})
})
50 changes: 50 additions & 0 deletions e2e/sidebar-persistence.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect, test } from "@playwright/test"

// NOTE: Use DEV_SERVER_URL or default; ensure dev server and recent folders exist

test("Sidebar sections persist collapsed state across reload", async ({ page }) => {
const base = process.env.DEV_SERVER_URL ?? "http://localhost:5173"

// Hydrate recent-folders store for deterministic test data
await page.addInitScript(() => {
try {
const key = "recent-folders"
const payload = {
state: { folders: [{ path: "/one", name: "One", lastVisited: Date.now() }] },
}
localStorage.setItem(key, JSON.stringify(payload))
} catch {
/* ignore */
}
})

await page.goto(base)

await page.waitForSelector("text=Недавние", { state: "visible" })

const folderBtn = page.locator('[aria-label^="Open "]').first()
if ((await folderBtn.count()) === 0) {
test.skip(true, "No recent items to exercise persistence")
return
}

await page.click("text=Недавние")

const raw = await page.evaluate(() => localStorage.getItem("layout-storage"))
expect(raw).not.toBeNull()
expect(raw).toContain('"expandedSections"')
expect(raw).toContain('"recent":false')
const parsed = JSON.parse(raw || "{}")
expect(parsed?.layout?.expandedSections?.recent).toBe(false)

await page.reload()
await page.waitForSelector("text=Недавние", { state: "visible" })

const raw2 = await page.evaluate(() => localStorage.getItem("layout-storage"))
if (raw2?.includes('"recent":false')) {
await page.waitForTimeout(100)
expect(await page.locator('[aria-label^="Open "]').count()).toBe(0)
} else {
throw new Error(`Persisted layout not found after reload: ${String(raw2)}`)
}
})
Loading