Archived
Add ability to favorite backgrounds
This commit is contained in:
@@ -1,54 +1,20 @@
|
|||||||
import { Random } from 'unsplash-js/dist/methods/photos/types';
|
import { createEffect, createSignal } from 'solid-js';
|
||||||
import { createLocalStore } from '~/lib/create-local-store';
|
import { useSettings } from '~/state/SettingsProvider';
|
||||||
import { random } from '~/lib/math';
|
|
||||||
import { getRandomBackground, unsplashApi } from '~/api/unsplash';
|
|
||||||
|
|
||||||
import style from './Background.module.css';
|
import style from './Background.module.css';
|
||||||
import { BACKGROUND_FETCH_MINS } from '~/config';
|
|
||||||
import { createEffect, createMemo, createSignal } from 'solid-js';
|
|
||||||
|
|
||||||
type BackgroundStorage = {
|
|
||||||
fetched_at: number;
|
|
||||||
backgrounds: Random[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Background() {
|
export function Background() {
|
||||||
const [backgrounds, setBackgrounds] = createLocalStore<BackgroundStorage>(
|
const { currentBackground } = useSettings();
|
||||||
'start-page-background',
|
|
||||||
{
|
|
||||||
fetched_at: 0,
|
|
||||||
backgrounds: null,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldFetch =
|
|
||||||
!backgrounds?.backgrounds ||
|
|
||||||
backgrounds.fetched_at < new Date().getTime() - 1000 * 60 * BACKGROUND_FETCH_MINS;
|
|
||||||
|
|
||||||
if (shouldFetch) {
|
|
||||||
getRandomBackground().then((r) =>
|
|
||||||
setBackgrounds({
|
|
||||||
fetched_at: new Date().getTime(),
|
|
||||||
backgrounds: (r?.response as Random[]) ?? null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const backgroundUrls = createMemo(() => {
|
|
||||||
const backgroundLength = backgrounds?.backgrounds?.length ?? 0;
|
|
||||||
const background = random(backgroundLength - 1);
|
|
||||||
return backgrounds?.backgrounds?.[background].urls;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [fullLoaded, setFullLoaded] = createSignal(false);
|
const [fullLoaded, setFullLoaded] = createSignal(false);
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!backgroundUrls()) {
|
if (!currentBackground()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var curImg = new Image();
|
var curImg = new Image();
|
||||||
curImg.src = backgroundUrls()!.full;
|
curImg.src = currentBackground()!.urls.full;
|
||||||
curImg.onload = () => setFullLoaded(true);
|
curImg.onload = () => setFullLoaded(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,7 +22,7 @@ export function Background() {
|
|||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
class={style.Background}
|
class={style.Background}
|
||||||
src={fullLoaded() ? backgroundUrls()?.full : backgroundUrls()?.thumb}
|
src={fullLoaded() ? currentBackground()?.urls.full : currentBackground()?.urls.thumb}
|
||||||
/>
|
/>
|
||||||
{!fullLoaded() ? <div class={style.Overlay}></div> : null}
|
{!fullLoaded() ? <div class={style.Overlay}></div> : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -43,13 +43,18 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Settings {
|
.SettingsWrapper {
|
||||||
cursor: pointer;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5rem;
|
||||||
margin-top: clamp(.5rem, 1vw, 1.5rem);
|
margin-top: clamp(.5rem, 1vw, 1.5rem);
|
||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.SettingsWrapper > * {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.Sidebar {
|
.Sidebar {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import { Tooltip } from '@hope-ui/solid';
|
||||||
|
import { IoSave } from 'solid-icons/io';
|
||||||
import { createTagOrder } from '~/api/get-apps';
|
import { createTagOrder } from '~/api/get-apps';
|
||||||
|
import { useSettings } from '~/state/SettingsProvider';
|
||||||
import { Settings } from '../widgets/Settings';
|
import { Settings } from '../widgets/Settings';
|
||||||
|
|
||||||
import style from './Sidebar.module.css';
|
import style from './Sidebar.module.css';
|
||||||
|
import { HiSolidStar } from 'solid-icons/hi';
|
||||||
|
|
||||||
type SidebarProps = {
|
type SidebarProps = {
|
||||||
activeTag?: string | null;
|
activeTag?: string | null;
|
||||||
@@ -9,6 +14,7 @@ type SidebarProps = {
|
|||||||
|
|
||||||
export function Sidebar(props: SidebarProps) {
|
export function Sidebar(props: SidebarProps) {
|
||||||
const tagOrder = createTagOrder();
|
const tagOrder = createTagOrder();
|
||||||
|
const { onSaveBackground } = useSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.Sidebar}>
|
<div class={style.Sidebar}>
|
||||||
@@ -23,7 +29,14 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<Settings className={style.Settings} />
|
<div class={style.SettingsWrapper}>
|
||||||
|
<Tooltip withArrow placement="top" label="Save background">
|
||||||
|
<HiSolidStar size={18} onClick={onSaveBackground} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip withArrow placement="top" label="Settings">
|
||||||
|
<Settings />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,17 @@ export function Settings(props: SettingsProps) {
|
|||||||
>
|
>
|
||||||
Show locations
|
Show locations
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={appSettings.useSavedBackgrounds}
|
||||||
|
onChange={(e) =>
|
||||||
|
setAppSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
useSavedBackgrounds: (e.target as any).checked,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Use saved backgrounds
|
||||||
|
</Checkbox>
|
||||||
<a
|
<a
|
||||||
style={{ display: 'flex', 'align-items': 'center', gap: '8px', cursor: 'pointer' }}
|
style={{ display: 'flex', 'align-items': 'center', gap: '8px', cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { random } from './math';
|
||||||
|
|
||||||
|
export function sample<T>(list: T[]): T {
|
||||||
|
if (list.length === 1) {
|
||||||
|
return list[0];
|
||||||
|
}
|
||||||
|
return list[random(list.length - 1)];
|
||||||
|
}
|
||||||
@@ -1,27 +1,67 @@
|
|||||||
import { JSX, createContext, useContext } from 'solid-js';
|
import { Accessor, JSX, createContext, createMemo, useContext } from 'solid-js';
|
||||||
import { SetStoreFunction } from 'solid-js/store';
|
import { SetStoreFunction } from 'solid-js/store';
|
||||||
|
import { Random } from 'unsplash-js/dist/methods/photos/types';
|
||||||
|
import { sample } from '~/lib/array';
|
||||||
import { createLocalStore } from '~/lib/create-local-store';
|
import { createLocalStore } from '~/lib/create-local-store';
|
||||||
|
import { SavedBackgrounds } from '~/types';
|
||||||
|
import { createBackgrounds } from './create-backgrounds';
|
||||||
|
import { random } from '~/lib/math';
|
||||||
|
|
||||||
type Settings = { showLocations: boolean; viewAsTable: boolean };
|
type Settings = { showLocations: boolean; viewAsTable: boolean; useSavedBackgrounds: boolean };
|
||||||
|
|
||||||
type SettingsContextType = {
|
type SettingsContextType = {
|
||||||
appSettings: Settings;
|
appSettings: Settings;
|
||||||
setAppSettings: SetStoreFunction<Settings>;
|
setAppSettings: SetStoreFunction<Settings>;
|
||||||
|
savedBackgrounds: SavedBackgrounds;
|
||||||
|
onSaveBackground(): void;
|
||||||
|
currentBackground: Accessor<Random | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsContext = createContext<SettingsContextType>({
|
const SettingsContext = createContext<SettingsContextType>({
|
||||||
appSettings: { showLocations: false, viewAsTable: false },
|
appSettings: { showLocations: false, viewAsTable: false, useSavedBackgrounds: false },
|
||||||
setAppSettings: () => {},
|
setAppSettings: () => {},
|
||||||
|
currentBackground: () => undefined,
|
||||||
|
savedBackgrounds: [],
|
||||||
|
onSaveBackground: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SettingsProvider(props: { children: JSX.Element }) {
|
export function SettingsProvider(props: { children: JSX.Element }) {
|
||||||
const [appSettings, setAppSettings] = createLocalStore<Settings>('start-page-app-settings', {
|
const [appSettings, setAppSettings] = createLocalStore<Settings>('start-page-app-settings', {
|
||||||
showLocations: false,
|
showLocations: false,
|
||||||
viewAsTable: false,
|
viewAsTable: false,
|
||||||
|
useSavedBackgrounds: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [savedBackgrounds, setSavedBackgrounds] = createLocalStore<SavedBackgrounds>(
|
||||||
|
'start-page-saved-backgrounds',
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { currentBackground: currentRandomBackground } = createBackgrounds();
|
||||||
|
|
||||||
|
const onSaveBackground = () => {
|
||||||
|
setSavedBackgrounds((prev) => {
|
||||||
|
if (prev.find((background) => background.id === currentRandomBackground()?.id)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return [...prev, currentRandomBackground()!];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentBackground = createMemo(() =>
|
||||||
|
appSettings.useSavedBackgrounds ? sample(savedBackgrounds) : currentRandomBackground()
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContext.Provider value={{ appSettings, setAppSettings }}>
|
<SettingsContext.Provider
|
||||||
|
value={{
|
||||||
|
currentBackground,
|
||||||
|
savedBackgrounds,
|
||||||
|
onSaveBackground,
|
||||||
|
appSettings,
|
||||||
|
setAppSettings,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { createMemo } from 'solid-js';
|
||||||
|
import { Random } from 'unsplash-js/dist/methods/photos/types';
|
||||||
|
import { getRandomBackground } from '~/api/unsplash';
|
||||||
|
import { BACKGROUND_FETCH_MINS } from '~/config';
|
||||||
|
import { sample } from '~/lib/array';
|
||||||
|
import { createLocalStore } from '~/lib/create-local-store';
|
||||||
|
import { random } from '~/lib/math';
|
||||||
|
import { BackgroundStorage } from '~/types';
|
||||||
|
|
||||||
|
export function createBackgrounds() {
|
||||||
|
const [backgrounds, setBackgrounds] = createLocalStore<BackgroundStorage>(
|
||||||
|
'start-page-background',
|
||||||
|
{ fetched_at: 0, backgrounds: null }
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldFetch =
|
||||||
|
!backgrounds?.backgrounds ||
|
||||||
|
backgrounds.fetched_at < new Date().getTime() - 1000 * 60 * BACKGROUND_FETCH_MINS;
|
||||||
|
|
||||||
|
if (shouldFetch) {
|
||||||
|
getRandomBackground().then((r) =>
|
||||||
|
setBackgrounds({
|
||||||
|
fetched_at: new Date().getTime(),
|
||||||
|
backgrounds: (r?.response as Random[]) ?? null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentBackground = createMemo<Random | undefined>(() =>
|
||||||
|
sample(backgrounds?.backgrounds ?? [])
|
||||||
|
);
|
||||||
|
|
||||||
|
return { backgrounds, currentBackground };
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Random } from 'unsplash-js/dist/methods/photos/types';
|
||||||
|
|
||||||
export type AppDefinition = {
|
export type AppDefinition = {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -31,3 +33,10 @@ export type WeatherData = {
|
|||||||
sunrise: number;
|
sunrise: number;
|
||||||
sunset: number;
|
sunset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BackgroundStorage = {
|
||||||
|
fetched_at: number;
|
||||||
|
backgrounds: Random[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SavedBackgrounds = Random[];
|
||||||
|
|||||||
Reference in New Issue
Block a user