diff --git a/.gitignore b/.gitignore index dfac92c..48c2aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,8 @@ database.db-* /packages/*/dist /packages/*/**/node_modules /apps/*/**/node_modules -/supabase \ No newline at end of file +/supabase +/tests/load/.tmp +/tests/chaos/.tmp +/tests/load/config.test.toml.local +/hls/* \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..76ce0f3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# CLAUDE.md + +Guidance for Claude Code working in the Vessel repository. + +## Project + +Vessel is C2 (Command & Control) software for connecting, monitoring, and orchestrating physical sensors through a visual flow-based interface. Local-first, offline-first. Apache-2.0. + +## Repository layout + +Cargo + npm workspaces (monorepo). + +- `apps/server` — Rust (axum, tokio) backend. SQLite via Diesel (`migrations/`, schema in `src/db/schema.rs`). MQTT broker (rumqttd) + client (rumqttc), WebRTC, RTP/RTSP via GStreamer, ONNX inference (ort/tract). Entry: `src/main.rs`. Routes wired in `src/routes.rs`, handlers under `src/handler/`. Flow engine in `src/flow/` (engine, manager, nodes). Built-in flow nodes live in `src/flow/nodes/`. The compiled server embeds the client `dist/` via `rust-embed`. +- `apps/client` — React 19 + Vite + TypeScript + Tailwind v4 + Radix + Zustand. FSD-ish layout: `app/`, `pages/`, `widgets/`, `features/`, `entities/`, `shared/`, plus `components/ui` (shadcn) and `hooks/`, `contexts/`, `lib/`. Routing in `src/App.tsx` (react-router v7). +- `apps/desktop` — Tauri v2 shell. The Tauri build runs `cargo build --release -p server` and bundles the server binary as a sidecar; frontend served from `apps/client/dist`. Crate at `apps/desktop/src-tauri`. +- `apps/landing` — Marketing site (React + Vite). Deployed at vessel.cartesiancs.com. +- `apps/capsule` — Standalone Rust service for "Capsule" zero-knowledge LLM proxy (X25519 + ChaCha20-Poly1305). Has its own Dockerfile/docker-compose. +- `packages/capsule-client` — TS client SDK for Capsule (`@vessel/capsule-client`). Built before `client` in the build chain. +- `packages/custom-node-utils` — Python helpers for user-authored custom flow nodes. +- `packages/shared-types` — empty placeholder. +- `docs/` — VitePress site (vessel.cartesiancs.com/docs). +- `tests/` — Jest + supertest E2E against a running server at `http://localhost:6174`. Default creds `admin/admin1`. +- `configs/`, `config.toml`, `.env.example` — server config (jwt_secret, listen_address, database_url). +- `migrations/` lives under `apps/server/`. `database.db` at repo root is the dev DB. + +## Common commands + +Run from repo root unless noted. + +- `npm run server` — `cargo run -p server` (debug). `npm run server:prod` for release. +- `npm run client` — Vite dev server for the React client. +- `npm run desktop` — Tauri dev (builds capsule-client + client + release server, then launches shell). `npm run desktop:build` to package. +- `npm run landing` — landing site dev server. +- `npm run capsule` — capsule service. +- `npm run build` — `cargo build --release` of the server only. Output: `target/release/server` (needs a `.env` next to it to run). +- `npm run client:build` — builds capsule-client then the client. +- `npm test` — Jest E2E in `tests/`. Server must already be running on `:6174` with seeded admin. +- `npm run docs:dev` / `docs:build` — VitePress. +- Diesel: `cd apps/server && diesel setup && diesel migration run` (requires diesel_cli). Migrations are also embedded and auto-run on server boot via `MIGRATIONS`. + +## Architecture notes + +- The server is the integration point: HTTP/WS API, embedded client UI, MQTT broker + client, RTP receiver, RTSP puller, WebRTC, recording manager, flow engine, tunnel manager. All share `Arc` (`apps/server/src/state.rs`). Background tasks are spawned into a `JoinSet` driven by a `watch` shutdown channel — prefer that pattern for new long-running tasks. +- Flow runtime: `FlowManagerActor` (mpsc-driven) owns the live engine. New node types go in `src/flow/nodes/` and are registered in `mod.rs`. Engine/types in `src/flow/engine.rs` and `src/flow/types.rs`. +- DB access uses Diesel with an r2d2 pool. Repositories in `src/db/repository/`. When adding tables, add a migration *and* update `schema.rs` (`diesel print-schema`) and models. +- Auth is JWT (`jsonwebtoken`). Initial admin is seeded by `init::db_record::create_initial_admin`. RBAC tables created by the `2025-09-08_create_rbac_tables` migration; permissions seeded on boot. +- Client follows feature-sliced design. New screens: add a route in `src/App.tsx`, page under `pages/`, feature logic under `features//`, domain models under `entities//`, reusable UI in `components/ui` (shadcn-style) or `widgets/`. State is Zustand; data fetching is axios in `shared/api`. +- Maps use MapLibre/Leaflet. Code editor uses Monaco + CodeMirror. Charts via d3. +- Desktop sidecar: `apps/desktop/src-tauri/src/main.rs` launches the bundled server binary; client detects desktop via `useDesktopSidecar`. A separate `desktop_settings` window is dispatched in `App.tsx` based on URL params. + +## Coding rules (enforced by `CODE_RULE.md`) + +Mission-critical posture. Highlights to keep in mind: + +- Fail-safe: every fallible op returns `Result`/`Promise`; handle both arms — no silent failures. Lints fail builds on unhandled results. +- Deterministic: no recursion (use iterative forms), no magic numbers (use `const`/`enum`), fixed-size buffers, no float `==` (use epsilon). +- Security by design: validate all external input at runtime, default to deny, least privilege, keep control flow simple. +- Concurrency: prefer message passing (we already do — `mpsc`/`broadcast`/`watch`) over shared mutable state. +- Tooling: zero warnings, format with `rustfmt` / `prettier` (`.prettierrc` is at repo root: 2-space, semi, double-quote, trailing-comma all, jsxSingleQuote), pass clippy/eslint at strict settings. Lockfiles (`Cargo.lock`, `package-lock.json`) are committed and authoritative. + +## Conventions + +- UI copy is **English only**. Even when prompts/issues are in Korean, ship English strings in `apps/client`. +- Don't create docs/markdown unless asked. +- Don't add comments for the "what"; only for non-obvious "why". +- Branch is `develop` for active work; PRs target `main`. diff --git a/apps/client/package.json b/apps/client/package.json index 994378e..18f4faa 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -17,7 +17,6 @@ "test:watch": "vitest" }, "dependencies": { - "@vessel/capsule-client": "file:../../packages/capsule-client", "@codemirror/lang-json": "^6.0.2", "@emotion/react": "^11.14.0", "@monaco-editor/react": "^4.7.0", @@ -42,12 +41,14 @@ "@tauri-apps/api": "^2.2.0", "@tauri-apps/plugin-shell": "^2.3.4", "@uiw/react-codemirror": "^4.24.2", + "@vessel/capsule-client": "file:../../packages/capsule-client", "autoprefixer": "^10.4.21", "axios": "^1.11.0", "clsx": "^2.1.1", "cmdk": "^1.1.1", "d3": "^7.9.0", "electron": "^35.0.0", + "hls.js": "^1.5.0", "js-cookie": "^3.0.5", "leaflet": "^1.9.4", "maplibre-gl": "^5.6.2", @@ -63,11 +64,13 @@ "zustand": "^5.0.5" }, "devDependencies": { - "vitest": "^3.0.0", "@eslint/js": "^9.21.0", + "@feature-sliced/steiger-plugin": "^0.5.7", "@types/d3": "^7.4.3", "@types/js-cookie": "^3.0.6", "@types/leaflet": "^1.9.20", + "steiger": "^0.5.11", + "vitest": "^3.0.0", "wait-on": "^8.0.3" } } diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index a88bf0a..692bf87 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,32 +1,33 @@ -import { AuthPage } from "./pages/auth"; +import { AuthPage } from "@/pages/auth"; import { createBrowserRouter, RouterProvider } from "react-router"; import { DashboardSwipeLayout, DashboardSwipeRoutePlaceholder, -} from "./features/dashboard-swipe/DashboardSwipeLayout"; -import { DevicePage } from "./pages/devices"; -import { FlowPage } from "./pages/flow"; -import { AuthInterceptor } from "./features/auth/AuthInterceptor"; -import { NotFound } from "./pages/notfound"; -import LandingPage from "./pages/landing"; -import { MapPage } from "./pages/map"; -import { SetupPage } from "./pages/setup"; -import { CodePage } from "./pages/code"; -import { AuthenticatedLayout } from "./widgets/auth/AuthenticatedLayout"; -import { TopBarWrapper } from "./widgets/auth/TopBarWrapper"; -import { useDesktopSidecar } from "./hooks/useDesktopSidecar"; -import { usePreventBackNavigation } from "./hooks/usePreventBackNavigation"; -import { SettingsPage } from "./pages/settings"; -import { AccountSettingsPage } from "./pages/settings/account"; -import { ServicesSettingsPage } from "./pages/settings/services"; -import { UsersSettingsPage } from "./pages/settings/users"; -import { NetworksSettingsPage } from "./pages/settings/networks"; -import { IntegrationSettingsPage } from "./pages/settings/integration"; -import { LogSettingsPage } from "./pages/settings/log"; -import { ConfigSettingsPage } from "./pages/settings/config"; -import { RecordingsPage } from "./pages/recordings"; -import { DesktopSettingsPage } from "./pages/desktop-settings"; +} from "@/features/dashboard-swipe"; +import { DevicePage } from "@/pages/devices"; +import { FlowPage } from "@/pages/flow"; +import { AuthInterceptor } from "@/features/auth"; +import { NotFound } from "@/pages/notfound"; +import LandingPage from "@/pages/landing"; +import { MapPage } from "@/pages/map"; +import { SetupPage } from "@/pages/setup"; +import { CodePage } from "@/pages/code"; +import { AuthenticatedLayout, TopBarWrapper } from "@/widgets/auth"; +import { useDesktopSidecar } from "@/shared/lib/hooks/useDesktopSidecar"; +import { usePreventBackNavigation } from "@/shared/lib/hooks/usePreventBackNavigation"; +import { + SettingsPage, + AccountSettingsPage, + ServicesSettingsPage, + UsersSettingsPage, + NetworksSettingsPage, + IntegrationSettingsPage, + LogSettingsPage, + ConfigSettingsPage, +} from "@/pages/settings"; +import { RecordingsPage } from "@/pages/recordings"; +import { DesktopSettingsPage } from "@/pages/desktop-settings"; const router = createBrowserRouter([ { diff --git a/apps/client/src/app/pageWrapper/page-wrapper.tsx b/apps/client/src/app/pageWrapper/page-wrapper.tsx index 9c7b269..e05706a 100644 --- a/apps/client/src/app/pageWrapper/page-wrapper.tsx +++ b/apps/client/src/app/pageWrapper/page-wrapper.tsx @@ -1,4 +1,4 @@ -import { isElectron } from "@/lib/electron"; +import { isElectron } from "@/shared/lib/electron"; import type { PropsWithChildren } from "react"; export function PageWrapper(props: PropsWithChildren) { diff --git a/apps/client/src/contexts/SupabaseAuthContext.tsx b/apps/client/src/app/providers/SupabaseAuthContext.tsx similarity index 98% rename from apps/client/src/contexts/SupabaseAuthContext.tsx rename to apps/client/src/app/providers/SupabaseAuthContext.tsx index 177a97b..a137faa 100644 --- a/apps/client/src/contexts/SupabaseAuthContext.tsx +++ b/apps/client/src/app/providers/SupabaseAuthContext.tsx @@ -7,7 +7,7 @@ import { type ReactNode, } from "react"; import type { Session } from "@supabase/supabase-js"; -import { supabase } from "@/lib/supabase"; +import { supabase } from "@/shared/lib/supabase"; const isTauri = (): boolean => { return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; diff --git a/apps/client/src/entities/configurations/api.ts b/apps/client/src/entities/configurations/api/index.ts similarity index 96% rename from apps/client/src/entities/configurations/api.ts rename to apps/client/src/entities/configurations/api/index.ts index 0cd42c7..3e3ed21 100644 --- a/apps/client/src/entities/configurations/api.ts +++ b/apps/client/src/entities/configurations/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { SystemConfiguration, SystemConfigurationPayload } from "./types"; +import type { SystemConfiguration, SystemConfigurationPayload } from "../model/types"; export const getConfigs = () => apiClient.get("/configurations"); diff --git a/apps/client/src/entities/configurations/index.ts b/apps/client/src/entities/configurations/index.ts new file mode 100644 index 0000000..471f7a5 --- /dev/null +++ b/apps/client/src/entities/configurations/index.ts @@ -0,0 +1,5 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; +export * from "./lib/codeService"; +export * from "./lib/streamMode"; diff --git a/apps/client/src/entities/configurations/codeService.ts b/apps/client/src/entities/configurations/lib/codeService.ts similarity index 82% rename from apps/client/src/entities/configurations/codeService.ts rename to apps/client/src/entities/configurations/lib/codeService.ts index 9efe74c..d393287 100644 --- a/apps/client/src/entities/configurations/codeService.ts +++ b/apps/client/src/entities/configurations/lib/codeService.ts @@ -1,4 +1,4 @@ -import type { SystemConfiguration } from "./types"; +import type { SystemConfiguration } from "../model/types"; export const CODE_SERVICE_CONFIG_KEY = "code_service_enabled"; diff --git a/apps/client/src/entities/configurations/lib/streamMode.ts b/apps/client/src/entities/configurations/lib/streamMode.ts new file mode 100644 index 0000000..6572aa4 --- /dev/null +++ b/apps/client/src/entities/configurations/lib/streamMode.ts @@ -0,0 +1,15 @@ +import type { SystemConfiguration } from "../model/types"; + +export const STREAM_MODE_CONFIG_KEY = "default_stream_mode"; + +export type StreamMode = "webrtc" | "http"; + +const DEFAULT_MODE: StreamMode = "webrtc"; + +export function getDefaultStreamMode( + configurations: SystemConfiguration[], +): StreamMode { + const row = configurations.find((c) => c.key === STREAM_MODE_CONFIG_KEY); + if (!row) return DEFAULT_MODE; + return row.value === "http" ? "http" : "webrtc"; +} diff --git a/apps/client/src/entities/configurations/store.ts b/apps/client/src/entities/configurations/model/store.ts similarity index 97% rename from apps/client/src/entities/configurations/store.ts rename to apps/client/src/entities/configurations/model/store.ts index 7bdbc25..b32539d 100644 --- a/apps/client/src/entities/configurations/store.ts +++ b/apps/client/src/entities/configurations/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { SystemConfiguration, SystemConfigurationPayload } from "./types"; interface ConfigState { diff --git a/apps/client/src/entities/configurations/types.ts b/apps/client/src/entities/configurations/model/types.ts similarity index 100% rename from apps/client/src/entities/configurations/types.ts rename to apps/client/src/entities/configurations/model/types.ts diff --git a/apps/client/src/entities/custom-nodes/api.ts b/apps/client/src/entities/custom-nodes/api/index.ts similarity index 91% rename from apps/client/src/entities/custom-nodes/api.ts rename to apps/client/src/entities/custom-nodes/api/index.ts index db0d3df..ac7f11e 100644 --- a/apps/client/src/entities/custom-nodes/api.ts +++ b/apps/client/src/entities/custom-nodes/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { CustomNodeDynamicData, CustomNodeFromApi } from "./types"; +import { CustomNodeDynamicData, CustomNodeFromApi } from "../model/types"; export const getAllCustomNodes = async (): Promise => { const response = await apiClient.get("/custom-nodes"); diff --git a/apps/client/src/entities/custom-nodes/index.ts b/apps/client/src/entities/custom-nodes/index.ts new file mode 100644 index 0000000..38f8b68 --- /dev/null +++ b/apps/client/src/entities/custom-nodes/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; +export * from "./lib/presets"; diff --git a/apps/client/src/entities/custom-nodes/presets.ts b/apps/client/src/entities/custom-nodes/lib/presets.ts similarity index 100% rename from apps/client/src/entities/custom-nodes/presets.ts rename to apps/client/src/entities/custom-nodes/lib/presets.ts diff --git a/apps/client/src/entities/custom-nodes/store.ts b/apps/client/src/entities/custom-nodes/model/store.ts similarity index 98% rename from apps/client/src/entities/custom-nodes/store.ts rename to apps/client/src/entities/custom-nodes/model/store.ts index 1a16de5..48934b8 100644 --- a/apps/client/src/entities/custom-nodes/store.ts +++ b/apps/client/src/entities/custom-nodes/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { CustomNodeState } from "./types"; -import * as api from "./api"; +import * as api from "../api"; // const parseNodeData = (nodeFromApi: CustomNodeFromApi): CustomNodeFromApi => { // try { diff --git a/apps/client/src/entities/custom-nodes/types.ts b/apps/client/src/entities/custom-nodes/model/types.ts similarity index 100% rename from apps/client/src/entities/custom-nodes/types.ts rename to apps/client/src/entities/custom-nodes/model/types.ts diff --git a/apps/client/src/entities/device-token/api.ts b/apps/client/src/entities/device-token/api/index.ts similarity index 85% rename from apps/client/src/entities/device-token/api.ts rename to apps/client/src/entities/device-token/api/index.ts index e5037aa..ee2b40b 100644 --- a/apps/client/src/entities/device-token/api.ts +++ b/apps/client/src/entities/device-token/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { DeviceToken, IssuedTokenResponse } from "./types"; +import type { DeviceToken, IssuedTokenResponse } from "../model/types"; export const issueDeviceToken = (deviceId: number) => apiClient.post(`/devices/${deviceId}/token`); diff --git a/apps/client/src/entities/device-token/index.ts b/apps/client/src/entities/device-token/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/device-token/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/device-token/store.ts b/apps/client/src/entities/device-token/model/store.ts similarity index 98% rename from apps/client/src/entities/device-token/store.ts rename to apps/client/src/entities/device-token/model/store.ts index 9a734ed..19c1e2f 100644 --- a/apps/client/src/entities/device-token/store.ts +++ b/apps/client/src/entities/device-token/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { DeviceToken } from "./types"; interface DeviceTokenState { diff --git a/apps/client/src/entities/device-token/types.ts b/apps/client/src/entities/device-token/model/types.ts similarity index 100% rename from apps/client/src/entities/device-token/types.ts rename to apps/client/src/entities/device-token/model/types.ts diff --git a/apps/client/src/entities/device/api.ts b/apps/client/src/entities/device/api/index.ts similarity index 86% rename from apps/client/src/entities/device/api.ts rename to apps/client/src/entities/device/api/index.ts index 56d0a3a..549def9 100644 --- a/apps/client/src/entities/device/api.ts +++ b/apps/client/src/entities/device/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { Device, DevicePayload, DeviceWithEntity } from "./types"; +import type { Device, DevicePayload, DeviceWithEntity } from "../model/types"; export const getDevices = () => apiClient.get("/devices"); export const getDeviceById = (pk_id: number) => diff --git a/apps/client/src/entities/device/index.ts b/apps/client/src/entities/device/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/device/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/device/store.ts b/apps/client/src/entities/device/model/store.ts similarity index 97% rename from apps/client/src/entities/device/store.ts rename to apps/client/src/entities/device/model/store.ts index 069dc65..44b062f 100644 --- a/apps/client/src/entities/device/store.ts +++ b/apps/client/src/entities/device/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Device, DevicePayload } from "./types"; interface DeviceState { diff --git a/apps/client/src/entities/device/types.ts b/apps/client/src/entities/device/model/types.ts similarity index 87% rename from apps/client/src/entities/device/types.ts rename to apps/client/src/entities/device/model/types.ts index af8ba96..95cc9f7 100644 --- a/apps/client/src/entities/device/types.ts +++ b/apps/client/src/entities/device/model/types.ts @@ -1,4 +1,4 @@ -import { EntityAll } from "../entity/types"; +import { EntityAll } from "../../entity/model/types"; export interface Device { id: number; diff --git a/apps/client/src/entities/dynamic-dashboard/api.ts b/apps/client/src/entities/dynamic-dashboard/api/index.ts similarity index 100% rename from apps/client/src/entities/dynamic-dashboard/api.ts rename to apps/client/src/entities/dynamic-dashboard/api/index.ts diff --git a/apps/client/src/entities/dynamic-dashboard/index.ts b/apps/client/src/entities/dynamic-dashboard/index.ts new file mode 100644 index 0000000..dc69921 --- /dev/null +++ b/apps/client/src/entities/dynamic-dashboard/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./model/store"; +export * from "./lib/layoutResolve"; +export * from "./lib/interaction"; diff --git a/apps/client/src/entities/dynamic-dashboard/interaction.ts b/apps/client/src/entities/dynamic-dashboard/lib/interaction.ts similarity index 100% rename from apps/client/src/entities/dynamic-dashboard/interaction.ts rename to apps/client/src/entities/dynamic-dashboard/lib/interaction.ts diff --git a/apps/client/src/entities/dynamic-dashboard/layoutResolve.ts b/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts similarity index 95% rename from apps/client/src/entities/dynamic-dashboard/layoutResolve.ts rename to apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts index 4ada425..e6fbd6b 100644 --- a/apps/client/src/entities/dynamic-dashboard/layoutResolve.ts +++ b/apps/client/src/entities/dynamic-dashboard/lib/layoutResolve.ts @@ -1,4 +1,4 @@ -import type { DashboardGroup, DashboardItem } from "./store"; +import type { DashboardGroup, DashboardItem } from "../model/store"; /** Clamp top-left grid position to group bounds (matches store behavior). */ export function clampItemPosition( diff --git a/apps/client/src/entities/dynamic-dashboard/store.ts b/apps/client/src/entities/dynamic-dashboard/model/store.ts similarity index 99% rename from apps/client/src/entities/dynamic-dashboard/store.ts rename to apps/client/src/entities/dynamic-dashboard/model/store.ts index 714835a..36f728f 100644 --- a/apps/client/src/entities/dynamic-dashboard/store.ts +++ b/apps/client/src/entities/dynamic-dashboard/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; -import * as api from "./api"; -import { clampItemPosition, itemsCollide } from "./layoutResolve"; +import * as api from "../api"; +import { clampItemPosition, itemsCollide } from "../lib/layoutResolve"; export type DashboardItemType = | "entity-card" diff --git a/apps/client/src/entities/entity/api.ts b/apps/client/src/entities/entity/api/index.ts similarity index 92% rename from apps/client/src/entities/entity/api.ts rename to apps/client/src/entities/entity/api/index.ts index 7fbdb0c..80f88f4 100644 --- a/apps/client/src/entities/entity/api.ts +++ b/apps/client/src/entities/entity/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { Entity, EntityAll, EntityPayload, State } from "./types"; +import type { Entity, EntityAll, EntityPayload, State } from "../model/types"; export const getEntities = () => apiClient.get("/entities"); export const getAllEntities = () => apiClient.get("/entities/all"); diff --git a/apps/client/src/entities/entity/index.ts b/apps/client/src/entities/entity/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/entity/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/entity/store.ts b/apps/client/src/entities/entity/model/store.ts similarity index 97% rename from apps/client/src/entities/entity/store.ts rename to apps/client/src/entities/entity/model/store.ts index 64d3604..87d555c 100644 --- a/apps/client/src/entities/entity/store.ts +++ b/apps/client/src/entities/entity/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Entity, EntityPayload } from "./types"; interface EntityState { diff --git a/apps/client/src/entities/entity/types.ts b/apps/client/src/entities/entity/model/types.ts similarity index 100% rename from apps/client/src/entities/entity/types.ts rename to apps/client/src/entities/entity/model/types.ts diff --git a/apps/client/src/entities/file/api.ts b/apps/client/src/entities/file/api/index.ts similarity index 96% rename from apps/client/src/entities/file/api.ts rename to apps/client/src/entities/file/api/index.ts index aea7f96..d49ace1 100644 --- a/apps/client/src/entities/file/api.ts +++ b/apps/client/src/entities/file/api/index.ts @@ -1,4 +1,4 @@ -import { DirEntry } from "./types"; +import { DirEntry } from "../model/types"; import { apiClient } from "@/shared/api"; export const getDirectoryListing = async ( diff --git a/apps/client/src/entities/file/index.ts b/apps/client/src/entities/file/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/file/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/file/store.ts b/apps/client/src/entities/file/model/store.ts similarity index 97% rename from apps/client/src/entities/file/store.ts rename to apps/client/src/entities/file/model/store.ts index 1685d30..a3a774a 100644 --- a/apps/client/src/entities/file/store.ts +++ b/apps/client/src/entities/file/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { IdeState } from "./types"; -import { getFileContent, updateFileContent } from "./api"; +import { getFileContent, updateFileContent } from "../api"; import { toast } from "sonner"; export interface FileTreeState { diff --git a/apps/client/src/entities/file/types.ts b/apps/client/src/entities/file/model/types.ts similarity index 100% rename from apps/client/src/entities/file/types.ts rename to apps/client/src/entities/file/model/types.ts diff --git a/apps/client/src/entities/flow/api.ts b/apps/client/src/entities/flow/api/index.ts similarity index 97% rename from apps/client/src/entities/flow/api.ts rename to apps/client/src/entities/flow/api/index.ts index 638cf58..bc7945a 100644 --- a/apps/client/src/entities/flow/api.ts +++ b/apps/client/src/entities/flow/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { Flow, FlowPayload, FlowVersion, FlowVersionPayload } from "./types"; +import { Flow, FlowPayload, FlowVersion, FlowVersionPayload } from "../model/types"; export const getFlows = async (): Promise => { const { data } = await apiClient.get("/flows"); diff --git a/apps/client/src/entities/flow/index.ts b/apps/client/src/entities/flow/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/flow/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/flow/store.ts b/apps/client/src/entities/flow/model/store.ts similarity index 96% rename from apps/client/src/entities/flow/store.ts rename to apps/client/src/entities/flow/model/store.ts index 443bc19..3e25511 100644 --- a/apps/client/src/entities/flow/store.ts +++ b/apps/client/src/entities/flow/model/store.ts @@ -1,12 +1,12 @@ import { create } from "zustand"; -import { DataNodeType, Edge, Node } from "@/features/flow/flowTypes"; +import { DataNodeType, Edge, Node } from "@/features/flow"; import { getFlows, getFlowVersions, saveFlowVersion, createFlow, -} from "@/entities/flow/api"; -import { Flow } from "@/entities/flow/types"; +} from "@/entities/flow"; +import { Flow } from "@/entities/flow"; interface FlowState { flows: Flow[]; diff --git a/apps/client/src/entities/flow/types.ts b/apps/client/src/entities/flow/model/types.ts similarity index 100% rename from apps/client/src/entities/flow/types.ts rename to apps/client/src/entities/flow/model/types.ts diff --git a/apps/client/src/entities/ha/api.ts b/apps/client/src/entities/ha/api/index.ts similarity index 89% rename from apps/client/src/entities/ha/api.ts rename to apps/client/src/entities/ha/api/index.ts index ea7f3e4..bd36a0a 100644 --- a/apps/client/src/entities/ha/api.ts +++ b/apps/client/src/entities/ha/api/index.ts @@ -1,6 +1,6 @@ import { apiClient } from "@/shared/api"; -import { HaState } from "./types"; +import { HaState } from "../model/types"; export const fetchAllHaStates = async (): Promise => { try { diff --git a/apps/client/src/entities/ha/index.ts b/apps/client/src/entities/ha/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/ha/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/ha/store.ts b/apps/client/src/entities/ha/model/store.ts similarity index 92% rename from apps/client/src/entities/ha/store.ts rename to apps/client/src/entities/ha/model/store.ts index 81e75cf..40fb893 100644 --- a/apps/client/src/entities/ha/store.ts +++ b/apps/client/src/entities/ha/model/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { HaStateStore } from "./types"; -import { fetchAllHaStates } from "./api"; +import { fetchAllHaStates } from "../api"; export const useHaStore = create((set) => ({ states: [], diff --git a/apps/client/src/entities/ha/types.ts b/apps/client/src/entities/ha/model/types.ts similarity index 100% rename from apps/client/src/entities/ha/types.ts rename to apps/client/src/entities/ha/model/types.ts diff --git a/apps/client/src/entities/integrations/api.ts b/apps/client/src/entities/integrations/api/index.ts similarity index 95% rename from apps/client/src/entities/integrations/api.ts rename to apps/client/src/entities/integrations/api/index.ts index dd69116..536b10b 100644 --- a/apps/client/src/entities/integrations/api.ts +++ b/apps/client/src/entities/integrations/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import type { IntegrationStatus, IntegrationRegisterPayload } from "./types"; +import type { IntegrationStatus, IntegrationRegisterPayload } from "../model/types"; export const getIntegrationStatus = () => apiClient.get("/integrations/status"); diff --git a/apps/client/src/entities/integrations/index.ts b/apps/client/src/entities/integrations/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/integrations/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/integrations/store.ts b/apps/client/src/entities/integrations/model/store.ts similarity index 97% rename from apps/client/src/entities/integrations/store.ts rename to apps/client/src/entities/integrations/model/store.ts index f45aeb9..9241bcf 100644 --- a/apps/client/src/entities/integrations/store.ts +++ b/apps/client/src/entities/integrations/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; interface IntegrationState { isHaConnected: boolean; diff --git a/apps/client/src/entities/integrations/types.ts b/apps/client/src/entities/integrations/model/types.ts similarity index 100% rename from apps/client/src/entities/integrations/types.ts rename to apps/client/src/entities/integrations/model/types.ts diff --git a/apps/client/src/entities/log/api.ts b/apps/client/src/entities/log/api/index.ts similarity index 88% rename from apps/client/src/entities/log/api.ts rename to apps/client/src/entities/log/api/index.ts index c23fe26..61d8d97 100644 --- a/apps/client/src/entities/log/api.ts +++ b/apps/client/src/entities/log/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { LogContentResponse, LogFileListResponse } from "./types"; +import { LogContentResponse, LogFileListResponse } from "../model/types"; export const getLatestLog = async (): Promise => { const { data } = await apiClient.get("/logs/latest"); diff --git a/apps/client/src/entities/log/index.ts b/apps/client/src/entities/log/index.ts new file mode 100644 index 0000000..81a3aef --- /dev/null +++ b/apps/client/src/entities/log/index.ts @@ -0,0 +1,2 @@ +export * from "./api"; +export * from "./model/types"; diff --git a/apps/client/src/entities/log/types.ts b/apps/client/src/entities/log/model/types.ts similarity index 100% rename from apps/client/src/entities/log/types.ts rename to apps/client/src/entities/log/model/types.ts diff --git a/apps/client/src/entities/map/api.ts b/apps/client/src/entities/map/api/index.ts similarity index 98% rename from apps/client/src/entities/map/api.ts rename to apps/client/src/entities/map/api/index.ts index ce78e0c..c99efa2 100644 --- a/apps/client/src/entities/map/api.ts +++ b/apps/client/src/entities/map/api/index.ts @@ -7,7 +7,7 @@ import { MapFeature, FeatureWithVertices, UpdateFeaturePayload, -} from "./types"; +} from "../model/types"; export const getAllLayers = async (): Promise => { const response = await apiClient.get("/map/layers"); diff --git a/apps/client/src/entities/map/index.ts b/apps/client/src/entities/map/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/map/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/map/store.ts b/apps/client/src/entities/map/model/store.ts similarity index 99% rename from apps/client/src/entities/map/store.ts rename to apps/client/src/entities/map/model/store.ts index 609a211..34dd617 100644 --- a/apps/client/src/entities/map/store.ts +++ b/apps/client/src/entities/map/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as mapApi from "./api"; +import * as mapApi from "../api"; import { MapDataState, MapInteractionState, diff --git a/apps/client/src/entities/map/types.ts b/apps/client/src/entities/map/model/types.ts similarity index 100% rename from apps/client/src/entities/map/types.ts rename to apps/client/src/entities/map/model/types.ts diff --git a/apps/client/src/entities/permission/api.ts b/apps/client/src/entities/permission/api/index.ts similarity index 73% rename from apps/client/src/entities/permission/api.ts rename to apps/client/src/entities/permission/api/index.ts index c5574a6..f92b592 100644 --- a/apps/client/src/entities/permission/api.ts +++ b/apps/client/src/entities/permission/api/index.ts @@ -1,4 +1,4 @@ import { apiClient } from "@/shared/api"; -import { Permission } from "./types"; +import { Permission } from "../model/types"; export const getPermissions = () => apiClient.get("/permissions"); diff --git a/apps/client/src/entities/permission/index.ts b/apps/client/src/entities/permission/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/permission/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/permission/store.ts b/apps/client/src/entities/permission/model/store.ts similarity index 95% rename from apps/client/src/entities/permission/store.ts rename to apps/client/src/entities/permission/model/store.ts index f841b83..2c47774 100644 --- a/apps/client/src/entities/permission/store.ts +++ b/apps/client/src/entities/permission/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { getPermissions } from "./api"; +import { getPermissions } from "../api"; import { Permission } from "./types"; type PermissionState = { diff --git a/apps/client/src/entities/permission/types.ts b/apps/client/src/entities/permission/model/types.ts similarity index 100% rename from apps/client/src/entities/permission/types.ts rename to apps/client/src/entities/permission/model/types.ts diff --git a/apps/client/src/entities/recording/api.ts b/apps/client/src/entities/recording/api/index.ts similarity index 98% rename from apps/client/src/entities/recording/api.ts rename to apps/client/src/entities/recording/api/index.ts index ab91e6b..78895f1 100644 --- a/apps/client/src/entities/recording/api.ts +++ b/apps/client/src/entities/recording/api/index.ts @@ -5,7 +5,7 @@ import { StartRecordingResponse, ActiveRecordingInfo, TopicRecordingStatus, -} from "./types"; +} from "../model/types"; export const getRecordings = async (): Promise => { const { data } = await apiClient.get("/recordings"); diff --git a/apps/client/src/entities/recording/index.ts b/apps/client/src/entities/recording/index.ts index dc2dd3d..a0b7a18 100644 --- a/apps/client/src/entities/recording/index.ts +++ b/apps/client/src/entities/recording/index.ts @@ -1,3 +1,3 @@ -export * from "./types"; export * from "./api"; -export { useRecordingStore } from "./store"; +export * from "./model/types"; +export { useRecordingStore } from "./model/store"; diff --git a/apps/client/src/entities/recording/store.ts b/apps/client/src/entities/recording/model/store.ts similarity index 98% rename from apps/client/src/entities/recording/store.ts rename to apps/client/src/entities/recording/model/store.ts index f4c6396..a956478 100644 --- a/apps/client/src/entities/recording/store.ts +++ b/apps/client/src/entities/recording/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Recording, ActiveRecordingInfo } from "./types"; interface RecordingState { diff --git a/apps/client/src/entities/recording/types.ts b/apps/client/src/entities/recording/model/types.ts similarity index 100% rename from apps/client/src/entities/recording/types.ts rename to apps/client/src/entities/recording/model/types.ts diff --git a/apps/client/src/entities/role/api.ts b/apps/client/src/entities/role/api/index.ts similarity index 83% rename from apps/client/src/entities/role/api.ts rename to apps/client/src/entities/role/api/index.ts index 1935198..ad50273 100644 --- a/apps/client/src/entities/role/api.ts +++ b/apps/client/src/entities/role/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { Role, CreateRolePayload, UpdateRolePayload } from "./types"; +import { Role, CreateRolePayload, UpdateRolePayload } from "../model/types"; export const getRoles = () => apiClient.get("/roles"); export const createRole = (data: CreateRolePayload) => diff --git a/apps/client/src/entities/role/index.ts b/apps/client/src/entities/role/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/role/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/role/store.ts b/apps/client/src/entities/role/model/store.ts similarity index 96% rename from apps/client/src/entities/role/store.ts rename to apps/client/src/entities/role/model/store.ts index 6d09c06..cfd2dcf 100644 --- a/apps/client/src/entities/role/store.ts +++ b/apps/client/src/entities/role/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { getRoles, createRole, updateRole, deleteRole } from "./api"; +import { getRoles, createRole, updateRole, deleteRole } from "../api"; import { Role, CreateRolePayload, UpdateRolePayload } from "./types"; type RoleState = { diff --git a/apps/client/src/entities/role/types.ts b/apps/client/src/entities/role/model/types.ts similarity index 82% rename from apps/client/src/entities/role/types.ts rename to apps/client/src/entities/role/model/types.ts index eb5c229..a9b6eaa 100644 --- a/apps/client/src/entities/role/types.ts +++ b/apps/client/src/entities/role/model/types.ts @@ -1,4 +1,4 @@ -import { Permission } from "@/entities/permission/types"; +import { Permission } from "@/entities/permission"; export type Role = { id: number; diff --git a/apps/client/src/entities/stat/api.ts b/apps/client/src/entities/stat/api/index.ts similarity index 81% rename from apps/client/src/entities/stat/api.ts rename to apps/client/src/entities/stat/api/index.ts index 484f0d8..c6f3529 100644 --- a/apps/client/src/entities/stat/api.ts +++ b/apps/client/src/entities/stat/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { Stat } from "./types"; +import { Stat } from "../model/types"; export const getStats = async (): Promise => { const { data } = await apiClient.get("/stat"); diff --git a/apps/client/src/entities/stat/index.ts b/apps/client/src/entities/stat/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/stat/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/stat/store.ts b/apps/client/src/entities/stat/model/store.ts similarity index 95% rename from apps/client/src/entities/stat/store.ts rename to apps/client/src/entities/stat/model/store.ts index 2034d0d..5c46593 100644 --- a/apps/client/src/entities/stat/store.ts +++ b/apps/client/src/entities/stat/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { Stat } from "./types"; interface StatState { diff --git a/apps/client/src/entities/stat/types.ts b/apps/client/src/entities/stat/model/types.ts similarity index 100% rename from apps/client/src/entities/stat/types.ts rename to apps/client/src/entities/stat/model/types.ts diff --git a/apps/client/src/entities/tunnel/api.ts b/apps/client/src/entities/tunnel/api/index.ts similarity index 93% rename from apps/client/src/entities/tunnel/api.ts rename to apps/client/src/entities/tunnel/api/index.ts index 246ea18..77dc2a2 100644 --- a/apps/client/src/entities/tunnel/api.ts +++ b/apps/client/src/entities/tunnel/api/index.ts @@ -3,7 +3,7 @@ import type { StartTunnelRequest, StartTunnelResponse, TunnelStatus, -} from "./types"; +} from "../model/types"; export const getStatus = () => apiClient.get("/tunnel/status"); diff --git a/apps/client/src/entities/tunnel/index.ts b/apps/client/src/entities/tunnel/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/tunnel/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/tunnel/store.ts b/apps/client/src/entities/tunnel/model/store.ts similarity index 98% rename from apps/client/src/entities/tunnel/store.ts rename to apps/client/src/entities/tunnel/model/store.ts index a97bbef..7a8a695 100644 --- a/apps/client/src/entities/tunnel/store.ts +++ b/apps/client/src/entities/tunnel/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import * as api from "./api"; +import * as api from "../api"; import type { TunnelStatus } from "./types"; type TunnelState = { diff --git a/apps/client/src/entities/tunnel/types.ts b/apps/client/src/entities/tunnel/model/types.ts similarity index 100% rename from apps/client/src/entities/tunnel/types.ts rename to apps/client/src/entities/tunnel/model/types.ts diff --git a/apps/client/src/entities/user/api.ts b/apps/client/src/entities/user/api/index.ts similarity index 89% rename from apps/client/src/entities/user/api.ts rename to apps/client/src/entities/user/api/index.ts index dd34023..46875d0 100644 --- a/apps/client/src/entities/user/api.ts +++ b/apps/client/src/entities/user/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { User, CreateUserPayload, UpdateUserPayload } from "./types"; +import { User, CreateUserPayload, UpdateUserPayload } from "../model/types"; export const getUsers = () => apiClient.get("/users"); export const createUser = (data: CreateUserPayload) => diff --git a/apps/client/src/entities/user/index.ts b/apps/client/src/entities/user/index.ts new file mode 100644 index 0000000..a3546ba --- /dev/null +++ b/apps/client/src/entities/user/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./model/types"; +export * from "./model/store"; diff --git a/apps/client/src/entities/user/store.ts b/apps/client/src/entities/user/model/store.ts similarity index 95% rename from apps/client/src/entities/user/store.ts rename to apps/client/src/entities/user/model/store.ts index d37440f..5a0e029 100644 --- a/apps/client/src/entities/user/store.ts +++ b/apps/client/src/entities/user/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { getUsers, createUser, updateUser, deleteUser } from "./api"; +import { getUsers, createUser, updateUser, deleteUser } from "../api"; import { User, CreateUserPayload, UpdateUserPayload } from "./types"; type UserState = { diff --git a/apps/client/src/entities/user/types.ts b/apps/client/src/entities/user/model/types.ts similarity index 87% rename from apps/client/src/entities/user/types.ts rename to apps/client/src/entities/user/model/types.ts index 6e41b64..5f9348a 100644 --- a/apps/client/src/entities/user/types.ts +++ b/apps/client/src/entities/user/model/types.ts @@ -1,4 +1,4 @@ -import { Role } from "../role/types"; +import { Role } from "../../role/model/types"; export type User = { id: number; diff --git a/apps/client/src/features/account-switcher/index.ts b/apps/client/src/features/account-switcher/index.ts new file mode 100644 index 0000000..0a020d4 --- /dev/null +++ b/apps/client/src/features/account-switcher/index.ts @@ -0,0 +1 @@ +export { AccountSwitcher } from "./ui/AccountSwitcher"; diff --git a/apps/client/src/features/account-switcher/index.tsx b/apps/client/src/features/account-switcher/index.tsx deleted file mode 100644 index 4dcbc4c..0000000 --- a/apps/client/src/features/account-switcher/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react"; -import { Check, ChevronsUpDown } from "lucide-react"; - -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from "@/components/ui/sidebar"; -import { VesselLogo } from "@/components/icon/Logo"; - -export function AccountSwitcher({ - versions, - defaultVersion, -}: { - versions: string[]; - defaultVersion: string; -}) { - const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion); - - return ( - - - - - -
- -
-
- Server - @{selectedVersion} -
- -
-
- - {versions.map((version) => ( - setSelectedVersion(version)} - > - @{version}{" "} - {version === selectedVersion && } - - ))} - -
-
-
- ); -} diff --git a/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx b/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx new file mode 100644 index 0000000..fbc5bc5 --- /dev/null +++ b/apps/client/src/features/account-switcher/ui/AccountSwitcher.tsx @@ -0,0 +1,152 @@ +import * as React from "react"; +import { Check, ChevronsUpDown, Plus, X } from "lucide-react"; +import { useNavigate } from "react-router"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/shared/ui/dropdown-menu"; +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/shared/ui/sidebar"; +import { VesselLogo } from "@/shared/ui/icon/Logo"; +import { storage, type ServerConnection } from "@/shared/lib/storage"; +import { hardNavigateToDashboard } from "@/shared/lib/resetStores"; +import { isTauri } from "@/shared/lib/desktop"; + +function displayName(server: ServerConnection): string { + if (server.name) return server.name; + try { + return new URL(server.url).host; + } catch { + return server.url; + } +} + +function useServers() { + const subscribe = React.useCallback( + (cb: () => void) => storage.subscribeServers(cb), + [], + ); + const servers = React.useSyncExternalStore( + subscribe, + () => storage.getServers(), + () => [], + ); + const active = React.useSyncExternalStore( + subscribe, + () => storage.getActiveServer(), + () => null, + ); + return { servers, active }; +} + +export function AccountSwitcher() { + const { servers, active } = useServers(); + const navigate = useNavigate(); + const desktop = isTauri(); + + const headerLabel = active ? displayName(active) : "main"; + + const switchTo = (id: string) => { + if (active?.id === id) return; + storage.setActiveServer(id); + hardNavigateToDashboard(); + }; + + const remove = (id: string) => { + const wasActive = active?.id === id; + storage.removeServer(id); + if (wasActive) { + const remaining = storage.getServers(); + if (remaining.length > 0) { + hardNavigateToDashboard(); + } else { + navigate("/auth", { replace: true }); + } + } + }; + + return ( + + + + + +
+ +
+
+ Server + + {headerLabel} + +
+ +
+
+ + {servers.length === 0 && ( + No servers + )} + {servers.map((server) => { + const isActive = active?.id === server.id; + return ( + { + e.preventDefault(); + switchTo(server.id); + }} + className='flex items-center gap-2' + > + + @{displayName(server)} + + {isActive && } + {!desktop && ( + + )} + + ); + })} + {!desktop && ( + <> + + navigate("/auth?add=1")} + className='flex items-center gap-2' + > + + Add server + + + )} + +
+
+
+ ); +} diff --git a/apps/client/src/features/auth/api.ts b/apps/client/src/features/auth/api/index.ts similarity index 96% rename from apps/client/src/features/auth/api.ts rename to apps/client/src/features/auth/api/index.ts index f151d8f..95ffa27 100644 --- a/apps/client/src/features/auth/api.ts +++ b/apps/client/src/features/auth/api/index.ts @@ -1,5 +1,5 @@ import { apiClient } from "@/shared/api"; -import { User } from "@/entities/user/types"; +import { User } from "@/entities/user"; export type AuthCredentials = { id: string; diff --git a/apps/client/src/features/auth/hook.ts b/apps/client/src/features/auth/hook.ts deleted file mode 100644 index 4602f8d..0000000 --- a/apps/client/src/features/auth/hook.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useCallback } from "react"; -import { useNavigate } from "react-router"; -import { storage } from "@/lib/storage"; - -export const useLogout = () => { - const navigate = useNavigate(); - - const logout = useCallback(() => { - storage.removeToken(); - storage.removeServerUrl(); - navigate("/auth", { replace: true }); - }, [navigate]); - - return { logout }; -}; diff --git a/apps/client/src/features/auth/index.ts b/apps/client/src/features/auth/index.ts new file mode 100644 index 0000000..738be84 --- /dev/null +++ b/apps/client/src/features/auth/index.ts @@ -0,0 +1,5 @@ +export { LoginForm } from "./ui/LoginForm"; +export { AuthInterceptor } from "./ui/AuthInterceptor"; +export { DefaultAdminPasswordDialog } from "./ui/DefaultAdminPasswordDialog"; +export { useLogout } from "./model/hook"; +export * from "./api"; diff --git a/apps/client/src/features/auth/model/hook.ts b/apps/client/src/features/auth/model/hook.ts new file mode 100644 index 0000000..4aa4297 --- /dev/null +++ b/apps/client/src/features/auth/model/hook.ts @@ -0,0 +1,23 @@ +import { useCallback } from "react"; +import { useNavigate } from "react-router"; +import { storage } from "@/shared/lib/storage"; +import { hardNavigateToDashboard } from "@/shared/lib/resetStores"; + +export const useLogout = () => { + const navigate = useNavigate(); + + const logout = useCallback(() => { + const active = storage.getActiveServer(); + if (active) { + storage.removeServer(active.id); + } + const remaining = storage.getServers(); + if (remaining.length > 0) { + hardNavigateToDashboard(); + return; + } + navigate("/auth", { replace: true }); + }, [navigate]); + + return { logout }; +}; diff --git a/apps/client/src/features/auth/AuthInterceptor.tsx b/apps/client/src/features/auth/ui/AuthInterceptor.tsx similarity index 85% rename from apps/client/src/features/auth/AuthInterceptor.tsx rename to apps/client/src/features/auth/ui/AuthInterceptor.tsx index 0402de1..40b3392 100644 --- a/apps/client/src/features/auth/AuthInterceptor.tsx +++ b/apps/client/src/features/auth/ui/AuthInterceptor.tsx @@ -1,8 +1,8 @@ import { useEffect } from "react"; import { Navigate, useLocation } from "react-router"; -import { parseJwt } from "@/lib/jwt"; -import { storage } from "@/lib/storage"; -import { isDemoMode } from "@/shared/demo"; +import { parseJwt } from "@/shared/lib/jwt"; +import { storage } from "@/shared/lib/storage"; +import { isDemoMode } from "@/shared/config/demo"; export function AuthInterceptor({ children }: { children: React.ReactNode }) { const location = useLocation(); diff --git a/apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx b/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx similarity index 95% rename from apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx rename to apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx index 25f7379..1dc2b5c 100644 --- a/apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx +++ b/apps/client/src/features/auth/ui/DefaultAdminPasswordDialog.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Dialog, DialogContent, @@ -11,12 +11,12 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { authenticateWithPassword, fetchAdminUser, updateUserPassword, -} from "./api"; +} from "../api"; type DefaultAdminPasswordDialogProps = { open: boolean; diff --git a/apps/client/src/features/auth/index.tsx b/apps/client/src/features/auth/ui/LoginForm.tsx similarity index 87% rename from apps/client/src/features/auth/index.tsx rename to apps/client/src/features/auth/ui/LoginForm.tsx index f7c523a..75ba4cb 100644 --- a/apps/client/src/features/auth/index.tsx +++ b/apps/client/src/features/auth/ui/LoginForm.tsx @@ -1,23 +1,23 @@ import { useEffect, useState } from "react"; import { toast } from "sonner"; -import { cn } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { useNavigate } from "react-router"; +import { cn } from "@/shared/lib/utils"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { useNavigate, useSearchParams } from "react-router"; import { ArrowRight, Loader2, Trash2 } from "lucide-react"; -import { DEMO_SERVER_URL, DEMO_TOKEN, isDemoMode } from "@/shared/demo"; +import { DEMO_SERVER_URL, DEMO_TOKEN, isDemoMode } from "@/shared/config/demo"; import { DefaultAdminPasswordDialog } from "./DefaultAdminPasswordDialog"; -import { authenticateWithPassword } from "./api"; -import { storage } from "@/lib/storage"; -import { parseJwt } from "@/lib/jwt"; +import { authenticateWithPassword } from "../api"; +import { storage } from "@/shared/lib/storage"; +import { parseJwt } from "@/shared/lib/jwt"; import { ensureSidecarRunning, getDesktopServerUrl, isTauri, openDesktopSettings, -} from "@/shared/desktop"; +} from "@/shared/lib/desktop"; const MAX_RECENT_URLS = 5; @@ -53,11 +53,12 @@ export function LoginForm({ ); const [desktopError, setDesktopError] = useState(null); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const isAddMode = searchParams.get("add") === "1"; const connectToServer = async (targetUrl: string) => { if (isDemoMode) { - storage.setServerUrl(DEMO_SERVER_URL); - storage.setToken(DEMO_TOKEN); + storage.addServer({ url: DEMO_SERVER_URL, token: DEMO_TOKEN }); toast.success("Demo mode enabled. Loading demo dashboard..."); navigate("/dashboard"); return; @@ -98,7 +99,6 @@ export function LoginForm({ setRecentUrls(updatedUrls); storage.setRecentUrls(updatedUrls); - storage.setServerUrl(processedUrl); setServerUrlForDialog(processedUrl); setShowAuthFields(true); @@ -148,8 +148,7 @@ export function LoginForm({ password, }); - storage.setToken(token); - storage.setServerUrl(processedUrl); + storage.addServer({ url: processedUrl, token }); setServerUrlForDialog(processedUrl); @@ -193,7 +192,13 @@ export function LoginForm({ ); return; } - storage.setServerUrl(baseUrl); + // Tauri: ensure a server entry exists for the local sidecar; token added on auth success. + const existing = storage.getServers().find((s) => s.url === baseUrl); + if (!existing) { + storage.addServer({ url: baseUrl, token: "" }); + } else { + storage.setActiveServer(existing.id); + } setUrl(baseUrl); setShowAuthFields(true); } finally { @@ -203,8 +208,7 @@ export function LoginForm({ useEffect(() => { if (isDemoMode) { - storage.setServerUrl(DEMO_SERVER_URL); - storage.setToken(DEMO_TOKEN); + storage.addServer({ url: DEMO_SERVER_URL, token: DEMO_TOKEN }); toast.message("Demo mode active", { description: "Using mock data without a backend.", }); @@ -212,21 +216,23 @@ export function LoginForm({ return; } - const token = storage.getToken(); - const serverUrl = storage.getServerUrl(); + if (!isAddMode) { + const token = storage.getToken(); + const serverUrl = storage.getServerUrl(); - if (token && serverUrl) { - const parsed = parseJwt(token); - if (!parsed?.exp) { - storage.removeToken(); - } else { - const now = new Date(); - const exp = new Date(parsed.exp * 1000); - if (now.getTime() >= exp.getTime()) { + if (token && serverUrl) { + const parsed = parseJwt(token); + if (!parsed?.exp) { storage.removeToken(); } else { - navigate("/dashboard"); - return; + const now = new Date(); + const exp = new Date(parsed.exp * 1000); + if (now.getTime() >= exp.getTime()) { + storage.removeToken(); + } else { + navigate("/dashboard"); + return; + } } } } @@ -240,7 +246,7 @@ export function LoginForm({ if (storedUrls.length > 0) { setRecentUrls(storedUrls); } - }, [navigate, isTauriClient]); + }, [navigate, isTauriClient, isAddMode]); return (
@@ -399,9 +405,10 @@ export function LoginForm({ open={isPasswordDialogOpen} serverUrl={serverUrlForDialog} onSuccess={(refreshedToken) => { - storage.setToken(refreshedToken); if (serverUrlForDialog) { - storage.setServerUrl(serverUrlForDialog); + storage.addServer({ url: serverUrlForDialog, token: refreshedToken }); + } else { + storage.setToken(refreshedToken); } setIsPasswordDialogOpen(false); toast.success( diff --git a/apps/client/src/features/code/index.ts b/apps/client/src/features/code/index.ts new file mode 100644 index 0000000..27d3f6c --- /dev/null +++ b/apps/client/src/features/code/index.ts @@ -0,0 +1,3 @@ +export { FileEditor } from "./ui/FileEditor"; +export { FileTree } from "./ui/FileTree"; +export { CreateItemDialog } from "./ui/CreateItemDialog"; diff --git a/apps/client/src/features/code/CreateItemDialog.tsx b/apps/client/src/features/code/ui/CreateItemDialog.tsx similarity index 92% rename from apps/client/src/features/code/CreateItemDialog.tsx rename to apps/client/src/features/code/ui/CreateItemDialog.tsx index a1c118a..d3babcf 100644 --- a/apps/client/src/features/code/CreateItemDialog.tsx +++ b/apps/client/src/features/code/ui/CreateItemDialog.tsx @@ -5,9 +5,9 @@ import { DialogHeader, DialogTitle, DialogFooter, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Button } from "@/shared/ui/button"; interface CreateItemDialogProps { isOpen: boolean; diff --git a/apps/client/src/features/code/FileEditor.tsx b/apps/client/src/features/code/ui/FileEditor.tsx similarity index 95% rename from apps/client/src/features/code/FileEditor.tsx rename to apps/client/src/features/code/ui/FileEditor.tsx index 15cd4f7..cd737ab 100644 --- a/apps/client/src/features/code/FileEditor.tsx +++ b/apps/client/src/features/code/ui/FileEditor.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/components/ui/button"; -import { useIdeStore } from "@/entities/file/store"; +import { Button } from "@/shared/ui/button"; +import { useIdeStore } from "@/entities/file"; import Editor, { loader } from "@monaco-editor/react"; import { Loader2 } from "lucide-react"; import * as monaco from "monaco-editor"; diff --git a/apps/client/src/features/code/FileTree.tsx b/apps/client/src/features/code/ui/FileTree.tsx similarity index 96% rename from apps/client/src/features/code/FileTree.tsx rename to apps/client/src/features/code/ui/FileTree.tsx index 51c43b9..4041c42 100644 --- a/apps/client/src/features/code/FileTree.tsx +++ b/apps/client/src/features/code/ui/FileTree.tsx @@ -15,16 +15,16 @@ import { createNewFolder, renameEntry, deleteEntry, -} from "@/entities/file/api"; +} from "@/entities/file"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, -} from "@/components/ui/context-menu"; +} from "@/shared/ui/context-menu"; import { AlertDialog, AlertDialogAction, @@ -34,9 +34,9 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { useFileTreeStore, useIdeStore } from "@/entities/file/store"; -import { DirEntry } from "@/entities/file/types"; +} from "@/shared/ui/alert-dialog"; +import { useFileTreeStore, useIdeStore } from "@/entities/file"; +import { DirEntry } from "@/entities/file"; import { CreateItemDialog } from "./CreateItemDialog"; interface TreeNodeProps { diff --git a/apps/client/src/features/configurations/index.ts b/apps/client/src/features/configurations/index.ts new file mode 100644 index 0000000..b4aa917 --- /dev/null +++ b/apps/client/src/features/configurations/index.ts @@ -0,0 +1,3 @@ +export { ConfigurationActionButton } from "./ui/ConfigurationActionButton"; +export { ConfigurationCreate } from "./ui/ConfigurationCreate"; +export { ConfigurationCreateButton } from "./ui/ConfigurationCreateButton"; diff --git a/apps/client/src/features/configurations/ConfigurationActionButton.tsx b/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx similarity index 91% rename from apps/client/src/features/configurations/ConfigurationActionButton.tsx rename to apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx index 3161312..779a142 100644 --- a/apps/client/src/features/configurations/ConfigurationActionButton.tsx +++ b/apps/client/src/features/configurations/ui/ConfigurationActionButton.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { MoreHorizontal, Pencil, Trash2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -9,13 +9,13 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, @@ -25,16 +25,16 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; -import { useConfigStore } from "@/entities/configurations/store"; +} from "@/shared/ui/alert-dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Switch } from "@/shared/ui/switch"; +import { Textarea } from "@/shared/ui/textarea"; +import { useConfigStore } from "@/entities/configurations"; import { SystemConfiguration, SystemConfigurationPayload, -} from "@/entities/configurations/types"; +} from "@/entities/configurations"; interface Props { config: SystemConfiguration; diff --git a/apps/client/src/features/configurations/ConfigurationCreate.tsx b/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx similarity index 87% rename from apps/client/src/features/configurations/ConfigurationCreate.tsx rename to apps/client/src/features/configurations/ui/ConfigurationCreate.tsx index d57dd3e..b0e6e14 100644 --- a/apps/client/src/features/configurations/ConfigurationCreate.tsx +++ b/apps/client/src/features/configurations/ui/ConfigurationCreate.tsx @@ -1,18 +1,18 @@ import { Controller, useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; -import { useConfigStore } from "@/entities/configurations/store"; -import { SystemConfigurationPayload } from "@/entities/configurations/types"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Switch } from "@/shared/ui/switch"; +import { Textarea } from "@/shared/ui/textarea"; +import { useConfigStore } from "@/entities/configurations"; +import { SystemConfigurationPayload } from "@/entities/configurations"; export function ConfigurationCreate({ isOpen, diff --git a/apps/client/src/features/configurations/ConfigurationCreateButton.tsx b/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx similarity index 91% rename from apps/client/src/features/configurations/ConfigurationCreateButton.tsx rename to apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx index d43fc69..8d961e7 100644 --- a/apps/client/src/features/configurations/ConfigurationCreateButton.tsx +++ b/apps/client/src/features/configurations/ui/ConfigurationCreateButton.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { ConfigurationCreate } from "./ConfigurationCreate"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { PlusCircle } from "lucide-react"; export function ConfigurationCreateButton() { diff --git a/apps/client/src/features/darkmode/index.ts b/apps/client/src/features/darkmode/index.ts new file mode 100644 index 0000000..f59aa1c --- /dev/null +++ b/apps/client/src/features/darkmode/index.ts @@ -0,0 +1 @@ +export { ModeToggle } from "./ui/ModeToggle"; diff --git a/apps/client/src/features/darkmode/mode-toggle.tsx b/apps/client/src/features/darkmode/ui/ModeToggle.tsx similarity index 92% rename from apps/client/src/features/darkmode/mode-toggle.tsx rename to apps/client/src/features/darkmode/ui/ModeToggle.tsx index 181396c..8619622 100644 --- a/apps/client/src/features/darkmode/mode-toggle.tsx +++ b/apps/client/src/features/darkmode/ui/ModeToggle.tsx @@ -1,12 +1,12 @@ import { Moon, Sun } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { useTheme } from "@/app/providers/theme-provider"; export function ModeToggle() { diff --git a/apps/client/src/features/dashboard-swipe/index.ts b/apps/client/src/features/dashboard-swipe/index.ts new file mode 100644 index 0000000..55ef2a7 --- /dev/null +++ b/apps/client/src/features/dashboard-swipe/index.ts @@ -0,0 +1,2 @@ +export { DashboardSwipeLayout, DashboardSwipeRoutePlaceholder } from "./ui/DashboardSwipeLayout"; +export { DashboardSwipeHeader } from "./ui/DashboardSwipeHeader"; diff --git a/apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx similarity index 95% rename from apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx rename to apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx index 47f2786..1be25ce 100644 --- a/apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx +++ b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeHeader.tsx @@ -7,41 +7,41 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Button } from "@/components/ui/button"; +} from "@/shared/ui/breadcrumb"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import { SidebarTrigger } from "@/components/ui/sidebar"; +} from "@/shared/ui/dropdown-menu"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Separator } from "@/shared/ui/separator"; +import { SidebarTrigger } from "@/shared/ui/sidebar"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; +} from "@/shared/ui/select"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { useIntegrationStore } from "@/entities/integrations"; import { MoreVertical, Pencil, Plus, Trash2 } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { diff --git a/apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx similarity index 98% rename from apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx rename to apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx index 712636a..2414256 100644 --- a/apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx +++ b/apps/client/src/features/dashboard-swipe/ui/DashboardSwipeLayout.tsx @@ -1,10 +1,10 @@ import { Footer } from "@/features/footer"; import { AppSidebar } from "@/features/sidebar"; -import { WebRTCProvider } from "@/features/rtc/WebRTCProvider"; +import { WebRTCProvider } from "@/features/rtc"; import { SidebarInset, SidebarProvider, -} from "@/components/ui/sidebar"; +} from "@/shared/ui/sidebar"; import { DashboardMainPanel, type DashboardMainPanelContentView, @@ -13,9 +13,9 @@ import { DynamicDashboardMainPanel, NewDynamicDashboardPanel, } from "@/pages/dynamic-dashboard"; -import type { DynamicDashboard } from "@/entities/dynamic-dashboard/store"; -import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; +import type { DynamicDashboard } from "@/entities/dynamic-dashboard"; +import { useDynamicDashboardStore } from "@/entities/dynamic-dashboard"; +import { useIntegrationStore } from "@/entities/integrations"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Outlet, useLocation, useNavigate } from "react-router"; import { DashboardSwipeHeader } from "./DashboardSwipeHeader"; diff --git a/apps/client/src/features/device-token/index.ts b/apps/client/src/features/device-token/index.ts new file mode 100644 index 0000000..eaeb931 --- /dev/null +++ b/apps/client/src/features/device-token/index.ts @@ -0,0 +1 @@ +export { DeviceTokenManager } from "./ui/DeviceTokenManager"; diff --git a/apps/client/src/features/device-token/DeviceTokenManager.tsx b/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx similarity index 95% rename from apps/client/src/features/device-token/DeviceTokenManager.tsx rename to apps/client/src/features/device-token/ui/DeviceTokenManager.tsx index 6412ae9..2a6a772 100644 --- a/apps/client/src/features/device-token/DeviceTokenManager.tsx +++ b/apps/client/src/features/device-token/ui/DeviceTokenManager.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Copy, Check, Key, AlertTriangle, RefreshCw } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,7 +8,7 @@ import { DialogTitle, DialogDescription, DialogFooter, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { AlertDialog, AlertDialogAction, @@ -19,9 +19,9 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Badge } from "@/components/ui/badge"; -import { useDeviceTokenStore } from "@/entities/device-token/store"; +} from "@/shared/ui/alert-dialog"; +import { Badge } from "@/shared/ui/badge"; +import { useDeviceTokenStore } from "@/entities/device-token"; interface Props { deviceId: number; diff --git a/apps/client/src/features/device/index.ts b/apps/client/src/features/device/index.ts new file mode 100644 index 0000000..bf5ece4 --- /dev/null +++ b/apps/client/src/features/device/index.ts @@ -0,0 +1,4 @@ +export { DeviceCreateButton } from "./ui/DeviceCreateButton"; +export { DeviceDeleteButton } from "./ui/DeviceDeleteButton"; +export { DeviceKeyButton } from "./ui/DeviceKeyButton"; +export { DeviceUpdateButton } from "./ui/DeviceUpdateButton"; diff --git a/apps/client/src/features/device/DeviceCreateButton.tsx b/apps/client/src/features/device/ui/DeviceCreateButton.tsx similarity index 87% rename from apps/client/src/features/device/DeviceCreateButton.tsx rename to apps/client/src/features/device/ui/DeviceCreateButton.tsx index 0cdc9b8..0cb65a6 100644 --- a/apps/client/src/features/device/DeviceCreateButton.tsx +++ b/apps/client/src/features/device/ui/DeviceCreateButton.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,12 +8,12 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { PlusCircle } from "lucide-react"; -import { DevicePayload } from "@/entities/device/types"; -import { useDeviceStore } from "@/entities/device/store"; +import { DevicePayload } from "@/entities/device"; +import { useDeviceStore } from "@/entities/device"; export function DeviceCreateButton() { const [isOpen, setIsOpen] = useState(false); diff --git a/apps/client/src/features/device/DeviceDeleteButton.tsx b/apps/client/src/features/device/ui/DeviceDeleteButton.tsx similarity index 88% rename from apps/client/src/features/device/DeviceDeleteButton.tsx rename to apps/client/src/features/device/ui/DeviceDeleteButton.tsx index a5d08ed..aa10fa2 100644 --- a/apps/client/src/features/device/DeviceDeleteButton.tsx +++ b/apps/client/src/features/device/ui/DeviceDeleteButton.tsx @@ -9,11 +9,11 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Trash2 } from "lucide-react"; -import { useDeviceStore } from "@/entities/device/store"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { Button } from "@/components/ui/button"; +import { useDeviceStore } from "@/entities/device"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { Button } from "@/shared/ui/button"; interface Props { deviceId: number; diff --git a/apps/client/src/features/device/DeviceKeyButton.tsx b/apps/client/src/features/device/ui/DeviceKeyButton.tsx similarity index 83% rename from apps/client/src/features/device/DeviceKeyButton.tsx rename to apps/client/src/features/device/ui/DeviceKeyButton.tsx index f30b549..d691329 100644 --- a/apps/client/src/features/device/DeviceKeyButton.tsx +++ b/apps/client/src/features/device/ui/DeviceKeyButton.tsx @@ -6,10 +6,10 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { Key } from "lucide-react"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { DeviceTokenManager } from "@/features/device-token/DeviceTokenManager"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { DeviceTokenManager } from "@/features/device-token"; interface Props { deviceId: number; diff --git a/apps/client/src/features/device/DeviceUpdateButton.tsx b/apps/client/src/features/device/ui/DeviceUpdateButton.tsx similarity index 86% rename from apps/client/src/features/device/DeviceUpdateButton.tsx rename to apps/client/src/features/device/ui/DeviceUpdateButton.tsx index 38aac73..99c6c61 100644 --- a/apps/client/src/features/device/DeviceUpdateButton.tsx +++ b/apps/client/src/features/device/ui/DeviceUpdateButton.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,13 +8,13 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Pencil } from "lucide-react"; -import { useDeviceStore } from "@/entities/device/store"; -import { Device, DevicePayload } from "@/entities/device/types"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useDeviceStore } from "@/entities/device"; +import { Device, DevicePayload } from "@/entities/device"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface Props { device: Device; diff --git a/apps/client/src/features/dynamic-dashboard/index.ts b/apps/client/src/features/dynamic-dashboard/index.ts new file mode 100644 index 0000000..65505b9 --- /dev/null +++ b/apps/client/src/features/dynamic-dashboard/index.ts @@ -0,0 +1,5 @@ +export { GroupCanvas } from "./ui/GroupCanvas"; +export { FlowPanel } from "./ui/panels/FlowPanel"; +export { ButtonPanel } from "./ui/panels/ButtonPanel"; +export { MapPanel } from "./ui/panels/MapPanel"; +export * from "./lib/events/dispatcher"; diff --git a/apps/client/src/features/dynamic-dashboard/events/dispatcher.test.ts b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts similarity index 98% rename from apps/client/src/features/dynamic-dashboard/events/dispatcher.test.ts rename to apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts index c1c9b44..f7ccf1e 100644 --- a/apps/client/src/features/dynamic-dashboard/events/dispatcher.test.ts +++ b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { createDashboardEventDispatcher } from "./dispatcher"; -import { DASHBOARD_COMPONENT_EVENT_VERSION } from "@/entities/dynamic-dashboard/interaction"; +import { DASHBOARD_COMPONENT_EVENT_VERSION } from "@/entities/dynamic-dashboard"; describe("createDashboardEventDispatcher", () => { const baseCtx = { diff --git a/apps/client/src/features/dynamic-dashboard/events/dispatcher.ts b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts similarity index 96% rename from apps/client/src/features/dynamic-dashboard/events/dispatcher.ts rename to apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts index 35dd2f3..6041b01 100644 --- a/apps/client/src/features/dynamic-dashboard/events/dispatcher.ts +++ b/apps/client/src/features/dynamic-dashboard/lib/events/dispatcher.ts @@ -3,8 +3,8 @@ import { type DashboardComponentEventPayload, type DashboardComponentAction, type DashboardComponentType, -} from "@/entities/dynamic-dashboard/interaction"; -import type { WebSocketMessage } from "@/features/ws/ws"; +} from "@/entities/dynamic-dashboard"; +import type { WebSocketMessage } from "@/features/ws"; export const DEFAULT_DASHBOARD_COOLDOWN_MS = 320; export const MIN_DASHBOARD_COOLDOWN_MS = 100; diff --git a/apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx b/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx similarity index 97% rename from apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx rename to apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx index 801753e..9d00dab 100644 --- a/apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/GroupCanvas.tsx @@ -1,24 +1,24 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { PointerEvent as ReactPointerEvent } from "react"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { resolveItemPositionOrNull } from "@/entities/dynamic-dashboard/layoutResolve"; +import { Button } from "@/shared/ui/button"; +import { Card } from "@/shared/ui/card"; +import { resolveItemPositionOrNull } from "@/entities/dynamic-dashboard"; import { DashboardGroup, DashboardItem, useDynamicDashboardStore, -} from "@/entities/dynamic-dashboard/store"; -import { EntityAll } from "@/entities/entity/types"; -import { StreamState } from "@/features/entity/useEntitiesData"; -import { EntityCard } from "@/features/entity/Card"; -import { StreamReceiver } from "@/features/rtc/StreamReceiver"; +} from "@/entities/dynamic-dashboard"; +import { EntityAll } from "@/entities/entity"; +import { StreamState } from "@/features/entity"; +import { EntityCard } from "@/features/entity"; +import { StreamReceiver } from "@/features/rtc"; import { Dialog, DialogContent, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { DropdownMenu, DropdownMenuContent, @@ -27,19 +27,19 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dropdown-menu"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Plus, X } from "lucide-react"; -import { useSidebar } from "@/components/ui/sidebar"; -import { useFlowStore } from "@/entities/flow/store"; -import { useMapDataStore } from "@/entities/map/store"; +import { useSidebar } from "@/shared/ui/sidebar"; +import { useFlowStore } from "@/entities/flow"; +import { useMapDataStore } from "@/entities/map"; import { MapPanel } from "./panels/MapPanel"; import { FlowPanel } from "./panels/FlowPanel"; -import { useWebSocket } from "@/features/ws/WebSocketProvider"; -import { getFlowRunSessionId } from "@/features/ws/ws"; -import { createDashboardEventDispatcher } from "./events/dispatcher"; -import { isValidListenerId } from "@/entities/dynamic-dashboard/interaction"; +import { useWebSocket } from "@/features/ws"; +import { getFlowRunSessionId } from "@/features/ws"; +import { createDashboardEventDispatcher } from "../lib/events/dispatcher"; +import { isValidListenerId } from "@/entities/dynamic-dashboard"; const isMapItem = ( candidate: DashboardItem, diff --git a/apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx similarity index 89% rename from apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx rename to apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx index 081acbb..898128c 100644 --- a/apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/panels/ButtonPanel.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import type { DashboardItemDataMap } from "@/entities/dynamic-dashboard/store"; -import { isValidListenerId } from "@/entities/dynamic-dashboard/interaction"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import type { DashboardItemDataMap } from "@/entities/dynamic-dashboard"; +import { isValidListenerId } from "@/entities/dynamic-dashboard"; type ButtonPanelProps = { data?: DashboardItemDataMap["button"]; diff --git a/apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx similarity index 94% rename from apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx rename to apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx index b863637..f5c32f5 100644 --- a/apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/panels/FlowPanel.tsx @@ -1,20 +1,20 @@ import { useEffect, useMemo, useState } from "react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Badge } from "@/shared/ui/badge"; +import { Button } from "@/shared/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useFlowStore } from "@/entities/flow/store"; +} from "@/shared/ui/select"; +import { useFlowStore } from "@/entities/flow"; import { useWebSocket, useWebSocketMessage, -} from "@/features/ws/WebSocketProvider"; -import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws/ws"; -import { DashboardItemDataMap } from "@/entities/dynamic-dashboard/store"; +} from "@/features/ws"; +import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws"; +import { DashboardItemDataMap } from "@/entities/dynamic-dashboard"; import { Play, Square } from "lucide-react"; type FlowPanelProps = { diff --git a/apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx b/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx similarity index 96% rename from apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx rename to apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx index 5008d2c..96642ac 100644 --- a/apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx +++ b/apps/client/src/features/dynamic-dashboard/ui/panels/MapPanel.tsx @@ -1,17 +1,17 @@ import { useEffect, useMemo, useState } from "react"; import { MapContainer, TileLayer, useMap } from "react-leaflet"; import "leaflet/dist/leaflet.css"; -import { Badge } from "@/components/ui/badge"; +import { Badge } from "@/shared/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; -import { FeatureRenderer } from "@/features/map-draw/FeatureRenderer"; -import { DashboardItemDataMap } from "@/entities/dynamic-dashboard/store"; +} from "@/shared/ui/select"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; +import { FeatureRenderer } from "@/features/map-draw"; +import { DashboardItemDataMap } from "@/entities/dynamic-dashboard"; type MapPanelProps = { data?: DashboardItemDataMap["map"]; diff --git a/apps/client/src/features/entity/index.ts b/apps/client/src/features/entity/index.ts new file mode 100644 index 0000000..1a1b94d --- /dev/null +++ b/apps/client/src/features/entity/index.ts @@ -0,0 +1,11 @@ +export { AllEntities } from "./ui/AllEntities"; +export { AnalyzeMenuItem } from "./ui/AnalyzeMenuItem"; +export { EntityCard } from "./ui/Card"; +export { EntityCreateButton } from "./ui/EntityCreateButton"; +export { EntityDeleteButton } from "./ui/EntityDeleteButton"; +export { EntityUpdateButton } from "./ui/EntityUpdateButton"; +export { EntitySelectPlatforms } from "./ui/SelectPlatforms"; +export { EntitySelectTypes } from "./ui/SelectTypes"; +export { StateHistorySheet } from "./ui/StateHistorySheet"; +export { useEntitiesData } from "./model/useEntitiesData"; +export type { StreamState, ChangeStatePayload } from "./model/useEntitiesData"; diff --git a/apps/client/src/features/entity/useEntitiesData.ts b/apps/client/src/features/entity/model/useEntitiesData.ts similarity index 90% rename from apps/client/src/features/entity/useEntitiesData.ts rename to apps/client/src/features/entity/model/useEntitiesData.ts index a72f9b8..54ecbe8 100644 --- a/apps/client/src/features/entity/useEntitiesData.ts +++ b/apps/client/src/features/entity/model/useEntitiesData.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useState } from "react"; -import * as api from "@/entities/entity/api"; -import { EntityAll, State } from "@/entities/entity/types"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import * as api from "@/entities/entity"; +import { EntityAll, State } from "@/entities/entity"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export type StreamState = { topic: string; diff --git a/apps/client/src/features/entity/AllEntities.tsx b/apps/client/src/features/entity/ui/AllEntities.tsx similarity index 83% rename from apps/client/src/features/entity/AllEntities.tsx rename to apps/client/src/features/entity/ui/AllEntities.tsx index fe7c9fb..a279b65 100644 --- a/apps/client/src/features/entity/AllEntities.tsx +++ b/apps/client/src/features/entity/ui/AllEntities.tsx @@ -1,10 +1,10 @@ import { Fragment } from "react"; -// import { EntityAll } from "@/entities/entity/types"; +// import { EntityAll } from "@/entities/entity"; import { EntityCard } from "./Card"; import { // StreamState, useEntitiesData, -} from "@/features/entity/useEntitiesData"; +} from "@/features/entity"; export function AllEntities() { const { entities, streamsState } = useEntitiesData(); diff --git a/apps/client/src/features/entity/AnalyzeMenuItem.tsx b/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx similarity index 83% rename from apps/client/src/features/entity/AnalyzeMenuItem.tsx rename to apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx index 48cf25f..817684f 100644 --- a/apps/client/src/features/entity/AnalyzeMenuItem.tsx +++ b/apps/client/src/features/entity/ui/AnalyzeMenuItem.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { Eye, Loader2 } from "lucide-react"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { useWebRTC } from "../rtc/WebRTCProvider"; -import { captureFrameFromStream } from "../rtc/captureFrame"; -import { useChatStore } from "../llm-chat/store"; -import { cn } from "@/lib/utils"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { useWebRTC } from "../../rtc"; +import { captureFrameFromStream } from "../../rtc"; +import { useChatStore } from "../../llm-chat"; +import { cn } from "@/shared/lib/utils"; interface AnalyzeMenuItemProps { topic: string; diff --git a/apps/client/src/features/entity/Card.tsx b/apps/client/src/features/entity/ui/Card.tsx similarity index 95% rename from apps/client/src/features/entity/Card.tsx rename to apps/client/src/features/entity/ui/Card.tsx index 64ec3e4..716244c 100644 --- a/apps/client/src/features/entity/Card.tsx +++ b/apps/client/src/features/entity/ui/Card.tsx @@ -1,22 +1,22 @@ import { useState } from "react"; -import { EntityAll } from "@/entities/entity/types"; +import { EntityAll } from "@/entities/entity"; import { Card, CardHeader, CardDescription, CardTitle, CardFooter, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Button } from "@/components/ui/button"; +} from "@/shared/ui/dropdown-menu"; +import { Button } from "@/shared/ui/button"; import { MoreVertical } from "lucide-react"; -import { formatSimpleDateTime } from "@/lib/time"; -import { StreamReceiver } from "../rtc/StreamReceiver"; -import { RecordingMenuItem } from "../recording/RecordingButton"; +import { formatSimpleDateTime } from "@/shared/lib/time"; +import { StreamReceiver } from "../../rtc"; +import { RecordingMenuItem } from "../../recording"; import { AnalyzeMenuItem } from "./AnalyzeMenuItem"; import { StateHistorySheet } from "./StateHistorySheet"; import { useNavigate } from "react-router"; diff --git a/apps/client/src/features/entity/EntityCreateButton.tsx b/apps/client/src/features/entity/ui/EntityCreateButton.tsx similarity index 91% rename from apps/client/src/features/entity/EntityCreateButton.tsx rename to apps/client/src/features/entity/ui/EntityCreateButton.tsx index 84a582c..b680ecf 100644 --- a/apps/client/src/features/entity/EntityCreateButton.tsx +++ b/apps/client/src/features/entity/ui/EntityCreateButton.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -9,24 +9,24 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { AlertCircleIcon, PlusCircle } from "lucide-react"; -import { useDeviceStore } from "@/entities/device/store"; -import { useEntityStore } from "@/entities/entity/store"; -import { EntityPayload } from "@/entities/entity/types"; +import { useDeviceStore } from "@/entities/device"; +import { useEntityStore } from "@/entities/entity"; +import { EntityPayload } from "@/entities/entity"; import { Select, SelectContent, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { JsonCodeEditor } from "../json/JsonEditor"; +} from "@/shared/ui/select"; +import { JsonCodeEditor } from "../../json"; import { EntitySelectTypes } from "./SelectTypes"; import { EntitySelectPlatforms } from "./SelectPlatforms"; import { toast } from "sonner"; -import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; +import { Alert, AlertTitle, AlertDescription } from "@/shared/ui/alert"; export function EntityCreateButton() { const [isOpen, setIsOpen] = useState(false); diff --git a/apps/client/src/features/entity/EntityDeleteButton.tsx b/apps/client/src/features/entity/ui/EntityDeleteButton.tsx similarity index 91% rename from apps/client/src/features/entity/EntityDeleteButton.tsx rename to apps/client/src/features/entity/ui/EntityDeleteButton.tsx index d647b9c..eea3399 100644 --- a/apps/client/src/features/entity/EntityDeleteButton.tsx +++ b/apps/client/src/features/entity/ui/EntityDeleteButton.tsx @@ -9,10 +9,10 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Trash2 } from "lucide-react"; -import { useEntityStore } from "@/entities/entity/store"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useEntityStore } from "@/entities/entity"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface Props { entityId: number; diff --git a/apps/client/src/features/entity/EntityUpdateButton.tsx b/apps/client/src/features/entity/ui/EntityUpdateButton.tsx similarity index 91% rename from apps/client/src/features/entity/EntityUpdateButton.tsx rename to apps/client/src/features/entity/ui/EntityUpdateButton.tsx index 77fc527..de363fc 100644 --- a/apps/client/src/features/entity/EntityUpdateButton.tsx +++ b/apps/client/src/features/entity/ui/EntityUpdateButton.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useForm, Controller } from "react-hook-form"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,24 +8,24 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Select, SelectContent, SelectTrigger, SelectValue, -} from "@/components/ui/select"; +} from "@/shared/ui/select"; import { AlertCircleIcon, Pencil } from "lucide-react"; -import { useEntityStore } from "@/entities/entity/store"; -import { Entity, EntityPayload } from "@/entities/entity/types"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { JsonCodeEditor } from "../json/JsonEditor"; +import { useEntityStore } from "@/entities/entity"; +import { Entity, EntityPayload } from "@/entities/entity"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; +import { JsonCodeEditor } from "../../json"; import { EntitySelectTypes } from "./SelectTypes"; import { EntitySelectPlatforms } from "./SelectPlatforms"; import { toast } from "sonner"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; interface Props { entity: Entity; diff --git a/apps/client/src/features/entity/SelectPlatforms.tsx b/apps/client/src/features/entity/ui/SelectPlatforms.tsx similarity index 84% rename from apps/client/src/features/entity/SelectPlatforms.tsx rename to apps/client/src/features/entity/ui/SelectPlatforms.tsx index 8d28092..8baaba7 100644 --- a/apps/client/src/features/entity/SelectPlatforms.tsx +++ b/apps/client/src/features/entity/ui/SelectPlatforms.tsx @@ -1,4 +1,4 @@ -import { SelectItem } from "@/components/ui/select"; +import { SelectItem } from "@/shared/ui/select"; export function EntitySelectPlatforms() { return ( diff --git a/apps/client/src/features/entity/SelectTypes.tsx b/apps/client/src/features/entity/ui/SelectTypes.tsx similarity index 90% rename from apps/client/src/features/entity/SelectTypes.tsx rename to apps/client/src/features/entity/ui/SelectTypes.tsx index 98e9df2..b7b099f 100644 --- a/apps/client/src/features/entity/SelectTypes.tsx +++ b/apps/client/src/features/entity/ui/SelectTypes.tsx @@ -1,4 +1,4 @@ -import { SelectItem } from "@/components/ui/select"; +import { SelectItem } from "@/shared/ui/select"; import { Baseline, Database, Locate, Play } from "lucide-react"; export function EntitySelectTypes() { diff --git a/apps/client/src/features/entity/StateHistorySheet.tsx b/apps/client/src/features/entity/ui/StateHistorySheet.tsx similarity index 96% rename from apps/client/src/features/entity/StateHistorySheet.tsx rename to apps/client/src/features/entity/ui/StateHistorySheet.tsx index b644eed..98a2793 100644 --- a/apps/client/src/features/entity/StateHistorySheet.tsx +++ b/apps/client/src/features/entity/ui/StateHistorySheet.tsx @@ -6,18 +6,18 @@ import { SheetDescription, SheetHeader, SheetTitle, -} from "@/components/ui/sheet"; -import { ScrollArea } from "@/components/ui/scroll-area"; +} from "@/shared/ui/sheet"; +import { ScrollArea } from "@/shared/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Skeleton } from "@/components/ui/skeleton"; -import { formatSimpleDateTime } from "@/lib/time"; -import * as api from "@/entities/entity/api"; -import type { State } from "@/entities/entity/types"; +} from "@/shared/ui/tooltip"; +import { Skeleton } from "@/shared/ui/skeleton"; +import { formatSimpleDateTime } from "@/shared/lib/time"; +import * as api from "@/entities/entity"; +import type { State } from "@/entities/entity"; type Props = { entityId: string; diff --git a/apps/client/src/features/error/index.ts b/apps/client/src/features/error/index.ts new file mode 100644 index 0000000..fe8046f --- /dev/null +++ b/apps/client/src/features/error/index.ts @@ -0,0 +1 @@ +export { ErrorRender } from "./ui/ErrorRender"; diff --git a/apps/client/src/features/error/index.tsx b/apps/client/src/features/error/ui/ErrorRender.tsx similarity index 77% rename from apps/client/src/features/error/index.tsx rename to apps/client/src/features/error/ui/ErrorRender.tsx index 49a0ce5..477ded5 100644 --- a/apps/client/src/features/error/index.tsx +++ b/apps/client/src/features/error/ui/ErrorRender.tsx @@ -1,10 +1,10 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; export function ErrorRender() { return ( -
+
-

+

ERR

diff --git a/apps/client/src/features/flow-log/index.ts b/apps/client/src/features/flow-log/index.ts new file mode 100644 index 0000000..6dd2a0a --- /dev/null +++ b/apps/client/src/features/flow-log/index.ts @@ -0,0 +1 @@ +export { FlowLog } from "./ui/FlowLog"; diff --git a/apps/client/src/features/flow-log/FlowLog.tsx b/apps/client/src/features/flow-log/ui/FlowLog.tsx similarity index 96% rename from apps/client/src/features/flow-log/FlowLog.tsx rename to apps/client/src/features/flow-log/ui/FlowLog.tsx index 1625684..a5f0be2 100644 --- a/apps/client/src/features/flow-log/FlowLog.tsx +++ b/apps/client/src/features/flow-log/ui/FlowLog.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useCallback } from "react"; import { X, ChevronDown, ChevronUp, Terminal } from "lucide-react"; -import { useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import { useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export function FlowLog() { const [logMessages, setLogMessages] = useState([]); diff --git a/apps/client/src/features/flow/index.ts b/apps/client/src/features/flow/index.ts new file mode 100644 index 0000000..1f11a09 --- /dev/null +++ b/apps/client/src/features/flow/index.ts @@ -0,0 +1,10 @@ +export { default, FlowHeader, FlowSidebar } from "./ui/Flow"; +export { Graph } from "./ui/Graph"; +export { Options } from "./ui/Options"; +export { AddCustomNode } from "./ui/AddCustomNode"; +export { RunFlowButton } from "./ui/RunFlow"; +export { SelectedItemActions } from "./ui/SelectedItemActions"; +export * from "./model/types"; +export { DEFINITION_NODE } from "./lib/flowNode"; +export * from "./lib/flowUtils"; +export * from "./lib/flow-chat"; diff --git a/apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts b/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts similarity index 94% rename from apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts rename to apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts index 2b2560e..5eeb2fe 100644 --- a/apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts +++ b/apps/client/src/features/flow/lib/flow-chat/buildSystemPrompt.ts @@ -1,6 +1,6 @@ -import type { Node, Edge } from "@/features/flow/flowTypes"; -import type { Flow } from "@/entities/flow/types"; -import { DEFINITION_NODE } from "@/features/flow/flowNode"; +import type { Node, Edge } from "../../model/types"; +import type { Flow } from "@/entities/flow"; +import { DEFINITION_NODE } from "../flowNode"; export function buildFlowSystemPrompt( currentNodes: Node[], diff --git a/apps/client/src/features/flow/flow-chat/executeToolCalls.ts b/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts similarity index 98% rename from apps/client/src/features/flow/flow-chat/executeToolCalls.ts rename to apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts index 1cae51a..56427d1 100644 --- a/apps/client/src/features/flow/flow-chat/executeToolCalls.ts +++ b/apps/client/src/features/flow/lib/flow-chat/executeToolCalls.ts @@ -1,5 +1,5 @@ import type { ToolCallResult } from "@vessel/capsule-client"; -import type { Node, Edge, DataNodeType } from "@/features/flow/flowTypes"; +import type { Node, Edge, DataNodeType } from "../../model/types"; export interface ToolExecutionResult { toolCallId: string; diff --git a/apps/client/src/features/flow/flow-chat/flowTools.ts b/apps/client/src/features/flow/lib/flow-chat/flowTools.ts similarity index 100% rename from apps/client/src/features/flow/flow-chat/flowTools.ts rename to apps/client/src/features/flow/lib/flow-chat/flowTools.ts diff --git a/apps/client/src/features/flow/flow-chat/index.ts b/apps/client/src/features/flow/lib/flow-chat/index.ts similarity index 100% rename from apps/client/src/features/flow/flow-chat/index.ts rename to apps/client/src/features/flow/lib/flow-chat/index.ts diff --git a/apps/client/src/features/flow/flowNode.ts b/apps/client/src/features/flow/lib/flowNode.ts similarity index 99% rename from apps/client/src/features/flow/flowNode.ts rename to apps/client/src/features/flow/lib/flowNode.ts index f7ddc43..360abcb 100644 --- a/apps/client/src/features/flow/flowNode.ts +++ b/apps/client/src/features/flow/lib/flowNode.ts @@ -1,4 +1,4 @@ -import { Node } from "./flowTypes"; +import { Node } from "../model/types"; export type DefaultValueType = { connectors: Node["connectors"]; diff --git a/apps/client/src/features/flow/flowUtils.ts b/apps/client/src/features/flow/lib/flowUtils.ts similarity index 97% rename from apps/client/src/features/flow/flowUtils.ts rename to apps/client/src/features/flow/lib/flowUtils.ts index d03183f..b7fc1aa 100644 --- a/apps/client/src/features/flow/flowUtils.ts +++ b/apps/client/src/features/flow/lib/flowUtils.ts @@ -1,6 +1,6 @@ -import { CustomNode, CustomNodeDynamicData } from "@/entities/custom-nodes/types"; +import { CustomNode, CustomNodeDynamicData } from "@/entities/custom-nodes"; import { DEFINITION_NODE } from "./flowNode"; -import { NodeTypes, Node, DataNodeTypeType } from "./flowTypes"; +import { NodeTypes, Node, DataNodeTypeType } from "../model/types"; export function getDefalutValue(type: NodeTypes, id: string) { const value = JSON.parse(JSON.stringify(DEFINITION_NODE[type])); diff --git a/apps/client/src/features/flow/flowTypes.ts b/apps/client/src/features/flow/model/types.ts similarity index 98% rename from apps/client/src/features/flow/flowTypes.ts rename to apps/client/src/features/flow/model/types.ts index 92500c6..9201cef 100644 --- a/apps/client/src/features/flow/flowTypes.ts +++ b/apps/client/src/features/flow/model/types.ts @@ -1,4 +1,4 @@ -import { DEFINITION_NODE } from "./flowNode"; +import { DEFINITION_NODE } from "../lib/flowNode"; export type Connector = { id: string; diff --git a/apps/client/src/features/flow/AddCustomNode.tsx b/apps/client/src/features/flow/ui/AddCustomNode.tsx similarity index 96% rename from apps/client/src/features/flow/AddCustomNode.tsx rename to apps/client/src/features/flow/ui/AddCustomNode.tsx index a4325be..0bde32c 100644 --- a/apps/client/src/features/flow/AddCustomNode.tsx +++ b/apps/client/src/features/flow/ui/AddCustomNode.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,7 +8,7 @@ import { DialogTitle, DialogTrigger, DialogFooter, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { AlertDialog, AlertDialogAction, @@ -18,10 +18,10 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; +} from "@/shared/ui/alert-dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { Badge } from "@/shared/ui/badge"; import { Blocks, Edit, @@ -30,12 +30,12 @@ import { Loader2, Download, } from "lucide-react"; -import { useCustomNodeStore } from "@/entities/custom-nodes/store"; +import { useCustomNodeStore } from "@/entities/custom-nodes"; import { CustomNode, CustomNodeDynamicData, CustomNodeFromApi, -} from "@/entities/custom-nodes/types"; +} from "@/entities/custom-nodes"; import { RHAI_PRESETS, getPresetCategories, @@ -43,9 +43,9 @@ import { presetToApiPayload, type RhaiPreset, type PresetCategory, -} from "@/entities/custom-nodes/presets"; +} from "@/entities/custom-nodes"; import { toast } from "sonner"; -import { JsonCodeEditor } from "../json/JsonEditor"; +import { JsonCodeEditor } from "../../json"; function CustomNodeForm({ onSubmit, diff --git a/apps/client/src/features/flow/Flow.tsx b/apps/client/src/features/flow/ui/Flow.tsx similarity index 95% rename from apps/client/src/features/flow/Flow.tsx rename to apps/client/src/features/flow/ui/Flow.tsx index 438453d..b51e283 100644 --- a/apps/client/src/features/flow/Flow.tsx +++ b/apps/client/src/features/flow/ui/Flow.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Graph } from "./Graph"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, @@ -11,19 +11,19 @@ import { DialogTitle, DialogTrigger, DialogClose, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { MoreHorizontal, Plus, Trash2 } from "lucide-react"; import { File } from "lucide-react"; -import { deleteFlow } from "@/entities/flow/api"; -import { Flow } from "@/entities/flow/types"; +import { deleteFlow } from "@/entities/flow"; +import { Flow } from "@/entities/flow"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { AlertDialogHeader, AlertDialogFooter, @@ -33,10 +33,10 @@ import { AlertDialogContent, AlertDialogDescription, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { useFlowStore } from "@/entities/flow/store"; +} from "@/shared/ui/alert-dialog"; +import { useFlowStore } from "@/entities/flow"; import { RunFlowButton } from "./RunFlow"; -import { FlowLog } from "../flow-log/FlowLog"; +import { FlowLog } from "../../flow-log"; export default function FlowPage() { const { diff --git a/apps/client/src/features/flow/Graph.tsx b/apps/client/src/features/flow/ui/Graph.tsx similarity index 98% rename from apps/client/src/features/flow/Graph.tsx rename to apps/client/src/features/flow/ui/Graph.tsx index 2d4a048..e197e93 100644 --- a/apps/client/src/features/flow/Graph.tsx +++ b/apps/client/src/features/flow/ui/Graph.tsx @@ -1,7 +1,7 @@ import { useState, useRef, useEffect, useCallback, useMemo } from "react"; import * as d3 from "d3"; import { Plus, Minus, Lock, LockOpen, SquarePlus } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { GraphProps, Node, @@ -9,7 +9,7 @@ import { Connector, NodeRenderer, NodeTypes, -} from "./flowTypes"; +} from "../model/types"; // import { renderButtonNode } from "./nodes/ButtonNode"; import { renderTitleNode } from "./nodes/TitleNode"; @@ -23,21 +23,21 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; import { renderProcessingNode } from "./nodes/ProcessingNode"; -import { getCustomNode, getDefalutNode } from "./flowUtils"; +import { getCustomNode, getDefalutNode } from "../lib/flowUtils"; import { renderVarNode } from "./nodes/VarNode"; import { renderCalcNode } from "./nodes/CalcNode"; import { renderHttpNode } from "./nodes/HttpNode"; -import { formatConstantCase } from "@/lib/string"; +import { formatConstantCase } from "@/shared/lib/string"; import { renderLogicNode } from "./nodes/LogicNode"; import { renderIntervalNode } from "./nodes/IntervalNode"; import { renderMQTTNode } from "./nodes/MQTTNode"; import { renderButtonNode } from "./nodes/ButtonNode"; import { zoomIdentity } from "d3-zoom"; import { AddCustomNode } from "./AddCustomNode"; -import { useCustomNodeStore } from "@/entities/custom-nodes/store"; -import { useIntegrationStore } from "@/entities/integrations/store"; +import { useCustomNodeStore } from "@/entities/custom-nodes"; +import { useIntegrationStore } from "@/entities/integrations"; import { SelectedItemActions } from "./SelectedItemActions"; type NodeGroup = { diff --git a/apps/client/src/features/flow/Options.tsx b/apps/client/src/features/flow/ui/Options.tsx similarity index 96% rename from apps/client/src/features/flow/Options.tsx rename to apps/client/src/features/flow/ui/Options.tsx index d5ad0f3..f8fd5ea 100644 --- a/apps/client/src/features/flow/Options.tsx +++ b/apps/client/src/features/flow/ui/Options.tsx @@ -1,14 +1,14 @@ import { useState, useEffect, useCallback } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; +} from "@/shared/ui/select"; import { Sheet, SheetContent, @@ -16,9 +16,9 @@ import { SheetFooter, SheetHeader, SheetTitle, -} from "@/components/ui/sheet"; -import { DataNodeType, DataNodeTypeType, Node } from "./flowTypes"; -import { useFlowStore } from "@/entities/flow/store"; +} from "@/shared/ui/sheet"; +import { DataNodeType, DataNodeTypeType, Node } from "../model/types"; +import { useFlowStore } from "@/entities/flow"; interface OptionsProps { open: boolean; diff --git a/apps/client/src/features/flow/OptionsVariation.tsx b/apps/client/src/features/flow/ui/OptionsVariation.tsx similarity index 100% rename from apps/client/src/features/flow/OptionsVariation.tsx rename to apps/client/src/features/flow/ui/OptionsVariation.tsx diff --git a/apps/client/src/features/flow/RunFlow.tsx b/apps/client/src/features/flow/ui/RunFlow.tsx similarity index 95% rename from apps/client/src/features/flow/RunFlow.tsx rename to apps/client/src/features/flow/ui/RunFlow.tsx index a9ceb03..ce7422e 100644 --- a/apps/client/src/features/flow/RunFlow.tsx +++ b/apps/client/src/features/flow/ui/RunFlow.tsx @@ -1,10 +1,10 @@ -import { Button } from "@/components/ui/button"; -import { useFlowStore } from "@/entities/flow/store"; +import { Button } from "@/shared/ui/button"; +import { useFlowStore } from "@/entities/flow"; import { Play, Square } from "lucide-react"; import { toast } from "sonner"; -import { useWebSocket, useWebSocketMessage } from "@/features/ws/WebSocketProvider"; +import { useWebSocket, useWebSocketMessage } from "@/features/ws"; import { useCallback, useEffect, useState } from "react"; -import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws/ws"; +import { getFlowRunSessionId, WebSocketMessage } from "@/features/ws"; export function RunFlowButton() { const { wsManager } = useWebSocket(); diff --git a/apps/client/src/features/flow/SelectedItemActions.tsx b/apps/client/src/features/flow/ui/SelectedItemActions.tsx similarity index 94% rename from apps/client/src/features/flow/SelectedItemActions.tsx rename to apps/client/src/features/flow/ui/SelectedItemActions.tsx index 509319a..0dd5c4d 100644 --- a/apps/client/src/features/flow/SelectedItemActions.tsx +++ b/apps/client/src/features/flow/ui/SelectedItemActions.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Trash2 } from "lucide-react"; type SelectedElement = { diff --git a/apps/client/src/features/flow/nodes/ButtonNode.tsx b/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx similarity index 94% rename from apps/client/src/features/flow/nodes/ButtonNode.tsx rename to apps/client/src/features/flow/ui/nodes/ButtonNode.tsx index f6826d9..7db9463 100644 --- a/apps/client/src/features/flow/nodes/ButtonNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/ButtonNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderButtonNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/CalcNode.tsx b/apps/client/src/features/flow/ui/nodes/CalcNode.tsx similarity index 92% rename from apps/client/src/features/flow/nodes/CalcNode.tsx rename to apps/client/src/features/flow/ui/nodes/CalcNode.tsx index 0d391a1..a9a432c 100644 --- a/apps/client/src/features/flow/nodes/CalcNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/CalcNode.tsx @@ -1,4 +1,4 @@ -import { CalculationNodeType, Node } from "../flowTypes"; +import { CalculationNodeType, Node } from "../../model/types"; export function renderCalcNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/HttpNode.tsx b/apps/client/src/features/flow/ui/nodes/HttpNode.tsx similarity index 92% rename from apps/client/src/features/flow/nodes/HttpNode.tsx rename to apps/client/src/features/flow/ui/nodes/HttpNode.tsx index e052daf..647f925 100644 --- a/apps/client/src/features/flow/nodes/HttpNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/HttpNode.tsx @@ -1,4 +1,4 @@ -import { HTTPRequestNodeType, Node } from "../flowTypes"; +import { HTTPRequestNodeType, Node } from "../../model/types"; export function renderHttpNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/IntervalNode.tsx b/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx similarity index 93% rename from apps/client/src/features/flow/nodes/IntervalNode.tsx rename to apps/client/src/features/flow/ui/nodes/IntervalNode.tsx index 6d13048..baa8977 100644 --- a/apps/client/src/features/flow/nodes/IntervalNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/IntervalNode.tsx @@ -1,4 +1,4 @@ -import { IntervalNodeType, Node } from "../flowTypes"; +import { IntervalNodeType, Node } from "../../model/types"; export function renderIntervalNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/LogicNode.tsx b/apps/client/src/features/flow/ui/nodes/LogicNode.tsx similarity index 92% rename from apps/client/src/features/flow/nodes/LogicNode.tsx rename to apps/client/src/features/flow/ui/nodes/LogicNode.tsx index 4560b24..63ccffc 100644 --- a/apps/client/src/features/flow/nodes/LogicNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/LogicNode.tsx @@ -1,4 +1,4 @@ -import { LogicOpetatorNodeType, Node } from "../flowTypes"; +import { LogicOpetatorNodeType, Node } from "../../model/types"; export function renderLogicNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/LoopNode.tsx b/apps/client/src/features/flow/ui/nodes/LoopNode.tsx similarity index 93% rename from apps/client/src/features/flow/nodes/LoopNode.tsx rename to apps/client/src/features/flow/ui/nodes/LoopNode.tsx index 61dff9e..2edafe2 100644 --- a/apps/client/src/features/flow/nodes/LoopNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/LoopNode.tsx @@ -1,4 +1,4 @@ -import { LoopNodeType, Node } from "../flowTypes"; +import { LoopNodeType, Node } from "../../model/types"; export function renderLoopNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/MQTTNode.tsx b/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx similarity index 94% rename from apps/client/src/features/flow/nodes/MQTTNode.tsx rename to apps/client/src/features/flow/ui/nodes/MQTTNode.tsx index d2d1e5b..512e9a5 100644 --- a/apps/client/src/features/flow/nodes/MQTTNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/MQTTNode.tsx @@ -1,4 +1,4 @@ -import { MqttPublishNodeType, Node } from "../flowTypes"; +import { MqttPublishNodeType, Node } from "../../model/types"; function mqttLikeCenterLabel(d: Node): string { if (d.nodeType === "DASHBOARD_EVENT_LISTENER") { diff --git a/apps/client/src/features/flow/nodes/NumberNode.tsx b/apps/client/src/features/flow/ui/nodes/NumberNode.tsx similarity index 93% rename from apps/client/src/features/flow/nodes/NumberNode.tsx rename to apps/client/src/features/flow/ui/nodes/NumberNode.tsx index fb839ea..b2d272c 100644 --- a/apps/client/src/features/flow/nodes/NumberNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/NumberNode.tsx @@ -1,4 +1,4 @@ -import { Node, NumberNodeType } from "../flowTypes"; +import { Node, NumberNodeType } from "../../model/types"; export function renderNumberNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/ProcessingNode.tsx b/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx similarity index 90% rename from apps/client/src/features/flow/nodes/ProcessingNode.tsx rename to apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx index db82d8e..9b08294 100644 --- a/apps/client/src/features/flow/nodes/ProcessingNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/ProcessingNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderProcessingNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/TitleNode.tsx b/apps/client/src/features/flow/ui/nodes/TitleNode.tsx similarity index 90% rename from apps/client/src/features/flow/nodes/TitleNode.tsx rename to apps/client/src/features/flow/ui/nodes/TitleNode.tsx index 7db174c..3f43714 100644 --- a/apps/client/src/features/flow/nodes/TitleNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/TitleNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderTitleNode( g: d3.Selection, diff --git a/apps/client/src/features/flow/nodes/VarNode.tsx b/apps/client/src/features/flow/ui/nodes/VarNode.tsx similarity index 94% rename from apps/client/src/features/flow/nodes/VarNode.tsx rename to apps/client/src/features/flow/ui/nodes/VarNode.tsx index a6d4446..a7c3cd7 100644 --- a/apps/client/src/features/flow/nodes/VarNode.tsx +++ b/apps/client/src/features/flow/ui/nodes/VarNode.tsx @@ -1,4 +1,4 @@ -import { Node } from "../flowTypes"; +import { Node } from "../../model/types"; export function renderVarNode( g: d3.Selection, diff --git a/apps/client/src/features/footer/index.ts b/apps/client/src/features/footer/index.ts new file mode 100644 index 0000000..079c661 --- /dev/null +++ b/apps/client/src/features/footer/index.ts @@ -0,0 +1 @@ +export { Footer } from "./ui/Footer"; diff --git a/apps/client/src/features/footer/index.tsx b/apps/client/src/features/footer/ui/Footer.tsx similarity index 88% rename from apps/client/src/features/footer/index.tsx rename to apps/client/src/features/footer/ui/Footer.tsx index e9622ca..4f10034 100644 --- a/apps/client/src/features/footer/index.tsx +++ b/apps/client/src/features/footer/ui/Footer.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; export function Footer() { const { wsManager } = useWebSocket(); @@ -50,10 +50,10 @@ export function Footer() { ); } -import { WebSocketStatusIndicator } from "../ws/IsConnected"; -import { parseJwt } from "@/lib/jwt"; -import { isDemoMode } from "@/shared/demo"; -import { storage } from "@/lib/storage"; +import { WebSocketStatusIndicator } from "../../ws"; +import { parseJwt } from "@/shared/lib/jwt"; +import { isDemoMode } from "@/shared/config/demo"; +import { storage } from "@/shared/lib/storage"; const TokenExpiration: React.FC = () => { const [timeLeft, setTimeLeft] = useState(""); diff --git a/apps/client/src/features/gps/index.ts b/apps/client/src/features/gps/index.ts new file mode 100644 index 0000000..86fd2b4 --- /dev/null +++ b/apps/client/src/features/gps/index.ts @@ -0,0 +1 @@ +export * from "./lib/parseGps"; diff --git a/apps/client/src/features/gps/parseGps.ts b/apps/client/src/features/gps/lib/parseGps.ts similarity index 100% rename from apps/client/src/features/gps/parseGps.ts rename to apps/client/src/features/gps/lib/parseGps.ts diff --git a/apps/client/src/features/ha/index.ts b/apps/client/src/features/ha/index.ts new file mode 100644 index 0000000..6ca171f --- /dev/null +++ b/apps/client/src/features/ha/index.ts @@ -0,0 +1 @@ +export { HaDashboard } from "./ui/HaDashboard"; diff --git a/apps/client/src/features/ha/index.tsx b/apps/client/src/features/ha/ui/HaDashboard.tsx similarity index 91% rename from apps/client/src/features/ha/index.tsx rename to apps/client/src/features/ha/ui/HaDashboard.tsx index e1452a1..b5bdfdb 100644 --- a/apps/client/src/features/ha/index.tsx +++ b/apps/client/src/features/ha/ui/HaDashboard.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from "react"; -import { useHaStore } from "@/entities/ha/store"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { useHaStore } from "@/entities/ha"; +import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; import { Loader2, AlertTriangle } from "lucide-react"; import { HaStatBlock } from "./HaStatBlock"; import { HaEntitiesTable } from "./HaEntitiesTable"; diff --git a/apps/client/src/features/ha/HaEntitiesTable.tsx b/apps/client/src/features/ha/ui/HaEntitiesTable.tsx similarity index 92% rename from apps/client/src/features/ha/HaEntitiesTable.tsx rename to apps/client/src/features/ha/ui/HaEntitiesTable.tsx index 8478bfd..b7ccfbd 100644 --- a/apps/client/src/features/ha/HaEntitiesTable.tsx +++ b/apps/client/src/features/ha/ui/HaEntitiesTable.tsx @@ -4,7 +4,7 @@ import { CardHeader, CardTitle, CardDescription, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { Table, TableBody, @@ -12,9 +12,9 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { Badge } from "@/components/ui/badge"; -import { HaState } from "@/entities/ha/types"; +} from "@/shared/ui/table"; +import { Badge } from "@/shared/ui/badge"; +import { HaState } from "@/entities/ha"; interface HaEntitiesTableProps { states: HaState[]; diff --git a/apps/client/src/features/ha/HaStatBlock.tsx b/apps/client/src/features/ha/ui/HaStatBlock.tsx similarity index 93% rename from apps/client/src/features/ha/HaStatBlock.tsx rename to apps/client/src/features/ha/ui/HaStatBlock.tsx index 305da91..5134f42 100644 --- a/apps/client/src/features/ha/HaStatBlock.tsx +++ b/apps/client/src/features/ha/ui/HaStatBlock.tsx @@ -1,5 +1,5 @@ -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { HaState } from "@/entities/ha/types"; +import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card"; +import { HaState } from "@/entities/ha"; import { Lightbulb, Power, Router, Thermometer } from "lucide-react"; import React, { useMemo } from "react"; diff --git a/apps/client/src/features/integration/constants.ts b/apps/client/src/features/integration/config/constants.ts similarity index 100% rename from apps/client/src/features/integration/constants.ts rename to apps/client/src/features/integration/config/constants.ts diff --git a/apps/client/src/features/integration/index.ts b/apps/client/src/features/integration/index.ts new file mode 100644 index 0000000..3e3b00a --- /dev/null +++ b/apps/client/src/features/integration/index.ts @@ -0,0 +1,3 @@ +export { Intergration } from "./ui/Integration"; +export * from "./model/types"; +export * from "./config/constants"; diff --git a/apps/client/src/features/integration/types.ts b/apps/client/src/features/integration/model/types.ts similarity index 100% rename from apps/client/src/features/integration/types.ts rename to apps/client/src/features/integration/model/types.ts diff --git a/apps/client/src/features/integration/HA.tsx b/apps/client/src/features/integration/ui/HA.tsx similarity index 90% rename from apps/client/src/features/integration/HA.tsx rename to apps/client/src/features/integration/ui/HA.tsx index 5aad2bb..7e0fee8 100644 --- a/apps/client/src/features/integration/HA.tsx +++ b/apps/client/src/features/integration/ui/HA.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; -import { StepComponentProps } from "./types"; +import { StepComponentProps } from "../model/types"; export const HA_Step1_URL: React.FC = ({ value, diff --git a/apps/client/src/features/integration/Integration.tsx b/apps/client/src/features/integration/ui/Integration.tsx similarity index 98% rename from apps/client/src/features/integration/Integration.tsx rename to apps/client/src/features/integration/ui/Integration.tsx index 74d07e3..79a438b 100644 --- a/apps/client/src/features/integration/Integration.tsx +++ b/apps/client/src/features/integration/ui/Integration.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useMemo } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent, @@ -8,15 +8,15 @@ import { DialogTitle, DialogFooter, DialogDescription, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { Home, Bot, Radio, ArrowRight, CheckCircle, Loader2 } from "lucide-react"; -import { useIntegrationStore } from "@/entities/integrations/store"; +import { useIntegrationStore } from "@/entities/integrations"; import { StepComponentProps, FinalStepProps, IntegrationId, IntegrationWizardModalProps, -} from "./types"; +} from "../model/types"; import { HA_Step1_URL, HA_Step2_Token } from "./HA"; import { ROS2_Step1_Bridge, ROS2_Step2_Address } from "./ROS"; import { SDR_Step1_Info, SDR_Step2_Host, SDR_Step3_Port } from "./SDR"; diff --git a/apps/client/src/features/integration/ROS.tsx b/apps/client/src/features/integration/ui/ROS.tsx similarity index 88% rename from apps/client/src/features/integration/ROS.tsx rename to apps/client/src/features/integration/ui/ROS.tsx index 5003ac7..779d3a6 100644 --- a/apps/client/src/features/integration/ROS.tsx +++ b/apps/client/src/features/integration/ui/ROS.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; -import { StepComponentProps } from "./types"; +import { StepComponentProps } from "../model/types"; export const ROS2_Step1_Bridge: React.FC = () => (
diff --git a/apps/client/src/features/integration/SDR.tsx b/apps/client/src/features/integration/ui/SDR.tsx similarity index 92% rename from apps/client/src/features/integration/SDR.tsx rename to apps/client/src/features/integration/ui/SDR.tsx index c223570..80d20c3 100644 --- a/apps/client/src/features/integration/SDR.tsx +++ b/apps/client/src/features/integration/ui/SDR.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; -import { StepComponentProps } from "./types"; +import { StepComponentProps } from "../model/types"; export const SDR_Step1_Info: React.FC = () => (
diff --git a/apps/client/src/features/json/index.ts b/apps/client/src/features/json/index.ts new file mode 100644 index 0000000..fd9cf4b --- /dev/null +++ b/apps/client/src/features/json/index.ts @@ -0,0 +1 @@ +export { JsonCodeEditor } from "./ui/JsonEditor"; diff --git a/apps/client/src/features/json/JsonEditor.tsx b/apps/client/src/features/json/ui/JsonEditor.tsx similarity index 100% rename from apps/client/src/features/json/JsonEditor.tsx rename to apps/client/src/features/json/ui/JsonEditor.tsx diff --git a/apps/client/src/features/llm-chat/index.ts b/apps/client/src/features/llm-chat/index.ts new file mode 100644 index 0000000..e3e2b21 --- /dev/null +++ b/apps/client/src/features/llm-chat/index.ts @@ -0,0 +1,4 @@ +export { ChatPanelContainer } from "./ui/ChatPanelContainer"; +export { ChatPanel, PANEL_WIDTH } from "./ui/ChatPanel"; +export { useChatStore } from "./model/store"; +export type { ChatMessage, ChatPanelState, FlowChatContext } from "./model/types"; diff --git a/apps/client/src/features/llm-chat/index.tsx b/apps/client/src/features/llm-chat/index.tsx deleted file mode 100644 index 85e73f3..0000000 --- a/apps/client/src/features/llm-chat/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { ChatPanelContainer } from "./ChatPanelContainer"; -export { useChatStore } from "./store"; -export { PANEL_WIDTH } from "./ChatPanel"; -export type { ChatMessage, ChatPanelState, FlowChatContext } from "./types"; diff --git a/apps/client/src/features/llm-chat/store.ts b/apps/client/src/features/llm-chat/model/store.ts similarity index 98% rename from apps/client/src/features/llm-chat/store.ts rename to apps/client/src/features/llm-chat/model/store.ts index 9f1d420..f17c122 100644 --- a/apps/client/src/features/llm-chat/store.ts +++ b/apps/client/src/features/llm-chat/model/store.ts @@ -6,8 +6,8 @@ import { CapsuleRateLimitError, } from "@vessel/capsule-client"; import type { HistoryMessage, ToolCallResult } from "@vessel/capsule-client"; -import { supabase } from "@/lib/supabase"; -import { storage } from "@/lib/storage"; +import { supabase } from "@/shared/lib/supabase"; +import { storage } from "@/shared/lib/storage"; import type { ChatMessage, ChatPanelState } from "./types"; function generateId(): string { diff --git a/apps/client/src/features/llm-chat/types.ts b/apps/client/src/features/llm-chat/model/types.ts similarity index 96% rename from apps/client/src/features/llm-chat/types.ts rename to apps/client/src/features/llm-chat/model/types.ts index 332dec0..c84e753 100644 --- a/apps/client/src/features/llm-chat/types.ts +++ b/apps/client/src/features/llm-chat/model/types.ts @@ -1,5 +1,5 @@ import type { Tool, ToolCallResult } from "@vessel/capsule-client"; -import type { ToolExecutionResult } from "@/features/flow/flow-chat"; +import type { ToolExecutionResult } from "@/features/flow"; export interface ChatMessageImage { /** Object URL for local preview (revoke after use) */ diff --git a/apps/client/src/features/llm-chat/useChatKeyboard.ts b/apps/client/src/features/llm-chat/model/useChatKeyboard.ts similarity index 100% rename from apps/client/src/features/llm-chat/useChatKeyboard.ts rename to apps/client/src/features/llm-chat/model/useChatKeyboard.ts diff --git a/apps/client/src/features/llm-chat/ChatInput.tsx b/apps/client/src/features/llm-chat/ui/ChatInput.tsx similarity index 98% rename from apps/client/src/features/llm-chat/ChatInput.tsx rename to apps/client/src/features/llm-chat/ui/ChatInput.tsx index da2695a..ad37629 100644 --- a/apps/client/src/features/llm-chat/ChatInput.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatInput.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect, useMemo } from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { SendIcon, ImageIcon, XIcon } from "lucide-react"; interface ChatInputProps { diff --git a/apps/client/src/features/llm-chat/ChatMessage.tsx b/apps/client/src/features/llm-chat/ui/ChatMessage.tsx similarity index 95% rename from apps/client/src/features/llm-chat/ChatMessage.tsx rename to apps/client/src/features/llm-chat/ui/ChatMessage.tsx index f6b4992..419802e 100644 --- a/apps/client/src/features/llm-chat/ChatMessage.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatMessage.tsx @@ -1,6 +1,6 @@ -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; import { LoaderCircle } from "lucide-react"; -import type { ChatMessage as ChatMessageType } from "./types"; +import type { ChatMessage as ChatMessageType } from "../model/types"; interface ChatMessageProps { message: ChatMessageType; diff --git a/apps/client/src/features/llm-chat/ChatMessages.tsx b/apps/client/src/features/llm-chat/ui/ChatMessages.tsx similarity index 92% rename from apps/client/src/features/llm-chat/ChatMessages.tsx rename to apps/client/src/features/llm-chat/ui/ChatMessages.tsx index 78b736d..5a6e9d0 100644 --- a/apps/client/src/features/llm-chat/ChatMessages.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatMessages.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { ScrollArea } from "@/shared/ui/scroll-area"; import { ChatMessage } from "./ChatMessage"; -import type { ChatMessage as ChatMessageType } from "./types"; +import type { ChatMessage as ChatMessageType } from "../model/types"; interface ChatMessagesProps { messages: ChatMessageType[]; diff --git a/apps/client/src/features/llm-chat/ChatPanel.tsx b/apps/client/src/features/llm-chat/ui/ChatPanel.tsx similarity index 91% rename from apps/client/src/features/llm-chat/ChatPanel.tsx rename to apps/client/src/features/llm-chat/ui/ChatPanel.tsx index cc40479..4a71012 100644 --- a/apps/client/src/features/llm-chat/ChatPanel.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatPanel.tsx @@ -1,8 +1,8 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { XIcon, TrashIcon } from "lucide-react"; -import { isElectron } from "@/lib/electron"; -import { cn } from "@/lib/utils"; -import { useChatStore } from "./store"; +import { isElectron } from "@/shared/lib/electron"; +import { cn } from "@/shared/lib/utils"; +import { useChatStore } from "../model/store"; import { ChatMessages } from "./ChatMessages"; import { ChatInput } from "./ChatInput"; diff --git a/apps/client/src/features/llm-chat/ChatPanelContainer.tsx b/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx similarity index 75% rename from apps/client/src/features/llm-chat/ChatPanelContainer.tsx rename to apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx index 3a03a4f..9bdc08e 100644 --- a/apps/client/src/features/llm-chat/ChatPanelContainer.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatPanelContainer.tsx @@ -1,6 +1,6 @@ -import { useIsMobile } from "@/hooks/use-mobile"; -import { useChatStore } from "./store"; -import { useChatKeyboard } from "./useChatKeyboard"; +import { useIsMobile } from "@/shared/lib/hooks/use-mobile"; +import { useChatStore } from "../model/store"; +import { useChatKeyboard } from "../model/useChatKeyboard"; import { ChatPanel } from "./ChatPanel"; import { ChatPanelMobile } from "./ChatPanelMobile"; diff --git a/apps/client/src/features/llm-chat/ChatPanelMobile.tsx b/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx similarity index 92% rename from apps/client/src/features/llm-chat/ChatPanelMobile.tsx rename to apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx index b41db02..e1fb440 100644 --- a/apps/client/src/features/llm-chat/ChatPanelMobile.tsx +++ b/apps/client/src/features/llm-chat/ui/ChatPanelMobile.tsx @@ -1,12 +1,12 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, -} from "@/components/ui/sheet"; +} from "@/shared/ui/sheet"; import { TrashIcon, MessageSquareIcon } from "lucide-react"; -import { useChatStore } from "./store"; +import { useChatStore } from "../model/store"; import { ChatMessages } from "./ChatMessages"; import { ChatInput } from "./ChatInput"; diff --git a/apps/client/src/features/log/index.ts b/apps/client/src/features/log/index.ts new file mode 100644 index 0000000..dd175ac --- /dev/null +++ b/apps/client/src/features/log/index.ts @@ -0,0 +1 @@ +export { Logs } from "./ui/Logs"; diff --git a/apps/client/src/features/log/index.tsx b/apps/client/src/features/log/ui/Logs.tsx similarity index 91% rename from apps/client/src/features/log/index.tsx rename to apps/client/src/features/log/ui/Logs.tsx index e7f64b1..3549a2d 100644 --- a/apps/client/src/features/log/index.tsx +++ b/apps/client/src/features/log/ui/Logs.tsx @@ -2,18 +2,18 @@ import { getLatestLog, getLogFileList, getLogByFilename, -} from "@/entities/log/api"; +} from "@/entities/log"; import { useEffect, useState } from "react"; import { ResizablePanelGroup, ResizablePanel, ResizableHandle, -} from "@/components/ui/resizable"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +} from "@/shared/ui/resizable"; +import { ScrollArea } from "@/shared/ui/scroll-area"; +import { Button } from "@/shared/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card"; +import { Skeleton } from "@/shared/ui/skeleton"; +import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; import { Terminal } from "lucide-react"; export function Logs() { diff --git a/apps/client/src/features/map-draw/index.ts b/apps/client/src/features/map-draw/index.ts new file mode 100644 index 0000000..0204633 --- /dev/null +++ b/apps/client/src/features/map-draw/index.ts @@ -0,0 +1,8 @@ +export { FeatureDetailsPanel } from "./ui/FeatureDetailsPanel"; +export { DrawingPreview } from "./ui/FeatureDrawingPreview"; +export { FeatureEditor } from "./ui/FeatureEditor"; +export { FeatureRenderer } from "./ui/FeatureRenderer"; +export { LayerDialog } from "./ui/LayerDialog"; +export { LayerSidebar } from "./ui/LayerSidebar"; +export { MapEvents } from "./ui/MapEvents"; +export { MapToolbar } from "./ui/MapToolbar"; diff --git a/apps/client/src/features/map-draw/FeatureDetailsPanel.tsx b/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx similarity index 96% rename from apps/client/src/features/map-draw/FeatureDetailsPanel.tsx rename to apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx index 6858589..29660d7 100644 --- a/apps/client/src/features/map-draw/FeatureDetailsPanel.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureDetailsPanel.tsx @@ -12,15 +12,15 @@ import { Palette, Minus, } from "lucide-react"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; -import { Button } from "@/components/ui/button"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; +import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { AlertDialog, AlertDialogAction, @@ -30,10 +30,10 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { cn } from "@/lib/utils"; -import { MapVertex, UpdateFeaturePayload } from "@/entities/map/types"; -import { calculateFeatureGeometry } from "@/lib/geometry-precision"; +} from "@/shared/ui/alert-dialog"; +import { cn } from "@/shared/lib/utils"; +import { MapVertex, UpdateFeaturePayload } from "@/entities/map"; +import { calculateFeatureGeometry } from "@/shared/lib/geometry-precision"; const FeatureIcon = ({ type }: { type: string }) => { switch (type) { diff --git a/apps/client/src/features/map-draw/FeatureDrawingPreview.tsx b/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx similarity index 88% rename from apps/client/src/features/map-draw/FeatureDrawingPreview.tsx rename to apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx index 03a7bb3..1236750 100644 --- a/apps/client/src/features/map-draw/FeatureDrawingPreview.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureDrawingPreview.tsx @@ -1,4 +1,4 @@ -import { useMapInteractionStore } from "@/entities/map/store"; +import { useMapInteractionStore } from "@/entities/map"; import { Polyline, Polygon } from "react-leaflet"; export function DrawingPreview() { diff --git a/apps/client/src/features/map-draw/FeatureEditor.tsx b/apps/client/src/features/map-draw/ui/FeatureEditor.tsx similarity index 92% rename from apps/client/src/features/map-draw/FeatureEditor.tsx rename to apps/client/src/features/map-draw/ui/FeatureEditor.tsx index 260f16a..bb31334 100644 --- a/apps/client/src/features/map-draw/FeatureEditor.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureEditor.tsx @@ -1,7 +1,7 @@ import { Marker } from "react-leaflet"; import L from "leaflet"; -import { useMapInteractionStore } from "@/entities/map/store"; -import { useMapDataStore } from "@/entities/map/store"; +import { useMapInteractionStore } from "@/entities/map"; +import { useMapDataStore } from "@/entities/map"; import { LatLng } from "leaflet"; const DraggableIcon = L.divIcon({ diff --git a/apps/client/src/features/map-draw/FeatureRenderer.tsx b/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx similarity index 93% rename from apps/client/src/features/map-draw/FeatureRenderer.tsx rename to apps/client/src/features/map-draw/ui/FeatureRenderer.tsx index 6e596da..d826de1 100644 --- a/apps/client/src/features/map-draw/FeatureRenderer.tsx +++ b/apps/client/src/features/map-draw/ui/FeatureRenderer.tsx @@ -1,7 +1,7 @@ import { CircleMarker, Polygon, Polyline } from "react-leaflet"; import { LatLngExpression } from "leaflet"; -import { FeatureWithVertices } from "@/entities/map/types"; -import { useMapInteractionStore } from "@/entities/map/store"; +import { FeatureWithVertices } from "@/entities/map"; +import { useMapInteractionStore } from "@/entities/map"; import { useMemo } from "react"; interface FeatureRendererProps { diff --git a/apps/client/src/features/map-draw/LayerDialog.tsx b/apps/client/src/features/map-draw/ui/LayerDialog.tsx similarity index 89% rename from apps/client/src/features/map-draw/LayerDialog.tsx rename to apps/client/src/features/map-draw/ui/LayerDialog.tsx index b6a92de..ddf66d6 100644 --- a/apps/client/src/features/map-draw/LayerDialog.tsx +++ b/apps/client/src/features/map-draw/ui/LayerDialog.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { DialogHeader, DialogFooter, @@ -6,11 +6,11 @@ import { Dialog, DialogContent, DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { useMapDataStore } from "@/entities/map/store"; -import { MapLayer, LayerPayload } from "@/entities/map/types"; +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; +import { useMapDataStore } from "@/entities/map"; +import { MapLayer, LayerPayload } from "@/entities/map"; import { useState, useEffect, useCallback } from "react"; interface LayerDialogProps { diff --git a/apps/client/src/features/map-draw/LayerSidebar.tsx b/apps/client/src/features/map-draw/ui/LayerSidebar.tsx similarity index 95% rename from apps/client/src/features/map-draw/LayerSidebar.tsx rename to apps/client/src/features/map-draw/ui/LayerSidebar.tsx index 66885ea..e33b7f3 100644 --- a/apps/client/src/features/map-draw/LayerSidebar.tsx +++ b/apps/client/src/features/map-draw/ui/LayerSidebar.tsx @@ -2,10 +2,10 @@ import { useState } from "react"; import { Plus, ChevronLeft, MoreHorizontal } from "lucide-react"; import { LayerDialog } from "./LayerDialog"; -import { useMapDataStore } from "@/entities/map/store"; -import { MapLayer } from "@/entities/map/types"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { useMapDataStore } from "@/entities/map"; +import { MapLayer } from "@/entities/map"; +import { Button } from "@/shared/ui/button"; +import { cn } from "@/shared/lib/utils"; import { AlertDialog, AlertDialogAction, @@ -15,14 +15,14 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/dropdown-menu"; type LayerDialogState = | { kind: "closed" } diff --git a/apps/client/src/features/map-draw/MapEvents.tsx b/apps/client/src/features/map-draw/ui/MapEvents.tsx similarity index 98% rename from apps/client/src/features/map-draw/MapEvents.tsx rename to apps/client/src/features/map-draw/ui/MapEvents.tsx index 0e94681..f5f2625 100644 --- a/apps/client/src/features/map-draw/MapEvents.tsx +++ b/apps/client/src/features/map-draw/ui/MapEvents.tsx @@ -1,4 +1,4 @@ -import { useMapInteractionStore, useMapDataStore } from "@/entities/map/store"; +import { useMapInteractionStore, useMapDataStore } from "@/entities/map"; import { useMapEvents } from "react-leaflet"; export function MapEvents() { diff --git a/apps/client/src/features/map-draw/MapToolbar.tsx b/apps/client/src/features/map-draw/ui/MapToolbar.tsx similarity index 97% rename from apps/client/src/features/map-draw/MapToolbar.tsx rename to apps/client/src/features/map-draw/ui/MapToolbar.tsx index 69c9702..7ccc133 100644 --- a/apps/client/src/features/map-draw/MapToolbar.tsx +++ b/apps/client/src/features/map-draw/ui/MapToolbar.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/components/ui/button"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; +import { Button } from "@/shared/ui/button"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; import { MapPin, Milestone, Squircle, Check, X, Map, Satellite } from "lucide-react"; export function MapToolbar() { diff --git a/apps/client/src/features/map-entity/index.ts b/apps/client/src/features/map-entity/index.ts new file mode 100644 index 0000000..af1a0a1 --- /dev/null +++ b/apps/client/src/features/map-entity/index.ts @@ -0,0 +1,3 @@ +export { MapEntityRender } from "./ui/render"; +export { EntityDetailsPanel } from "./ui/EntityDetailsPanel"; +export { useMapEntityStore } from "./model/store"; diff --git a/apps/client/src/features/map-entity/store.ts b/apps/client/src/features/map-entity/model/store.ts similarity index 84% rename from apps/client/src/features/map-entity/store.ts rename to apps/client/src/features/map-entity/model/store.ts index 0ac8057..d4365a6 100644 --- a/apps/client/src/features/map-entity/store.ts +++ b/apps/client/src/features/map-entity/model/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import type { EntityAll } from "@/entities/entity/types"; +import type { EntityAll } from "@/entities/entity"; interface MapEntityState { selectedEntity: EntityAll | null; diff --git a/apps/client/src/features/map-entity/EntityDetailsPanel.tsx b/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx similarity index 92% rename from apps/client/src/features/map-entity/EntityDetailsPanel.tsx rename to apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx index ec2705d..b1b7cac 100644 --- a/apps/client/src/features/map-entity/EntityDetailsPanel.tsx +++ b/apps/client/src/features/map-entity/ui/EntityDetailsPanel.tsx @@ -1,22 +1,22 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { X, MapPin, TabletSmartphone, Server, Minus } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { getDeviceById } from "@/entities/device/api"; -import { EntityAll } from "@/entities/entity/types"; -import { EntityCard } from "../entity/Card"; -import { useWebSocket, useWebSocketMessage } from "../ws/WebSocketProvider"; -import { WebSocketMessage } from "../ws/ws"; -import { ChangeStatePayload, StreamState } from "../entity/AllEntities"; -import * as api from "../../entities/entity/api"; -import { useMapEntityStore } from "./store"; -import { cn } from "@/lib/utils"; +import { getDeviceById } from "@/entities/device"; +import { EntityAll } from "@/entities/entity"; +import { EntityCard } from "../../entity"; +import { useWebSocket, useWebSocketMessage } from "../../ws"; +import { WebSocketMessage } from "../../ws"; +import { ChangeStatePayload, StreamState } from "../../entity"; +import * as api from "@/entities/entity"; +import { useMapEntityStore } from "../model/store"; +import { cn } from "@/shared/lib/utils"; interface EntityDetailsPanelProps { isCollapsed: boolean; diff --git a/apps/client/src/features/map-entity/render.tsx b/apps/client/src/features/map-entity/ui/render.tsx similarity index 92% rename from apps/client/src/features/map-entity/render.tsx rename to apps/client/src/features/map-entity/ui/render.tsx index 8ccfebf..42f3422 100644 --- a/apps/client/src/features/map-entity/render.tsx +++ b/apps/client/src/features/map-entity/ui/render.tsx @@ -1,11 +1,11 @@ -import { getAllEntitiesFilter } from "@/entities/entity/api"; -import { EntityAll } from "@/entities/entity/types"; +import { getAllEntitiesFilter } from "@/entities/entity"; +import { EntityAll } from "@/entities/entity"; import L from "leaflet"; import { MapPin } from "lucide-react"; import { useState, useEffect, createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; -import { parseGpsState } from "../gps/parseGps"; -import { useMapEntityStore } from "./store"; +import { parseGpsState } from "../../gps"; +import { useMapEntityStore } from "../model/store"; import { Marker } from "react-leaflet"; const MARKER_W = 32; diff --git a/apps/client/src/features/map/index.ts b/apps/client/src/features/map/index.ts new file mode 100644 index 0000000..53f48b3 --- /dev/null +++ b/apps/client/src/features/map/index.ts @@ -0,0 +1 @@ +export { MapView } from "./ui/MapView"; diff --git a/apps/client/src/features/map/CurrentLocationMarker.tsx b/apps/client/src/features/map/ui/CurrentLocationMarker.tsx similarity index 100% rename from apps/client/src/features/map/CurrentLocationMarker.tsx rename to apps/client/src/features/map/ui/CurrentLocationMarker.tsx diff --git a/apps/client/src/features/map/index.tsx b/apps/client/src/features/map/ui/MapView.tsx similarity index 91% rename from apps/client/src/features/map/index.tsx rename to apps/client/src/features/map/ui/MapView.tsx index a45c638..250db75 100644 --- a/apps/client/src/features/map/index.tsx +++ b/apps/client/src/features/map/ui/MapView.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { Plus, Minus } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { MapContainer, TileLayer, useMap } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; @@ -8,18 +8,18 @@ import L from "leaflet"; import icon from "leaflet/dist/images/marker-icon.png"; import iconShadow from "leaflet/dist/images/marker-shadow.png"; import "./style.css"; -import { useMapDataStore, useMapInteractionStore } from "@/entities/map/store"; -import { TILE_MAPS } from "@/entities/map/types"; - -import { MapEntityRender } from "../map-entity/render"; -import { FeatureDetailsPanel } from "../map-draw/FeatureDetailsPanel"; -import { DrawingPreview } from "../map-draw/FeatureDrawingPreview"; -import { FeatureEditor } from "../map-draw/FeatureEditor"; -import { FeatureRenderer } from "../map-draw/FeatureRenderer"; -import { MapEvents } from "../map-draw/MapEvents"; -import { EntityDetailsPanel } from "../map-entity/EntityDetailsPanel"; -import { useMapEntityStore } from "../map-entity/store"; -import { cn } from "@/lib/utils"; +import { useMapDataStore, useMapInteractionStore } from "@/entities/map"; +import { TILE_MAPS } from "@/entities/map"; + +import { MapEntityRender } from "../../map-entity"; +import { FeatureDetailsPanel } from "../../map-draw"; +import { DrawingPreview } from "../../map-draw"; +import { FeatureEditor } from "../../map-draw"; +import { FeatureRenderer } from "../../map-draw"; +import { MapEvents } from "../../map-draw"; +import { EntityDetailsPanel } from "../../map-entity"; +import { useMapEntityStore } from "../../map-entity"; +import { cn } from "@/shared/lib/utils"; import { MapLastViewTracker, getStoredMapView } from "./MapViewPersistence"; import { CurrentLocationMarker } from "./CurrentLocationMarker"; diff --git a/apps/client/src/features/map/MapViewPersistence.tsx b/apps/client/src/features/map/ui/MapViewPersistence.tsx similarity index 100% rename from apps/client/src/features/map/MapViewPersistence.tsx rename to apps/client/src/features/map/ui/MapViewPersistence.tsx diff --git a/apps/client/src/features/map/style.css b/apps/client/src/features/map/ui/style.css similarity index 100% rename from apps/client/src/features/map/style.css rename to apps/client/src/features/map/ui/style.css diff --git a/apps/client/src/features/recording/index.ts b/apps/client/src/features/recording/index.ts index b5b3595..7e9cf0f 100644 --- a/apps/client/src/features/recording/index.ts +++ b/apps/client/src/features/recording/index.ts @@ -1,3 +1,3 @@ -export { RecordingButton } from "./RecordingButton"; -export { RecordingsList } from "./RecordingsList"; -export { VideoPlaybackDialog } from "./VideoPlaybackDialog"; +export { RecordingButton, RecordingMenuItem } from "./ui/RecordingButton"; +export { RecordingsList } from "./ui/RecordingsList"; +export { VideoPlaybackDialog } from "./ui/VideoPlaybackDialog"; diff --git a/apps/client/src/features/recording/hooks/useAudioWaveform.ts b/apps/client/src/features/recording/model/useAudioWaveform.ts similarity index 100% rename from apps/client/src/features/recording/hooks/useAudioWaveform.ts rename to apps/client/src/features/recording/model/useAudioWaveform.ts diff --git a/apps/client/src/features/recording/hooks/useMediaPlayback.ts b/apps/client/src/features/recording/model/useMediaPlayback.ts similarity index 100% rename from apps/client/src/features/recording/hooks/useMediaPlayback.ts rename to apps/client/src/features/recording/model/useMediaPlayback.ts diff --git a/apps/client/src/features/recording/hooks/useVideoFrames.ts b/apps/client/src/features/recording/model/useVideoFrames.ts similarity index 100% rename from apps/client/src/features/recording/hooks/useVideoFrames.ts rename to apps/client/src/features/recording/model/useVideoFrames.ts diff --git a/apps/client/src/features/recording/components/AudioWaveformPlayer.tsx b/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx similarity index 93% rename from apps/client/src/features/recording/components/AudioWaveformPlayer.tsx rename to apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx index 320e42f..cd14f2a 100644 --- a/apps/client/src/features/recording/components/AudioWaveformPlayer.tsx +++ b/apps/client/src/features/recording/ui/AudioWaveformPlayer.tsx @@ -1,8 +1,8 @@ import { useRef, useState, useEffect, useCallback } from "react"; -import { cn } from "@/lib/utils"; -import { Skeleton } from "@/components/ui/skeleton"; -import { useMediaPlayback } from "../hooks/useMediaPlayback"; -import { useAudioWaveform } from "../hooks/useAudioWaveform"; +import { cn } from "@/shared/lib/utils"; +import { Skeleton } from "@/shared/ui/skeleton"; +import { useMediaPlayback } from "../model/useMediaPlayback"; +import { useAudioWaveform } from "../model/useAudioWaveform"; import { PlaybackControls } from "./PlaybackControls"; import { WaveformCanvas } from "./WaveformCanvas"; import { TimeRuler } from "./TimeRuler"; diff --git a/apps/client/src/features/recording/components/FrameTimeline.tsx b/apps/client/src/features/recording/ui/FrameTimeline.tsx similarity index 97% rename from apps/client/src/features/recording/components/FrameTimeline.tsx rename to apps/client/src/features/recording/ui/FrameTimeline.tsx index 112c520..0544035 100644 --- a/apps/client/src/features/recording/components/FrameTimeline.tsx +++ b/apps/client/src/features/recording/ui/FrameTimeline.tsx @@ -1,6 +1,6 @@ import { useRef, useState, useCallback } from "react"; -import { cn } from "@/lib/utils"; -import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/shared/lib/utils"; +import { Skeleton } from "@/shared/ui/skeleton"; interface Frame { timeMs: number; diff --git a/apps/client/src/features/recording/components/PlaybackControls.tsx b/apps/client/src/features/recording/ui/PlaybackControls.tsx similarity index 93% rename from apps/client/src/features/recording/components/PlaybackControls.tsx rename to apps/client/src/features/recording/ui/PlaybackControls.tsx index 81265c7..9342726 100644 --- a/apps/client/src/features/recording/components/PlaybackControls.tsx +++ b/apps/client/src/features/recording/ui/PlaybackControls.tsx @@ -1,6 +1,6 @@ import { Play, Pause, Square } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { Button } from "@/shared/ui/button"; +import { cn } from "@/shared/lib/utils"; interface PlaybackControlsProps { isPlaying: boolean; diff --git a/apps/client/src/features/recording/RecordingButton.tsx b/apps/client/src/features/recording/ui/RecordingButton.tsx similarity index 93% rename from apps/client/src/features/recording/RecordingButton.tsx rename to apps/client/src/features/recording/ui/RecordingButton.tsx index 842f48e..6ab7b7c 100644 --- a/apps/client/src/features/recording/RecordingButton.tsx +++ b/apps/client/src/features/recording/ui/RecordingButton.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; import { Circle, Square, Loader2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useRecordingStore } from "@/entities/recording/store"; -import { cn } from "@/lib/utils"; +import { Button } from "@/shared/ui/button"; +import { useRecordingStore } from "@/entities/recording"; +import { cn } from "@/shared/lib/utils"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +} from "@/shared/ui/tooltip"; +import { DropdownMenuItem } from "@/shared/ui/dropdown-menu"; interface RecordingButtonProps { topic: string; diff --git a/apps/client/src/features/recording/RecordingsList.tsx b/apps/client/src/features/recording/ui/RecordingsList.tsx similarity index 95% rename from apps/client/src/features/recording/RecordingsList.tsx rename to apps/client/src/features/recording/ui/RecordingsList.tsx index 300dd25..c2c44fe 100644 --- a/apps/client/src/features/recording/RecordingsList.tsx +++ b/apps/client/src/features/recording/ui/RecordingsList.tsx @@ -5,7 +5,7 @@ import { CardDescription, CardHeader, CardTitle, -} from "@/components/ui/card"; +} from "@/shared/ui/card"; import { Table, TableBody, @@ -13,9 +13,9 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; +} from "@/shared/ui/table"; +import { Button } from "@/shared/ui/button"; +import { Badge } from "@/shared/ui/badge"; import { AlertDialog, AlertDialogAction, @@ -26,12 +26,12 @@ import { AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; +} from "@/shared/ui/alert-dialog"; import { Play, Trash2, Loader2, RefreshCw, Square } from "lucide-react"; -import { useRecordingStore } from "@/entities/recording/store"; +import { useRecordingStore } from "@/entities/recording"; import { VideoPlaybackDialog } from "./VideoPlaybackDialog"; -import { formatSimpleDateTime } from "@/lib/time"; -import type { Recording } from "@/entities/recording/types"; +import { formatSimpleDateTime } from "@/shared/lib/time"; +import type { Recording } from "@/entities/recording"; export function RecordingsList() { const { diff --git a/apps/client/src/features/recording/components/TimeRuler.tsx b/apps/client/src/features/recording/ui/TimeRuler.tsx similarity index 98% rename from apps/client/src/features/recording/components/TimeRuler.tsx rename to apps/client/src/features/recording/ui/TimeRuler.tsx index c9e6ff0..0e6f190 100644 --- a/apps/client/src/features/recording/components/TimeRuler.tsx +++ b/apps/client/src/features/recording/ui/TimeRuler.tsx @@ -1,5 +1,5 @@ import { useRef, useCallback, useMemo } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; interface TimeRulerProps { durationMs: number; diff --git a/apps/client/src/features/recording/components/VideoControlBar.tsx b/apps/client/src/features/recording/ui/VideoControlBar.tsx similarity index 93% rename from apps/client/src/features/recording/components/VideoControlBar.tsx rename to apps/client/src/features/recording/ui/VideoControlBar.tsx index 31ef2fc..41253b0 100644 --- a/apps/client/src/features/recording/components/VideoControlBar.tsx +++ b/apps/client/src/features/recording/ui/VideoControlBar.tsx @@ -1,7 +1,7 @@ import { useRef, useState, useEffect, useCallback, RefObject } from "react"; -import { cn } from "@/lib/utils"; -import { useMediaPlayback } from "../hooks/useMediaPlayback"; -import { useVideoFrames } from "../hooks/useVideoFrames"; +import { cn } from "@/shared/lib/utils"; +import { useMediaPlayback } from "../model/useMediaPlayback"; +import { useVideoFrames } from "../model/useVideoFrames"; import { PlaybackControls } from "./PlaybackControls"; import { FrameTimeline } from "./FrameTimeline"; import { TimeRuler } from "./TimeRuler"; diff --git a/apps/client/src/features/recording/VideoPlaybackDialog.tsx b/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx similarity index 90% rename from apps/client/src/features/recording/VideoPlaybackDialog.tsx rename to apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx index 06d9f69..df31c7b 100644 --- a/apps/client/src/features/recording/VideoPlaybackDialog.tsx +++ b/apps/client/src/features/recording/ui/VideoPlaybackDialog.tsx @@ -4,12 +4,12 @@ import { DialogContent, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; -import { useRecordingStore } from "@/entities/recording/store"; -import { getRecordingStreamUrl } from "@/entities/recording/api"; -import { storage } from "@/lib/storage"; -import { AudioWaveformPlayer } from "./components/AudioWaveformPlayer"; -import { VideoControlBar } from "./components/VideoControlBar"; +} from "@/shared/ui/dialog"; +import { useRecordingStore } from "@/entities/recording"; +import { getRecordingStreamUrl } from "@/entities/recording"; +import { storage } from "@/shared/lib/storage"; +import { AudioWaveformPlayer } from "./AudioWaveformPlayer"; +import { VideoControlBar } from "./VideoControlBar"; interface VideoPlaybackDialogProps { recordingId: number | null; diff --git a/apps/client/src/features/recording/components/WaveformCanvas.tsx b/apps/client/src/features/recording/ui/WaveformCanvas.tsx similarity index 99% rename from apps/client/src/features/recording/components/WaveformCanvas.tsx rename to apps/client/src/features/recording/ui/WaveformCanvas.tsx index 5ee0483..4280dfe 100644 --- a/apps/client/src/features/recording/components/WaveformCanvas.tsx +++ b/apps/client/src/features/recording/ui/WaveformCanvas.tsx @@ -1,6 +1,6 @@ import { useRef, useEffect, useCallback } from "react"; import * as d3 from "d3"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; interface WaveformCanvasProps { waveformData: number[]; diff --git a/apps/client/src/features/role/index.ts b/apps/client/src/features/role/index.ts new file mode 100644 index 0000000..275eb1c --- /dev/null +++ b/apps/client/src/features/role/index.ts @@ -0,0 +1,2 @@ +export * from "./ui/RoleDialogs"; +export { RoleForm } from "./ui/RoleForm"; diff --git a/apps/client/src/features/role/RoleDialogs.tsx b/apps/client/src/features/role/ui/RoleDialogs.tsx similarity index 93% rename from apps/client/src/features/role/RoleDialogs.tsx rename to apps/client/src/features/role/ui/RoleDialogs.tsx index c78b42f..2ca3a94 100644 --- a/apps/client/src/features/role/RoleDialogs.tsx +++ b/apps/client/src/features/role/ui/RoleDialogs.tsx @@ -4,17 +4,17 @@ import { DialogDescription, DialogHeader, DialogTitle, -} from "@/components/ui/dialog"; +} from "@/shared/ui/dialog"; import { CreateRolePayload, Role, UpdateRolePayload, -} from "@/entities/role/types"; +} from "@/entities/role"; import { FC, useState } from "react"; import { RoleForm } from "./RoleForm"; -import { Button } from "@/components/ui/button"; -import { DialogFooter } from "@/components/ui/dialog"; -import { useRoleStore } from "@/entities/role/store"; +import { Button } from "@/shared/ui/button"; +import { DialogFooter } from "@/shared/ui/dialog"; +import { useRoleStore } from "@/entities/role"; // Add Role Dialog export const AddRoleDialog: FC<{ diff --git a/apps/client/src/features/role/RoleForm.tsx b/apps/client/src/features/role/ui/RoleForm.tsx similarity index 92% rename from apps/client/src/features/role/RoleForm.tsx rename to apps/client/src/features/role/ui/RoleForm.tsx index dc14a11..bd28f01 100644 --- a/apps/client/src/features/role/RoleForm.tsx +++ b/apps/client/src/features/role/ui/RoleForm.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/shared/ui/button"; import { Command, CommandEmpty, @@ -6,23 +6,23 @@ import { CommandInput, CommandItem, CommandList, -} from "@/components/ui/command"; -import { DialogFooter } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +} from "@/shared/ui/command"; +import { DialogFooter } from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; +import { Label } from "@/shared/ui/label"; import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover"; -import { usePermissionStore } from "@/entities/permission/store"; -import { Permission } from "@/entities/permission/types"; +} from "@/shared/ui/popover"; +import { usePermissionStore } from "@/entities/permission"; +import { Permission } from "@/entities/permission"; import { CreateRolePayload, Role, UpdateRolePayload, -} from "@/entities/role/types"; -import { cn } from "@/lib/utils"; +} from "@/entities/role"; +import { cn } from "@/shared/lib/utils"; import { Check, ChevronsUpDown } from "lucide-react"; import { FC, useEffect, useState } from "react"; diff --git a/apps/client/src/features/ros2/index.ts b/apps/client/src/features/ros2/index.ts new file mode 100644 index 0000000..e7df3ff --- /dev/null +++ b/apps/client/src/features/ros2/index.ts @@ -0,0 +1 @@ +export { Ros2Dashboard } from "./ui/Ros2Dashboard"; diff --git a/apps/client/src/features/ros2/Ros2Dashboard.tsx b/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx similarity index 91% rename from apps/client/src/features/ros2/Ros2Dashboard.tsx rename to apps/client/src/features/ros2/ui/Ros2Dashboard.tsx index 5417547..8bc829b 100644 --- a/apps/client/src/features/ros2/Ros2Dashboard.tsx +++ b/apps/client/src/features/ros2/ui/Ros2Dashboard.tsx @@ -1,6 +1,6 @@ import { Link } from "react-router"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/ui/card"; +import { Button } from "@/shared/ui/button"; export function Ros2Dashboard() { return ( diff --git a/apps/client/src/features/rtc/index.ts b/apps/client/src/features/rtc/index.ts new file mode 100644 index 0000000..c6f41d9 --- /dev/null +++ b/apps/client/src/features/rtc/index.ts @@ -0,0 +1,8 @@ +export { AudioLevelBar } from "./ui/AudioLevelBar"; +export { StreamReceiver } from "./ui/StreamReceiver"; +export { HttpStreamReceiver } from "./ui/HttpStreamReceiver"; +export { WebRTCProvider, useWebRTC } from "./ui/WebRTCProvider"; +export { WebRTCManager } from "./lib/rtc"; +export type { WebRTCConfig } from "./lib/rtc"; +export { captureFrameFromStream } from "./lib/captureFrame"; +export * from "./lib/turnService"; diff --git a/apps/client/src/features/rtc/captureFrame.ts b/apps/client/src/features/rtc/lib/captureFrame.ts similarity index 100% rename from apps/client/src/features/rtc/captureFrame.ts rename to apps/client/src/features/rtc/lib/captureFrame.ts diff --git a/apps/client/src/features/rtc/rtc.ts b/apps/client/src/features/rtc/lib/rtc.ts similarity index 98% rename from apps/client/src/features/rtc/rtc.ts rename to apps/client/src/features/rtc/lib/rtc.ts index 3bb3868..76ac916 100644 --- a/apps/client/src/features/rtc/rtc.ts +++ b/apps/client/src/features/rtc/lib/rtc.ts @@ -1,4 +1,4 @@ -import { WebSocketChannel, WebSocketMessage } from "../ws/ws"; +import { WebSocketChannel, WebSocketMessage } from "../../ws"; export interface WebRTCConfig { iceServers: RTCIceServer[]; diff --git a/apps/client/src/features/rtc/turnService.ts b/apps/client/src/features/rtc/lib/turnService.ts similarity index 99% rename from apps/client/src/features/rtc/turnService.ts rename to apps/client/src/features/rtc/lib/turnService.ts index 607b97e..dd280a3 100644 --- a/apps/client/src/features/rtc/turnService.ts +++ b/apps/client/src/features/rtc/lib/turnService.ts @@ -1,5 +1,5 @@ -import { supabase } from "@/lib/supabase"; -import { getConfigs, updateConfig } from "@/entities/configurations/api"; +import { supabase } from "@/shared/lib/supabase"; +import { getConfigs, updateConfig } from "@/entities/configurations"; export interface TurnUsage { egressBytes: number; diff --git a/apps/client/src/features/rtc/AudioLevelBar.tsx b/apps/client/src/features/rtc/ui/AudioLevelBar.tsx similarity index 98% rename from apps/client/src/features/rtc/AudioLevelBar.tsx rename to apps/client/src/features/rtc/ui/AudioLevelBar.tsx index e1d9775..4361e74 100644 --- a/apps/client/src/features/rtc/AudioLevelBar.tsx +++ b/apps/client/src/features/rtc/ui/AudioLevelBar.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/shared/lib/utils"; type AudioLevelBarProps = { stream: MediaStream | null; diff --git a/apps/client/src/features/rtc/ui/HttpStreamReceiver.tsx b/apps/client/src/features/rtc/ui/HttpStreamReceiver.tsx new file mode 100644 index 0000000..634598b --- /dev/null +++ b/apps/client/src/features/rtc/ui/HttpStreamReceiver.tsx @@ -0,0 +1,132 @@ +import { useEffect, useRef, useState } from "react"; +import Hls from "hls.js"; +import { Button } from "@/shared/ui/button"; +import { apiClient } from "@/shared/api"; +import { storage } from "@/shared/lib/storage"; + +type HttpStreamReceiverProps = { + topic: string; + streamType: "audio" | "video"; +}; + +type TokenResponse = { + token: string; + expires_in: number; +}; + +function buildPlaylistUrl(topic: string, token: string): string { + const serverUrl = storage.getServerUrl() ?? ""; + const base = serverUrl ? `${serverUrl}/api` : "/api"; + const encodedTopic = encodeURIComponent(topic); + return `${base}/streams/hls/${encodedTopic}/playlist.m3u8?token=${encodeURIComponent(token)}`; +} + +export function HttpStreamReceiver({ topic, streamType }: HttpStreamReceiverProps) { + const videoRef = useRef(null); + const hlsRef = useRef(null); + const [active, setActive] = useState(false); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + return () => { + if (hlsRef.current) { + hlsRef.current.destroy(); + hlsRef.current = null; + } + }; + }, []); + + if (streamType === "audio") { + return ( +
+ Audio over HLS is not supported yet. Switch to WebRTC. +
+ ); + } + + const attachStream = (url: string) => { + const video = videoRef.current; + if (!video) { + setError("Video element not ready"); + return false; + } + + if (Hls.isSupported()) { + const hls = new Hls({ + lowLatencyMode: true, + liveSyncDuration: 2, + liveMaxLatencyDuration: 6, + backBufferLength: 10, + }); + hls.loadSource(url); + hls.attachMedia(video); + hls.on(Hls.Events.ERROR, (_event, data) => { + if (data.fatal) { + console.error("[HLS] fatal error", data); + setError(data.details ?? "Playback error"); + } + }); + hlsRef.current = hls; + } else if (video.canPlayType("application/vnd.apple.mpegurl")) { + video.src = url; + } else { + setError("HLS is not supported in this browser"); + return false; + } + + video.play().catch((e) => console.error("HLS play failed:", e)); + return true; + }; + + const handlePlay = async () => { + setLoading(true); + setError(null); + try { + const encodedTopic = encodeURIComponent(topic); + const { data } = await apiClient.post( + `/streams/hls/${encodedTopic}/token`, + ); + const url = buildPlaylistUrl(topic, data.token); + + setActive(true); + // Wait one frame so React mounts the
)} -
-

- Personal C2 platform
for Physical AI -

+
+
- - + + + + + +
{/* */} - + {/* @@ -159,7 +174,7 @@ function LandingPage() { - + */}