Skip to content

Commit

Permalink
favicon wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jayanth-kumar-morem committed Jan 6, 2025
1 parent 7d32a9f commit 610ee7c
Show file tree
Hide file tree
Showing 18 changed files with 404 additions and 266 deletions.
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,13 @@ cython_debug/
#.idea/

# frontend build assets in preswald server
preswald/static/assets/
preswald/static/assets/**.js
preswald/static/assets/**.css
preswald/static/index.html
temp
preswald_project/

# Keep default assets
!preswald/static/assets/defaults/
!preswald/static/assets/defaults/*

temp
notes.md
4 changes: 2 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/preswald.svg" />
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<title>Preswald</title>
</head>
<body>
<div id="root"></div>
Expand Down
202 changes: 73 additions & 129 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import { useEffect, useState } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { websocket } from "./utils/websocket";
import Layout from "./components/Layout";
import Dashboard from "./components/pages/Dashboard";

import { AppStateProvider } from "./state/AppStateContext";
import Connections from "./components/pages/Connections";
import Metrics from "./components/pages/Metrics";
import Dashboard from "./components/pages/Dashboard";
import Definitions from "./components/pages/Definitions";
import Entities from "./components/pages/Entities";
import Schedules from "./components/pages/Schedules";
import Layout from "./components/Layout";
import Metrics from "./components/pages/Metrics";
import Queries from "./components/pages/Queries";
import Definitions from "./components/pages/Definitions";
import Schedules from "./components/pages/Schedules";
import { websocket } from "./utils/websocket";

const App = () => {
const AppContent = () => {
const [components, setComponents] = useState([]);
const [error, setError] = useState(null);
const [config, setConfig] = useState(null);
Expand All @@ -19,145 +21,87 @@ const App = () => {
useEffect(() => {
websocket.connect();

const unsubscribe = websocket.subscribe(handleWebSocketMessage);

return () => {
unsubscribe();
websocket.disconnect();
};
}, []);

// TODO: where is this used, if at all?
useEffect(() => {
// Update document title based on config
const updateTitle = () => {
const title = config?.project?.title;
if (title) {
document.title = title;
}
};

updateTitle();

document.addEventListener("visibilitychange", updateTitle);
return () => document.removeEventListener("visibilitychange", updateTitle);
}, [config]);

const handleWebSocketMessage = (message) => {
console.log("[App] Received WebSocket message:", message);

switch (message.type) {
case "components":
refreshComponentsList(message.components);
break;

case "error":
handleError(message.content);
break;
const handleWebSocketMessage = (message) => {
console.log("[App] Received WebSocket message:", message);

case "connection_status":
updateConnectionStatus(message);
break;
switch (message.type) {
case "components":
setComponents(message.components || []);
setError(null);
break;

case "config": // TODO: not used anywhere
setConfig(message.config);
break;
case "error":
console.error("[App] Received error:", message.content);
setError(message.content.message);
break;

// state_update and initial_state are handled by websocket.js
}
};
case "connection_status":
setIsConnected(message.connected);
setError(message.connected ? null : "Lost connection to server. Attempting to reconnect...");
break;

const refreshComponentsList = (components) => {
const updatedComponents = components.map((component) => {
if (component.id) {
const currentState = websocket.getComponentState(component.id);
return {
...component,
value: currentState !== undefined ? currentState : component.value,
error: null,
};
case "config":
setConfig(message.config);
break;
}
return component;
});
setComponents(updatedComponents);
setError(null);
};

const handleError = (errorContent) => {
console.error("[App] Received error:", errorContent);
setError(errorContent.message);
};

if (errorContent.componentId) {
setComponents((prevComponents) =>
prevComponents.map((component) =>
component.id === errorContent.componentId
? { ...component, error: errorContent.message }
: component
)
);
}
};
const unsubscribe = websocket.subscribe(handleWebSocketMessage);
return () => {
unsubscribe();
websocket.disconnect();
};
}, []);

const handleComponentUpdate = (componentId, value) => {
try {
websocket.updateComponentState(componentId, value);
} catch (error) {
console.error("[App] Error updating component state:", error);
setComponents((prevComponents) =>
prevComponents.map((component) =>
component.id === componentId
? { ...component, error: error.message }
: component
)
);
setError(error.message);
}
};

const updateConnectionStatus = (message) => {
setIsConnected(message.connected);
setError(
message.connected
? null
: "Lost connection to server. Attempting to reconnect..."
);
};

const renderLoadingState = () => (
<div className="flex items-center justify-center h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Connecting to server...</p>
</div>
</div>
return (
<Layout>
{!isConnected ? (
<div className="flex items-center justify-center h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Connecting to server...</p>
</div>
</div>
) : (
<Routes>
<Route
path="/"
element={
<Dashboard
components={components}
error={error}
handleComponentUpdate={handleComponentUpdate}
/>
}
/>
<Route path="/connections" element={<Connections />} />
<Route path="/metrics" element={<Metrics />} />
<Route path="/entities" element={<Entities />} />
<Route path="/schedules" element={<Schedules />} />
<Route path="/definitions" element={<Definitions />} />
<Route path="/queries" element={<Queries />} />
</Routes>
)}
</Layout>
);
};

const App = () => {
return (
<Router>
<Layout>
{!isConnected ? (
renderLoadingState
) : (
<Routes>
<Route
path="/"
element={
<Dashboard
components={components}
error={error}
handleComponentUpdate={handleComponentUpdate}
/>
}
/>
<Route path="/connections" element={<Connections />} />
<Route path="/metrics" element={<Metrics />} />
<Route path="/entities" element={<Entities />} />
<Route path="/schedules" element={<Schedules />} />
<Route path="/definitions" element={<Definitions />} />
<Route path="/queries" element={<Queries />} />
</Routes>
)}
</Layout>
</Router>
<AppStateProvider>
<Router>
<AppContent />
</Router>
</AppStateProvider>
);
};

Expand Down
79 changes: 27 additions & 52 deletions frontend/src/components/Layout.jsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,39 @@
'use client'

import { ChartBarIcon, ClockIcon, DocumentTextIcon, GlobeAltIcon, HomeIcon, MagnifyingGlassIcon, ServerIcon } from "@heroicons/react/24/solid";
import { useEffect, useState } from 'react'
import React, { useEffect } from 'react';

import Content from "./Content";
import Sidebar from "./Sidebar";
import Topbar from "./TopBar";

const navigation = [
{ name: 'Dashboard', href: '/', icon: HomeIcon, current: true },
{ name: 'Queries', href: '/queries', icon: MagnifyingGlassIcon, current: false },
{ name: 'Metrics', href: '/metrics', icon: ChartBarIcon, current: false },
{ name: 'Schedules', href: '/schedules', icon: ClockIcon, current: false },
{ name: 'Connections', href: '/connections', icon: ServerIcon, current: false },
{ name: 'Entities', href: '/entities', icon: GlobeAltIcon, current: false },
{ name: 'Definitions', href: '/definitions', icon: DocumentTextIcon, current: false },
];

export default function Example({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [branding, setBranding] = useState({
name: 'Preswald',
logo: '/assets/default-logo.png',
favicon: '/assets/favicon.ico'
});
import Content from './Content';
import Sidebar from './Sidebar';
import TopBar from './TopBar';
import { useAppState } from '../state/AppStateContext';

// Favicon component to handle dynamic favicon updates
const Favicon = ({ href }) => {
useEffect(() => {
// Get branding from window object (set by server)
if (window.PRESWALD_BRANDING) {
setBranding(window.PRESWALD_BRANDING);
// Update document title
document.title = window.PRESWALD_BRANDING.name;
const favicon = document.querySelector('link[rel="icon"]');
if (favicon && href) {
favicon.href = href;
}
}, []);
}, [href]);

const toggleSidebar = () => {
setSidebarCollapsed(!sidebarCollapsed);
};
return null;
};

const Layout = ({ children }) => {
const { branding } = useAppState();

return (
<>
<Sidebar
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
navigation={navigation}
branding={branding}
isCollapsed={sidebarCollapsed}
/>
<div className={`transition-all duration-300 ${sidebarCollapsed ? 'lg:pl-20' : 'lg:pl-80'}`}>
<Topbar
setSidebarOpen={setSidebarOpen}
branding={branding}
onToggleSidebar={toggleSidebar}
isCollapsed={sidebarCollapsed}
/>
<main>
<Content>{children}</Content>
<div className="flex h-screen bg-gray-100">
<Favicon href={branding.favicon} />
<Sidebar branding={branding} />
<div className="flex flex-col flex-1 overflow-hidden">
<TopBar branding={branding} />
<main className="flex-1 overflow-auto">
{children}
</main>
</div>
</>
</div>
);
}
};

export default Layout;
Loading

0 comments on commit 610ee7c

Please sign in to comment.