Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic wallpapers #114

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file removed public/assets/wallpapers/13.jpg
Binary file not shown.
Binary file removed public/assets/wallpapers/14.jpg
Binary file not shown.
Binary file removed public/assets/wallpapers/15.jpg
Binary file not shown.
Binary file removed public/assets/wallpapers/16.jpg
Binary file not shown.
Binary file removed public/assets/wallpapers/24-0.jpg
Binary file not shown.
Binary file modified public/assets/wallpapers/24-7.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/assets/wallpapers/24-8.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/apps/AppNexus.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AppID } from '__/stores/apps.store';
import { lazy } from 'react';
import { lazy } from 'preact/compat';

type AppNexusProps = {
appID: AppID;
Expand All @@ -9,13 +9,15 @@ type AppNexusProps = {
const Calculator = lazy(() => import('./Calculator/Calculator'));
const VSCode = lazy(() => import('./VSCode/VSCode'));
const Calendar = lazy(() => import('./Calendar/Calendar'));
const SystemPreferences = lazy(() => import('./SystemPreferences/SystemPreferences'));

const PlaceholderApp = lazy(() => import('./Placeholder/Placeholder'));

export const AppNexus = ({ appID, isBeingDragged }: AppNexusProps) => {
if (appID === 'calculator') return <Calculator />;
if (appID === 'vscode') return <VSCode isBeingDragged={isBeingDragged} />;
if (appID === 'calendar') return <Calendar />;
if (appID === 'system-preferences') return <SystemPreferences />;

return <PlaceholderApp appID={appID} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container {
background-color: var(--app-color-light);
}

.header {
padding: 1rem 1rem;

width: 100%;

position: absolute;
top: 0;
left: 0;
}
14 changes: 14 additions & 0 deletions src/components/apps/SystemPreferences/SystemPreferences.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { WallpaperSwitcher } from './WallpaperSwitcher/WallpaperSwitcher';
import css from './SystemPreferences.module.scss';
import clsx from 'clsx';

export const SystemPreferences = () => {
return (
<section class={css.container}>
<header class={clsx('app-window-drag-handle', css.header)}></header>
<WallpaperSwitcher />
</section>
);
};

export default SystemPreferences;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const WallpaperSwitcher = () => {
return <></>;
};

export default WallpaperSwitcher;
63 changes: 63 additions & 0 deletions src/data/wallpapers/wallpapers.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Theme } from '__/stores/theme.store';

export type Wallpaper = {
name: string;
type: 'standalone' | 'automatic' | 'dynamic';

/** Timestamps definition in terms of when a new wallpaper should take effect */
wallpaperTimestamps?: Record<number, string>;

/** Timestamps for when the theme changes */
themeTimestamps?: Record<number, Theme>;
};

const createWallpapersConfig = <TConfig>(et: Record<keyof TConfig, Wallpaper>) => et;

export const wallpapersConfig = createWallpapersConfig({
'big-sur-graphic': {
name: 'Big Sur Graphic',
type: 'automatic',
wallpaperTimestamps: {
7: '3-2',
18: '3-1',
},
themeTimestamps: {
7: 'light',
18: 'dark',
},
},

monterey: {
name: 'Monterey',
type: 'automatic',
wallpaperTimestamps: {
7: '37-2',
18: '37-1',
},
themeTimestamps: {
7: 'light',
18: 'dark',
},
},

catalina: {
name: 'Catalina',
type: 'dynamic',
wallpaperTimestamps: {
7: '24-2',
9: '24-3',
11: '24-4',
13: '24-5',
15: '24-6',
16: '24-7',
17: '24-8',
18: '24-1',
},
themeTimestamps: {
9: 'light',
17: 'dark',
},
},
});

export type WallpaperID = keyof typeof wallpapersConfig;
16 changes: 16 additions & 0 deletions src/helpers/smaller-closest-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Find the closest smaller number in an array
* @param arr Ascendingly sorted array
* @param value Value to check against
*/
export function smallerClosestValue(arr: number[], value: number) {
let prevVal = arr[0];

for (const val of arr) {
if (val > value) return prevVal;
if (val == value) return val;
prevVal = val;
}

return arr[arr.length - 1];
}
3 changes: 3 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ export { useTheme } from './use-theme';
export { useTimeout } from './use-timeout';
export { useContextMenu } from './use-context-menu';
export { useFocusOutside } from './use-focus-outside';
export { useTimelyWallpapers } from './use-timely-wallpapers';
export { useWallpaperName } from './use-wallpaper-name';
export { usePrevious } from './use-previous';
11 changes: 11 additions & 0 deletions src/hooks/use-previous.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useEffect, useRef } from 'preact/hooks';

export function usePrevious<T>(value: T) {
const ref = useRef<T>();

useEffect(() => {
ref.current = value;
});

return ref.current;
}
76 changes: 76 additions & 0 deletions src/hooks/use-timely-wallpapers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useAtom } from 'jotai';
import { useEffect } from 'preact/hooks';
import { wallpapersConfig } from '__/data/wallpapers/wallpapers.config';
import { smallerClosestValue } from '__/helpers/smaller-closest-value';
import { wallpaperImageStore } from '__/stores/wallpapers.store';
import { useInterval } from './use-interval';
import { useTheme } from './use-theme';
import { useWallpaperName } from './use-wallpaper-name';

export const useTimelyWallpapers = () => {
const [wallpaperName] = useWallpaperName();
const [currWallpaperImg, setCurrWallpaperImg] = useAtom(wallpaperImageStore);
const [, setTheme] = useTheme();

function handleWallpaper() {
const date = new Date();
const hour = date.getHours();

const wallpaperTimestampsMap = wallpapersConfig[wallpaperName].wallpaperTimestamps;
const timestamps = Object.keys(wallpaperTimestampsMap);

const minTimestamp = Math.min(...timestamps);
const maxTimestamp = Math.max(...timestamps);

if (hour > maxTimestamp || hour < minTimestamp) {
// Go for the min timestamp value
setCurrWallpaperImg(wallpaperTimestampsMap?.[maxTimestamp] || currWallpaperImg);
return;
}

// Now set the right timestamp
const chosenTimeStamp = smallerClosestValue(timestamps, hour);
setCurrWallpaperImg(wallpaperTimestampsMap?.[chosenTimeStamp] || currWallpaperImg);
}

function handleTheme() {
const date = new Date();
const hour = date.getHours();

const themeTimestampsMap = wallpapersConfig[wallpaperName].themeTimestamps;
const timestamps = Object.keys(themeTimestampsMap);

const minTimestamp = Math.min(...timestamps);
const maxTimestamp = Math.max(...timestamps);

if (hour > maxTimestamp || hour < minTimestamp) {
// Go for the min timestamp value
setTheme('dark');
return;
}

// Now set the right timestamp
const chosenTimeStamp = smallerClosestValue(timestamps, hour);
setTheme(themeTimestampsMap?.[chosenTimeStamp] || 'light');
}

function handler() {
if (wallpapersConfig[wallpaperName].type === 'standalone') return;
// console.log({ wallpaperName });
/** Only dynamic and light/dark wallpaper logic to tackle */
// Now check if user really wants the change to happen.

handleTheme();
handleWallpaper();
}

useEffect(() => {
handler();
}, [wallpaperName]);

useInterval(() => {
handler();
}, 60 * 1000);

return [currWallpaperImg, setCurrWallpaperImg] as const;
};
8 changes: 8 additions & 0 deletions src/hooks/use-wallpaper-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useAtom } from 'jotai';
import { wallpaperNameStore } from '__/stores/wallpapers.store';

export function useWallpaperName() {
const [wallpaperName, setWallpaperName] = useAtom(wallpaperNameStore);

return [wallpaperName, setWallpaperName] as const;
}
7 changes: 7 additions & 0 deletions src/stores/wallpapers.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import type { WallpaperID } from '__/data/wallpapers/wallpapers.config';

export const wallpaperImageStore = atom('24-3');

export const wallpaperNameStore = atomWithStorage<WallpaperID>('wallpaper:name', 'catalina');
2 changes: 2 additions & 0 deletions src/views/desktop/Desktop.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

will-change: background-image;

transition: background-image 200ms ease-in;

background-repeat: no-repeat;
background-size: cover;
background-position: center;
Expand Down
37 changes: 26 additions & 11 deletions src/views/desktop/Desktop.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { useEffect, useRef } from 'preact/hooks';
import { useEffect, useRef, useState } from 'preact/hooks';
import { ContextMenu } from '__/components/Desktop/ContextMenu/ContextMenu';
import { StartupChime } from '__/components/Desktop/StartupChime';
import { WindowsArea } from '__/components/Desktop/Window/WindowsArea';
import { Dock } from '__/components/dock/Dock';
import { TopBar } from '__/components/topbar/TopBar';
import { useTimelyWallpapers } from '__/hooks';
import { useWallpaperName } from '__/hooks/use-wallpaper-name';
import css from './Desktop.module.scss';

const DarkBackground = '/assets/wallpapers/3-1.jpg';
const LightBackground = '/assets/wallpapers/3-2.jpg';

export const Desktop = () => {
const outerRef = useRef<HTMLDivElement>();

const [wallpaperName, setWallpaperName] = useWallpaperName();
const [currWallpaperImg] = useTimelyWallpapers();

const [wallpaper, setWallpaper] = useState('');

useEffect(() => {
preloadImage(DarkBackground);
preloadImage(LightBackground);
}, []);
async function main() {
await preloadImage(`/assets/wallpapers/${currWallpaperImg}.jpg`);
setWallpaper(currWallpaperImg);
}
main();
}, [currWallpaperImg]);

return (
<>
Expand All @@ -28,12 +35,20 @@ export const Desktop = () => {

<StartupChime />

<div class={css.backgroundCover} aria-hidden="true" />
<div
class={css.backgroundCover}
style={{ backgroundImage: `url(/assets/wallpapers/${wallpaper}.jpg)` }}
aria-hidden="true"
/>
</>
);
};

function preloadImage(path: string) {
const img = new Image();
img.src = path;
async function preloadImage(path: string) {
return new Promise((resolve) => {
const img = new Image();
img.src = path;

img.onload = resolve;
});
}