diff --git a/src/App.tsx b/src/App.tsx index e97b3a5..751e950 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,9 @@ import { DashboardProvider } from './dashboard/provider/DashboardProvider.tsx'; import { PrinterWidgetProvider } from './printers/providers/PrinterWidgetProvider.tsx'; import { SSEProvider } from './core/sse/SSEProvider.tsx'; import { SettingsProvider } from './core/settings/settingsProvider.tsx'; -import { EventNotifications } from './system/components/event-notifications/EventNotifications.tsx'; +import { DiscoveryNotifications } from './system/components/discovery-notifications/DiscoveryNotifications.tsx'; +import { NewProjectNotification } from './projects/notifications/new-project-notification/NewProjectNotification.tsx'; +import { Notifications } from '@mantine/notifications'; export default function App() { const [opened, { toggle }] = useDisclosure(); @@ -41,8 +43,10 @@ export default function App() { - + + + diff --git a/src/assets/components/AssetCardProps.ts b/src/assets/components/AssetCardProps.ts deleted file mode 100644 index 9debae9..0000000 --- a/src/assets/components/AssetCardProps.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Asset } from "../entities/Assets"; - -export type AssetCardProps = { - projectUuid: string; - asset: Asset; - selected: boolean; - onSelectChange: (arg0: boolean) => void; - view3d?: boolean, - onView3dChange?: (arg0: boolean) => void; - onDelete: (projectUuid: string, id: string) => void; - onChange: (projectUuid: string, id: string) => void; -} \ No newline at end of file diff --git a/src/assets/components/asset-card/AssetCard.tsx b/src/assets/components/asset-card/AssetCard.tsx index 5aa95da..b09d7d7 100644 --- a/src/assets/components/asset-card/AssetCard.tsx +++ b/src/assets/components/asset-card/AssetCard.tsx @@ -40,8 +40,8 @@ export function AssetCard({ asset, focused, onFocused, onDelete, onChange, view3 return ( <> {modal && asset.image_id && asset.image_id != "" && } @@ -50,7 +50,7 @@ export function AssetCard({ asset, focused, onFocused, onDelete, onChange, view3 {asset?.image_id === "" ? (iconMap.get(asset.extension) ?? ) : {asset.name} } @@ -58,7 +58,7 @@ export function AssetCard({ asset, focused, onFocused, onDelete, onChange, view3 { onFocused() }}> - {asset.name} + {asset.label != "" ? asset.label : asset.name} @@ -80,7 +80,7 @@ export function AssetCard({ asset, focused, onFocused, onDelete, onChange, view3 projectUuid={asset.project_uuid} id={asset.id} openDetails={() => { onFocused() }} - downloadURL={`${settings.localBackend}/projects/${asset.project_uuid}/assets/${asset.id}?download=true'`} + downloadURL={`${settings.localBackend}/projects/${asset.project_uuid}/assets/${asset.id}/file?download=true'`} onDelete={onDelete} toggleLoad={toggleLoadingCallback}> diff --git a/src/assets/components/model/model-detail-pane/ModelDetailPane.tsx b/src/assets/components/model/model-detail-pane/ModelDetailPane.tsx index 5a5b758..2548ff3 100644 --- a/src/assets/components/model/model-detail-pane/ModelDetailPane.tsx +++ b/src/assets/components/model/model-detail-pane/ModelDetailPane.tsx @@ -17,7 +17,7 @@ type ModelProps = { function Model({ color, model, projectUuid }: ModelProps) { const { settings } = useContext(SettingsContext); - const geom = useLoader(STLLoader, `${settings.localBackend}/projects/${projectUuid}/assets/${model.id}`); + const geom = useLoader(STLLoader, `${settings.localBackend}/projects/${projectUuid}/assets/${model.id}/file`); const meshRef = useRef(null!) const [active, setActive] = useState(false) diff --git a/src/assets/entities/Assets.ts b/src/assets/entities/Assets.ts index e67386c..57df517 100644 --- a/src/assets/entities/Assets.ts +++ b/src/assets/entities/Assets.ts @@ -2,6 +2,7 @@ export interface Asset { id: string name: string + label: string origin: string project_uuid: string path: string diff --git a/src/core/sse/SubscriptionManager.ts b/src/core/sse/SubscriptionManager.ts index b28e917..0d4f982 100644 --- a/src/core/sse/SubscriptionManager.ts +++ b/src/core/sse/SubscriptionManager.ts @@ -48,6 +48,9 @@ export const createSubsManager = (url: string): SubscriptionManager => { return; } if (ev.event) { + if (ev.data.name) { + state.evSubs.get(`${ev.event}.${ev.data.name}`)?.forEach(sub => sub.callback(ev.data)); + } if (ev.data.state) { state.evSubs.get(ev.event)?.forEach(sub => sub.callback(ev.data.state)); } else if (Array.isArray(ev.data)) { diff --git a/src/main.tsx b/src/main.tsx index d220cf7..1f3baeb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -24,7 +24,6 @@ import '@mantine/core/styles/Checkbox.css'; import '@mantine/dropzone/styles.css'; import '@mantine/notifications/styles.css'; import { createTheme, MantineProvider } from '@mantine/core'; -import { Notifications } from '@mantine/notifications'; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { routes as dashboardRoutes } from "./dashboard/routes.tsx"; @@ -71,7 +70,6 @@ console.log(router); ReactDOM.createRoot(document.getElementById('root')!).render( - diff --git a/src/projects/components/project-page/ProjectPage.tsx b/src/projects/components/project-page/ProjectPage.tsx index 42619d3..a6dba2c 100644 --- a/src/projects/components/project-page/ProjectPage.tsx +++ b/src/projects/components/project-page/ProjectPage.tsx @@ -5,6 +5,7 @@ import { useContext } from "react"; import { ProjectPageBody } from "./parts/project-page-body/ProjectPageBody.tsx"; import { Header } from "@/core/header/Header.tsx"; import { SettingsContext } from "@/core/settings/settingsContext.ts"; +import { Refresher } from "./parts/refresher/Refresher.tsx"; export function ProjectPage() { @@ -23,7 +24,7 @@ export function ProjectPage() { description={project?.description} tags={project?.tags} link={project?.external_link} - imagePath={`${settings.localBackend}/projects/${project?.uuid}/assets/${project?.default_image_id}`} + imagePath={`${settings.localBackend}/projects/${project?.uuid}/assets/${project?.default_image_id}/file`} onTagClick={(t) => navigate(`/projects/list?filter=${JSON.stringify({ tags: [t.value] })}`)} /> {error &&

Error!

} @@ -31,6 +32,7 @@ export function ProjectPage() { console.log("onProjectChange") refetch() }} />} + ) } \ No newline at end of file diff --git a/src/projects/components/project-page/parts/project-page-body/ProjectPageBody.tsx b/src/projects/components/project-page/parts/project-page-body/ProjectPageBody.tsx index 194c5a9..3394702 100644 --- a/src/projects/components/project-page/parts/project-page-body/ProjectPageBody.tsx +++ b/src/projects/components/project-page/parts/project-page-body/ProjectPageBody.tsx @@ -1,8 +1,8 @@ -import { Alert, Container, Flex, rem, SimpleGrid, Skeleton, Tabs } from "@mantine/core"; +import { Alert, Button, Container, Flex, rem, SimpleGrid, Skeleton, Tabs } from "@mantine/core"; import useAxios from "axios-hooks"; import { Asset, AssetType } from "@/assets/entities/Assets.ts"; import { useListState } from "@mantine/hooks"; -import React, { useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { ModelDetailPane } from "@/assets/components/model/model-detail-pane/ModelDetailPane.tsx"; import { IconSettings, IconFiles } from "@tabler/icons-react"; import { useNavigate, useSearchParams } from "react-router-dom"; @@ -11,6 +11,7 @@ import { EditProject } from "./parts/edit-project/EditProject.tsx"; import { Project } from "../../../../entities/Project.ts"; import { SettingsContext } from "@/core/settings/settingsContext.ts"; import { AssetDetails } from "@/assets/components/asset-details/AssetDetails.tsx"; +import { Refresher } from "./parts/refresher/Refresher.tsx"; import { AssetCard } from "@/assets/components/asset-card/AssetCard.tsx"; const iconStyle = { width: rem(12), height: rem(12) }; @@ -25,15 +26,21 @@ export function ProjectPageBody({ projectUuid, project, onProjectChange }: Proje const { settings } = useContext(SettingsContext); const navigate = useNavigate(); const [searchParams] = useSearchParams(); + const [assets, setAssets] = useState([]); const [selectedModels, selectedModelsHandlers] = useListState([]); const [selectedAsset, setSelectedAsset] = useState(); const [typeFilter, setTypeFilter] = useState(searchParams.get('tab')); const [{ data: assetTypes, loading: tLoading, error: tError }] = useAxios( `${settings.localBackend}/assettypes` ); - const [{ data: assets, loading, error }, refetch] = useAxios( + const [{ data, loading, error }, refetch] = useAxios( `${settings.localBackend}/projects/${projectUuid}/assets` ); + useEffect(() => { + if (data) { + setAssets(data); + } + }, [data]); useEffect(() => { if (selectedModels.length == 0) { @@ -127,6 +134,7 @@ export function ProjectPageBody({ projectUuid, project, onProjectChange }: Proje } + ); } diff --git a/src/projects/components/project-page/parts/project-page-body/parts/refresher/Refresher.tsx b/src/projects/components/project-page/parts/project-page-body/parts/refresher/Refresher.tsx new file mode 100644 index 0000000..18865e1 --- /dev/null +++ b/src/projects/components/project-page/parts/project-page-body/parts/refresher/Refresher.tsx @@ -0,0 +1,45 @@ +import SSEContext from "@/core/sse/SSEContext"; +import { Anchor } from "@mantine/core"; +import { useId } from "@mantine/hooks"; +import { notifications } from "@mantine/notifications"; +import { useContext, useEffect, useState } from "react"; +import { Link } from "react-router-dom"; + +type RefresherProps = { + projectUUID: string; +} + +export function Refresher({ projectUUID }: RefresherProps) { + const subscriberId = useId(); + const { connected, subscribe, unsubscribe } = useContext(SSEContext) + const [assetUpdate, setAssetUpdate] = useState({} as any) + const [error, setError] = useState(null); + useEffect(() => { + if (!connected) return; + setAssetUpdate({}) + const subscription = { + subscriberId, + provider: `system/events`, + } + subscribe({ + ...subscription, + event: `system.state.asset.event`, + callback: setAssetUpdate + }).catch(setError); + return () => { + unsubscribe(subscriberId) + } + }, [connected]) + + useEffect(() => { + console.log(assetUpdate) + if (!assetUpdate.state) return; + if (projectUUID == assetUpdate.state.projectUUID) { + notifications.show({ + title: `Assets have changed`, + message: Refresh, + }) + } + }, [assetUpdate]) + return (<>) +} \ No newline at end of file diff --git a/src/projects/components/project-page/parts/refresher/Refresher.tsx b/src/projects/components/project-page/parts/refresher/Refresher.tsx new file mode 100644 index 0000000..65d0d2c --- /dev/null +++ b/src/projects/components/project-page/parts/refresher/Refresher.tsx @@ -0,0 +1,40 @@ +import SSEContext from "@/core/sse/SSEContext"; +import { useId } from "@mantine/hooks"; +import { useContext, useEffect, useState } from "react"; + +type RefresherProps = { + projectUUID: string; + refresh: () => void; +} + +export function Refresher({ projectUUID, refresh }: RefresherProps) { + const subscriberId = useId(); + const { connected, subscribe, unsubscribe } = useContext(SSEContext) + const [projectUpdate, setProjectUpdate] = useState({} as any) + const [error, setError] = useState(null); + useEffect(() => { + if (!connected) return; + setProjectUpdate({}) + const subscription = { + subscriberId, + provider: `system/events`, + } + subscribe({ + ...subscription, + event: `system.state.project.event`, + callback: setProjectUpdate + }).catch(setError); + return () => { + unsubscribe(subscriberId) + } + }, [connected]) + + useEffect(() => { + console.log(projectUpdate) + if (!projectUpdate.state) return; + if (projectUpdate.state.projectUUID == projectUUID && projectUpdate.state.type == "update") { + refresh(); + } + }, [projectUpdate]) + return (<>) +} \ No newline at end of file diff --git a/src/projects/components/projects-page/parts/projects-list/parts/project-card/ProjectCard.tsx b/src/projects/components/projects-page/parts/projects-list/parts/project-card/ProjectCard.tsx index 04a475b..40d3281 100644 --- a/src/projects/components/projects-page/parts/projects-list/parts/project-card/ProjectCard.tsx +++ b/src/projects/components/projects-page/parts/projects-list/parts/project-card/ProjectCard.tsx @@ -26,7 +26,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
} diff --git a/src/system/components/event-notifications/EventNotifications.tsx b/src/projects/notifications/new-project-notification/NewProjectNotification.tsx similarity index 58% rename from src/system/components/event-notifications/EventNotifications.tsx rename to src/projects/notifications/new-project-notification/NewProjectNotification.tsx index fdd20ec..6a2be9d 100644 --- a/src/system/components/event-notifications/EventNotifications.tsx +++ b/src/projects/notifications/new-project-notification/NewProjectNotification.tsx @@ -1,23 +1,25 @@ import SSEContext from "@/core/sse/SSEContext"; +import { Anchor } from "@mantine/core"; import { useId } from "@mantine/hooks"; import { notifications } from "@mantine/notifications"; import { useContext, useEffect, useState } from "react"; +import { Link } from "react-router-dom"; -export function EventNotifications() { +export function NewProjectNotification() { const subscriberId = useId(); const { connected, subscribe, unsubscribe } = useContext(SSEContext) - const [message, setMessage] = useState("") + const [message, setMessage] = useState({} as any) const [error, setError] = useState(null); useEffect(() => { if (!connected) return; - setMessage("") + setMessage({}) const subscription = { subscriberId, provider: `system/events`, } subscribe({ ...subscription, - event: `system.state`, + event: `system.state.project.event`, callback: setMessage }).catch(setError); return () => { @@ -27,10 +29,13 @@ export function EventNotifications() { useEffect(() => { console.log(message) - notifications.show({ - title: message, - message: message, - }) + if (!message.state) return; + if (message.state.type == "new") { + notifications.show({ + title: `New project found`, + message: <>Go to {message.state.projectName}, + }) + } }, [message]) return (<>) } \ No newline at end of file diff --git a/src/system/components/discovery-notifications/DiscoveryNotifications.tsx b/src/system/components/discovery-notifications/DiscoveryNotifications.tsx new file mode 100644 index 0000000..cd1400e --- /dev/null +++ b/src/system/components/discovery-notifications/DiscoveryNotifications.tsx @@ -0,0 +1,54 @@ +import SSEContext from "@/core/sse/SSEContext"; +import { rem } from "@mantine/core"; +import { useId } from "@mantine/hooks"; +import { notifications } from "@mantine/notifications"; +import { IconCheck } from "@tabler/icons-react"; +import { useContext, useEffect, useState } from "react"; + +export function DiscoveryNotifications() { + const subscriberId = useId(); + const { connected, subscribe, unsubscribe } = useContext(SSEContext) + const [id, setId] = useState(null); + const [message, setMessage] = useState({} as any) + const [error, setError] = useState(null); + useEffect(() => { + if (!connected) return; + setMessage("") + const subscription = { + subscriberId, + provider: `system/events`, + } + subscribe({ + ...subscription, + event: `system.state.discovery.scan`, + callback: setMessage + }).catch(setError); + return () => { + unsubscribe(subscriberId) + } + }, [connected]) + + useEffect(() => { + console.log(message) + if (!message.state) return; + if (message.state.state == "started") { + setId(notifications.show({ + loading: true, + title: "New Scan started", + message: "Let's find new projects!", + autoClose: false + })) + } else if (id) { + notifications.update({ + id, + color: 'teal', + title: 'Scan finished!', + message: '', + icon: , + loading: false, + autoClose: 2000, + }); + } + }, [message]) + return (<>) +} \ No newline at end of file