diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 9ada2beb..55f8621c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -6,8 +6,8 @@ on: - main jobs: - build: - name: Push Docker Image to Docker Hub and Deploy to Server + test: + name: Run Vitest runs-on: ubuntu-latest steps: - name: Check out repository @@ -18,10 +18,19 @@ jobs: with: node-version: 18 - - name: Install Dependencies and Run Vitest - run: | - npm install --legacy-peer-deps - npm run test + - name: Install Dependencies + run: npm install --legacy-peer-deps + + - name: Run Tests + run: npm run test + + build-deploy: + name: Build and Deploy Docker Image to Server + runs-on: ubuntu-latest + needs: test # This ensures the build only runs if tests pass + steps: + - name: Check out repository + uses: actions/checkout@v2 - name: Login to Docker Hub id: docker-hub diff --git a/package-lock.json b/package-lock.json index 863d5d25..56f850cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webport", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webport", - "version": "0.2.0", + "version": "0.2.1", "dependencies": { "framer-motion": "^12.4.7", "jsmediatags": "^3.9.7", @@ -15,6 +15,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", @@ -1554,7 +1555,6 @@ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -1658,8 +1658,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1937,7 +1936,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -2624,8 +2622,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -4328,7 +4325,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -4768,7 +4764,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -4784,7 +4779,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -4847,8 +4841,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.14.2", diff --git a/package.json b/package.json index ece3f463..9a8292dd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", diff --git a/src/App.jsx b/src/App.jsx index a28406b4..ec06b617 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import './App.css'; import Desktop from './components/Desktop/Desktop.jsx'; import DesktopAppsList from './lists/DesktopAppsList.jsx'; +import { AppsProvider } from './services/AppsContext/AppsContext.jsx'; function App() { const [openApps, setOpenApps] = useState([]); @@ -26,22 +27,24 @@ function App() { }; return ( -
- - {openApps.map((appId) => { - const appConfig = DesktopAppsList.find((app) => app.id === appId); - const AppComponent = appConfig?.component; - - return ( - AppComponent && ( - handleCloseApp(appId)} - /> - ) - ); - })} -
+ +
+ + {openApps.map((appId) => { + const appConfig = DesktopAppsList.find((app) => app.id === appId); + const AppComponent = appConfig?.component; + + return ( + AppComponent && ( + handleCloseApp(appId)} + /> + ) + ); + })} +
+
); } diff --git a/src/components/Desktop/Desktop.jsx b/src/components/Desktop/Desktop.jsx index 7715df6a..ff0a5333 100644 --- a/src/components/Desktop/Desktop.jsx +++ b/src/components/Desktop/Desktop.jsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; +import { AppsContext } from '../../services/AppsContext/AppsContext.jsx'; import DesktopIcon from '../DesktopIcon/DesktopIcon.jsx'; -import DesktopAppsList from '../../lists/DesktopAppsList.jsx'; import { GRID_GAP, TOP_MARGIN, LEFT_MARGIN } from '../../configs/DesktopIconConfig/DesktopIconConfig.jsx'; import { GRID_SIZE } from '../../configs/DesktopIconConfig/DesktopIconConfig.jsx'; import './Desktop.css'; @@ -12,17 +12,15 @@ import { FocusWrapper } from '../../interactions/FocusControl/FocusControl.jsx'; */ function getPositionFromPriority(priority) { const safePriority = priority && priority > 0 ? priority : 1; - // Single-column approach: each subsequent icon moves (GRID_SIZE + GRID_GAP) down const effectiveCellSize = GRID_SIZE + GRID_GAP; - const x = LEFT_MARGIN; const y = TOP_MARGIN + (safePriority - 1) * effectiveCellSize; - return { x, y }; } function Desktop({ onOpenApp }) { + const { apps } = useContext(AppsContext); const [selectedIcon, setSelectedIcon] = useState(null); const handleWallpaperClick = () => { @@ -36,7 +34,7 @@ function Desktop({ onOpenApp }) { return (
- {DesktopAppsList.map((iconConfig) => { + {apps.filter(iconConfig => !iconConfig.indock).map((iconConfig) => { // Convert the icon's priority into an (x,y) position, // now using GRID_GAP to keep them spaced out. const position = getPositionFromPriority(iconConfig.priority); @@ -49,7 +47,7 @@ function Desktop({ onOpenApp }) { isSelected={selectedIcon === iconConfig.id} onClick={() => handleIconClick(iconConfig.id)} onDoubleClick={() => onOpenApp(iconConfig.id)} - position={position} + position={position} /> ); })} diff --git a/src/components/MiniApps/MiniApps.jsx b/src/components/MiniApps/MiniApps.jsx index e2a38d0b..a33ea9cd 100644 --- a/src/components/MiniApps/MiniApps.jsx +++ b/src/components/MiniApps/MiniApps.jsx @@ -69,7 +69,6 @@ function MiniApps() { .filter(app => app.available) .filter(app => { if (app.id === 'battery' && deviceInfo.battery.level === null) return false; - if (app.id === 'user' && deviceInfo.orientation === 'portrait') return false; return true; }) .slice() diff --git a/src/lists/DesktopAppsList.jsx b/src/lists/DesktopAppsList.jsx index 33450c4d..8bd47b48 100644 --- a/src/lists/DesktopAppsList.jsx +++ b/src/lists/DesktopAppsList.jsx @@ -8,6 +8,8 @@ import awaiIcon from '../media/icons/awai.png'; /** * Each icon now has a `priority` which determines its order on the desktop grid. * Additionally, apps that are only links have a `link` property. + * The new `indock` field determines if the icon should be displayed on the desktop. + * If `indock` is true, the icon will not be rendered on the desktop. */ const DesktopAppsList = [ { @@ -16,6 +18,7 @@ const DesktopAppsList = [ icon: defaultIcon, component: SortingAlgorithms, priority: 4, + indock: false, }, { @@ -24,6 +27,7 @@ const DesktopAppsList = [ icon: folderIcon, component: null, priority: 5, + indock: true, // This icon will not display on the desktop }, { @@ -33,6 +37,7 @@ const DesktopAppsList = [ link: 'http://linkedin.com/in/htdguide/', component: null, priority: 2, + indock: false, }, { @@ -42,6 +47,7 @@ const DesktopAppsList = [ link: 'https://github.com/htdguide', component: null, priority: 1, + indock: true, // This icon will not display on the desktop }, { @@ -51,6 +57,7 @@ const DesktopAppsList = [ link: 'https://applywithai.com', component: null, priority: 3, + indock: false, }, // Add more apps here as needed, with increasing `priority` diff --git a/src/miniapps/ControlCentreMiniApp/ControlCentreMiniApp.css b/src/miniapps/ControlCentreMiniApp/ControlCentreMiniApp.css index 7e722078..821c16fa 100644 --- a/src/miniapps/ControlCentreMiniApp/ControlCentreMiniApp.css +++ b/src/miniapps/ControlCentreMiniApp/ControlCentreMiniApp.css @@ -7,7 +7,15 @@ width: 280px; /* Semi-translucent white with blur, to mimic macOS style */ background: rgba(255, 255, 255, 0.5); + + /* Ensures the blur effect works in Safari */ + -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); + + /* Prevents artefacts in Safari */ + -webkit-background-clip: padding-box; + background-clip: padding-box; + margin-top: 6px; padding-left: 10px; padding-right: 10px; @@ -23,7 +31,12 @@ /* Each “segment” or “card” in the control center */ .cc-segment { - backdrop-filter: blur(1px); + background: rgba(255, 255, 255, 0.5); + + /* Clip the background to avoid artefacts */ + -webkit-background-clip: padding-box; + background-clip: padding-box; + border-radius: 12px; padding: 12px 16px; margin-top: 10px; diff --git a/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.css b/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.css index 54c55dfd..eba97ae8 100644 --- a/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.css +++ b/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.css @@ -30,7 +30,7 @@ .song-title-container { overflow: hidden; white-space: nowrap; - margin-top: -7px; + margin-top: -5px; margin-left: 2px; } diff --git a/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.jsx b/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.jsx index 5b69af57..7a11bbc7 100644 --- a/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.jsx +++ b/src/miniapps/ControlCentreMiniApp/ControlCentreWidgets/MusicControl/MusicControl.jsx @@ -36,11 +36,12 @@ function MusicControl() { #888 80%)` }; - // Convert seconds to mm:ss + // Convert seconds to mm:ss, cutting off milliseconds const formatTime = (secs) => { if (!secs || secs < 0) secs = 0; - const minutes = Math.floor(secs / 60); - const seconds = secs % 60; + const totalSeconds = Math.floor(secs); // <-- Truncate decimal part + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; }; diff --git a/src/services/AppsContext/AppsContext.jsx b/src/services/AppsContext/AppsContext.jsx new file mode 100644 index 00000000..f0f21348 --- /dev/null +++ b/src/services/AppsContext/AppsContext.jsx @@ -0,0 +1,73 @@ +import React, { createContext, useState } from 'react'; +import defaultIcon from '../../media/icons/defaultapp.png'; // Example icon +import folderIcon from '../../media/icons/folder.webp'; +import SortingAlgorithms from '../../apps/SortingAlgorithms/SortingAlgorithms.jsx'; +import linkedinIcon from '../../media/icons/linkedin.png'; +import githubIcon from '../../media/icons/github.png'; +import awaiIcon from '../../media/icons/awai.png'; + +/** + * The initial list of apps. + * The `indock` field determines if the icon should be rendered on the desktop. + * If `indock` is true, the icon will not be rendered on the desktop. + */ +const initialAppsList = [ + { + id: 'sorting-algorithms', + name: 'Sorting Algorithms', + icon: defaultIcon, + component: SortingAlgorithms, + priority: 4, + indock: false, + }, + { + id: 'untitled-folder', + name: 'untitled folder', + icon: folderIcon, + component: null, + priority: 5, + indock: true, // Initially in the dock, so not on desktop + }, + { + id: 'linkedin', + name: 'LinkedIn', + icon: linkedinIcon, + link: 'http://linkedin.com/in/htdguide/', + component: null, + priority: 2, + indock: false, + }, + { + id: 'github', + name: 'Github', + icon: githubIcon, + link: 'https://github.com/htdguide', + component: null, + priority: 1, + indock: false, // Initially in the dock, so not on desktop + }, + { + id: 'awai', + name: 'ApplyWithAI', + icon: awaiIcon, + link: 'https://applywithai.com', + component: null, + priority: 3, + indock: false, + }, +]; + +export const AppsContext = createContext({ + apps: initialAppsList, + setApps: () => {}, +}); + +export const AppsProvider = ({ children }) => { + const [apps, setApps] = useState(initialAppsList); + + return ( + + {children} + + ); +}; diff --git a/src/services/MusicService/MusicService.jsx b/src/services/MusicService/MusicService.jsx index 8625df15..5c0b4795 100644 --- a/src/services/MusicService/MusicService.jsx +++ b/src/services/MusicService/MusicService.jsx @@ -1,283 +1,430 @@ // MusicService.jsx import React, { - createContext, - useContext, - useRef, - useState, - useEffect, - useCallback, - } from 'react'; - - // Import jsmediatags from the UMD build to avoid Vite dep-scan issues - import jsmediatags from 'jsmediatags/dist/jsmediatags.min.js'; - - // Import our config - import musicServiceConfig from '../../configs/MusicServiceConfig/MusicServiceConfig'; - - // Create the context - const MusicServiceContext = createContext(null); - - // Custom hook - export function useMusicService() { - return useContext(MusicServiceContext); - } - - export function MusicServiceProvider({ children }) { - // We'll store the loaded list of MP3 "entries" from the JSON. - const [musicList, setMusicList] = useState([]); - - // The