Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 233 additions & 26 deletions hyperdrive/packages/app-store/app-store/src/icon

Large diffs are not rendered by default.

Binary file modified hyperdrive/packages/app-store/app-store/src/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions hyperdrive/packages/homepage/ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions hyperdrive/packages/homepage/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@hello-pangea/dnd": "^16.6.0",
"@tailwindcss/vite": "^4.1.11",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ export const AppContainer: React.FC<AppContainerProps> = ({ app, isVisible }) =>

return (
<div
className={`app-container fixed inset-0 bg-white z-30 transition-transform duration-300
className={`app-container fixed inset-0 dark:bg-black bg-white z-30 transition-transform duration-300
${isVisible ? 'translate-x-0' : 'translate-x-full'}`}
>
{hasError ? (
<div className="w-full h-full flex items-center justify-center bg-gradient-to-b from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-900">
<div className="w-full h-full flex flex-col items-center justify-center bg-gradient-to-b from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-900">
<div className="text-center">
<div className="text-6xl mb-4">⚠️</div>
<h2 className="text-xl font-semibold mb-2 text-gray-800 dark:text-gray-200">Failed to load {app.label}</h2>
Expand All @@ -41,7 +41,7 @@ export const AppContainer: React.FC<AppContainerProps> = ({ app, isVisible }) =>
<>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-gradient-to-b from-gray-100 to-gray-200 dark:from-gray-800 dark:to-gray-900 z-10">
<div className="text-center">
<div className="flex flex-col items-center justify-center gap-2">
<div className="w-12 h-12 border-4 border-gray-300 dark:border-gray-600 border-t-blue-500 rounded-full animate-spin mb-4"></div>
<p className="text-gray-600 dark:text-gray-400">Loading {app.label}...</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { HomepageApp } from '../../../types/app.types';
import { useNavigationStore } from '../../../stores/navigationStore';
import { usePersistenceStore } from '../../../stores/persistenceStore';
import classNames from 'classnames';
import { BsX } from 'react-icons/bs';

interface AppIconProps {
app: HomepageApp;
Expand Down Expand Up @@ -34,7 +35,7 @@ export const AppIcon: React.FC<AppIconProps> = ({

return (
<div
className={classNames('app-icon relative flex flex-col items-center justify-center rounded-2xl cursor-pointer select-none transition-all', {
className={classNames('app-icon relative flex gap-1 flex-col items-center justify-center rounded-2xl cursor-pointer select-none transition-all', {
'scale-95': isPressed,
'scale-100': !isPressed,
'animate-wiggle': isEditMode && isFloating,
Expand All @@ -56,7 +57,7 @@ export const AppIcon: React.FC<AppIconProps> = ({
onClick={handleRemove}
className="absolute -top-2 -right-2 w-6 h-6 !p-0 !bg-red-500 !text-white !rounded-full text-xs z-10 shadow-lg hover:!bg-red-600 transition-colors"
>
×
<BsX />
</button>
)}

Expand All @@ -73,7 +74,7 @@ export const AppIcon: React.FC<AppIconProps> = ({
</div>

{showLabel && (
<span className="text-xs text-center max-w-full truncate text-black dark:text-white drop-shadow-md mt-1">
<span className="text-xs text-center max-w-full truncate px-2 py-1 bg-black/5 dark:bg-white/5 rounded-full backdrop-blur-xl">
{app.label}
</span>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useNavigationStore } from '../../../stores/navigationStore';
import classNames from 'classnames';
import { BsChevronLeft, BsClock } from 'react-icons/bs';

export const GestureZone: React.FC = () => {
const { toggleRecentApps, runningApps, currentAppId, switchToApp } = useNavigationStore();
const { toggleRecentApps, runningApps, currentAppId, switchToApp, isRecentAppsOpen } = useNavigationStore();
const [touchStart, setTouchStart] = useState<{ x: number; y: number } | null>(null);
const [isActive, setIsActive] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const [_isHovered, setIsHovered] = useState(false);

// Touch handlers
const handleTouchStart = (e: React.TouchEvent) => {
Expand Down Expand Up @@ -52,22 +54,36 @@ export const GestureZone: React.FC = () => {
toggleRecentApps();
};

useEffect(() => {
if (!isRecentAppsOpen) {
setTouchStart(null);
setIsActive(false);
}
}, [isRecentAppsOpen]);

return (
<>
<div
className={`gesture-zone fixed right-0 top-0 w-8 h-full z-40 transition-all cursor-pointer
${isActive ? 'bg-white/20 w-12' : ''}
${isHovered && !isActive ? 'bg-gradient-to-l from-white/10 to-transparent' : ''}`}
className={classNames("gesture-zone fixed right-0 w-12 z-40 transition-transform cursor-pointer",
{
'bg-radial-[at_100%_50%] from-black/20 dark:from-white/20 to-transparent w-12 h-full top-0': isActive,
'flex flex-col place-items-center place-content-center h-1/2 top-1/4': !isActive
})}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
onClick={handleClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
/>
>
{!isActive && <div className="bg-black/20 dark:bg-white/20 h-8 backdrop-blur-xl px-1 py-6 rounded-l-xl flex items-center justify-center self-end gap-1">
<BsChevronLeft className="text-xs" />
<BsClock className="text-lg" />
</div>}
</div>
{/* Desktop hint */}
{isHovered && !isActive && (
<div className="fixed right-12 top-1/2 transform -translate-y-1/2 bg-black/90 backdrop-blur text-white px-4 py-3 rounded-lg text-sm pointer-events-none z-50 shadow-xl">
{/* {isHovered && !isActive && (
<div className="hidden md:block fixed right-12 top-1/2 transform -translate-y-1/2 bg-black/90 backdrop-blur text-white px-4 py-3 rounded-lg text-sm pointer-events-none z-50 shadow-xl">
<div className="flex items-center gap-2 mb-1">
<kbd className="px-2 py-1 bg-white/20 rounded text-xs">Click</kbd>
<span>or</span>
Expand All @@ -83,7 +99,7 @@ export const GestureZone: React.FC = () => {
<span>Home</span>
</div>
</div>
)}
)} */}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { Draggable } from './Draggable';
import { AppIcon } from './AppIcon';
import { Widget } from './Widget';
import type { HomepageApp } from '../../../types/app.types';
import { BsCheck, BsImage, BsLayers, BsSearch } from 'react-icons/bs';
import { BsCheck, BsClock, BsGridFill, BsImage, BsLayers, BsSearch } from 'react-icons/bs';
import classNames from 'classnames';

export const HomeScreen: React.FC = () => {
const { apps } = useAppStore();
const { homeScreenApps, dockApps, appPositions, widgetSettings, toggleWidget, moveItem, backgroundImage, setBackgroundImage, addToDock, removeFromDock, isInitialized, setIsInitialized, addToHomeScreen } = usePersistenceStore();
const { isEditMode, setEditMode } = useAppStore();
const { toggleAppDrawer } = useNavigationStore();
const { toggleAppDrawer, toggleRecentApps } = useNavigationStore();
const [draggedAppId, setDraggedAppId] = React.useState<string | null>(null);
const [touchDragPosition, setTouchDragPosition] = React.useState<{ x: number; y: number } | null>(null);
const [showBackgroundSettings, setShowBackgroundSettings] = React.useState(false);
Expand All @@ -30,7 +30,8 @@ export const HomeScreen: React.FC = () => {
addToHomeScreen("main:app-store:sys");
addToHomeScreen("contacts:contacts:sys");
addToHomeScreen("settings:settings:sys");
addToHomeScreen("homepage:homepage:sys"); // actually the clock widget
addToDock("settings:settings:sys", 0);
// addToHomeScreen("homepage:homepage:sys"); // actually the clock widget
setBackgroundImage('/large-background-vector.svg');
}, [isInitialized]);

Expand Down Expand Up @@ -197,13 +198,19 @@ export const HomeScreen: React.FC = () => {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}, []);

const calculateAppIconPosition = (index: number, totalApps: number) => {
const calculateAppIconPosition = (appId: string, index: number, totalApps: number) => {
const isMobile = window.innerWidth < 768; // Tailwind md breakpoint
const spacing = isMobile ? 5 : 10;
const screenPortion = window.innerWidth / totalApps
// if no setting, row along the 0.75 height of the screen
const y = 0.75 * window.innerHeight
const dockHeight = 120;
const iconHeight = 96;
// if no setting, row along screen bottom above dock
const y = window.innerHeight - dockHeight - spacing - iconHeight
// space evenly
const x = index * (screenPortion + spacing) + screenPortion / 4
// ensure position sets so future movements do not cause a jump
moveItem(appId, { x, y });
console.log('autosetting position', appId, { x, y });
return { x, y }
}

Expand Down Expand Up @@ -263,7 +270,7 @@ export const HomeScreen: React.FC = () => {
{floatingApps
.filter(app => app.label.toLowerCase().includes(searchQuery.toLowerCase()))
.map((app, index, allApps) => {
const position = appPositions[app.id] || calculateAppIconPosition(index, allApps.length);
const position = appPositions[app.id] || calculateAppIconPosition(app.id, index, allApps.length);

return (
<Draggable
Expand Down Expand Up @@ -296,11 +303,11 @@ export const HomeScreen: React.FC = () => {

{/* Dock at bottom */}
<div
className="dock-area absolute bottom-4 left-1/2 transform -translate-x-1/2"
className="dock-area absolute bottom-0 md:bottom-4 left-1/2 transform -translate-x-1/2"
onDragOver={handleDockDragOver}
onDrop={(e) => handleDockDrop(e, dockAppsList.length)}
>
<div className="bg-white dark:bg-black/60 backdrop-blur-xl rounded-3xl p-3 flex items-center gap-2 shadow-2xl border border-white/20">
<div className="bg-white dark:bg-black/60 backdrop-blur-xl rounded-t-3xl md:rounded-b-3xl p-3 flex items-center gap-2 shadow-2xl border-b-0 md:border-b border border-white/20">
{/* Dock slots */}
{Array.from({ length: 4 }).map((_, index) => {
const app = dockAppsList[index];
Expand All @@ -316,57 +323,65 @@ export const HomeScreen: React.FC = () => {
}}
>
{app ? (
isEditMode ? (
<div
draggable
onDragStart={(e) => {
e.dataTransfer.setData('appId', app.id);
e.dataTransfer.effectAllowed = 'move';
}}
onDragEnd={() => {
// If dropped outside, it's handled by floating area
}}
onTouchStart={handleTouchStart(app.id)}
onTouchMove={handleTouchMove}
onTouchEnd={(e) => {
if (!draggedAppId || !touchDragPosition) return;

const touch = e.changedTouches[0];
const element = document.elementFromPoint(touch.clientX, touch.clientY);

// If not dropped on dock, remove from dock
if (!element?.closest('.dock-area')) {
removeFromDock(app.id);
// Place at drop position
const dockHeight = 120;
const maxY = window.innerHeight - 80 - dockHeight;
moveItem(app.id, {
x: touch.clientX - 40,
y: Math.min(touch.clientY - 40, maxY)
});
}

setDraggedAppId(null);
setTouchDragPosition(null);
}}
>
<AppIcon app={app} isEditMode={false} showLabel={false} />
</div>
) : (
<AppIcon app={app} isEditMode={false} showLabel={false} />
)
<div
draggable
onDragStart={(e) => {
e.dataTransfer.setData('appId', app.id);
e.dataTransfer.effectAllowed = 'move';
}}
onDragEnd={() => {
// If dropped outside, it's handled by floating area
}}
onTouchStart={handleTouchStart(app.id)}
onTouchMove={handleTouchMove}
onTouchEnd={(e) => {
if (!draggedAppId || !touchDragPosition) return;

const touch = e.changedTouches[0];
const element = document.elementFromPoint(touch.clientX, touch.clientY);

// If not dropped on dock, remove from dock
if (!element?.closest('.dock-area')) {
removeFromDock(app.id);
// Place at drop position
const dockHeight = 120;
const maxY = window.innerHeight - 80 - dockHeight;
moveItem(app.id, {
x: touch.clientX - 40,
y: Math.min(touch.clientY - 40, maxY)
});
}

setDraggedAppId(null);
setTouchDragPosition(null);
}}
>
<AppIcon
app={app}
isEditMode={isEditMode}
showLabel={false}
/>
</div>
) : (
<div className="w-full h-full border-2 border-dashed border-black/20 dark:border-white/20 rounded-2xl transition-all hover:border-black/40 dark:hover:border-white/40 hover:bg-black/5 dark:hover:bg-white/5" />
)}
</div>
);
})}
<div className="w-px h-12 bg-white/20 mx-1" />
<div className="w-px h-12 bg-black/20 dark:bg-white/20 mx-1" />
<button
onClick={toggleAppDrawer}
className="w-16 h-16 !bg-iris !text-neon !rounded-xl text-2xl hover:!bg-neon hover:!text-iris transition-all shadow-lg"
className="w-16 h-16 !bg-iris !text-neon !rounded-xl text-2xl hover:!bg-neon hover:!text-iris flex-col justify-center !gap-1"
>
<BsGridFill />
<span className="text-xs">Apps</span>
</button>
<button
onClick={toggleRecentApps}
className="!hidden md:!flex w-16 h-16 !bg-iris !text-neon !rounded-xl text-2xl hover:!bg-neon hover:!text-iris flex-col justify-center !gap-1"
>
<BsClock />
<span className="text-xs">Recent</span>
</button>
</div>
</div>
Expand Down Expand Up @@ -519,14 +534,14 @@ export const HomeScreen: React.FC = () => {
</div>

{/* Desktop hint */}
<div className="hidden md:block absolute bottom-32 left-1/2 -translate-x-1/2 text-black/30 dark:text-white/30 text-xs bg-white/50 dark:bg-black/50 backdrop-blur rounded-lg px-3 py-2">
{/* <div className="hidden md:block absolute bottom-24 left-1/2 -translate-x-1/2 text-black/30 dark:text-white/30 text-xs bg-white/50 dark:bg-black/50 backdrop-blur rounded-lg px-3 py-2">
<div className="flex items-center gap-4">
<span><kbd className="p-1 bg-black/10 dark:bg-white/10 rounded text-xs">A</kbd> All apps</span>
<span><kbd className="p-1 bg-black/10 dark:bg-white/10 rounded text-xs">S</kbd> Recent apps</span>
<span><kbd className="p-1 bg-black/10 dark:bg-white/10 rounded text-xs">H</kbd> Home</span>
<span><kbd className="p-1 bg-black/10 dark:bg-white/10 rounded text-xs">1-9</kbd> Switch apps</span>
</div>
</div>
</div> */}
</div>
</div>
);
Expand Down
Loading