WIP: homepage redo

This commit is contained in:
dr-frmr 2024-07-25 21:58:26 +03:00
parent 3731b9204a
commit c9b2179e89
No known key found for this signature in database
21 changed files with 42 additions and 585 deletions

View File

@ -9,6 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<link rel="stylesheet" href="/index.css">
</head>
<body>

View File

@ -1,10 +0,0 @@
<svg width="122" height="81" viewBox="0 0 122 81" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_651)">
<path d="M89.3665 8.06803L121.5 0.35155L66.5111 0.320312L63.7089 7.69502L0.5 5.7032L54.0253 32.9925L36.1529 80.3203L89.3665 8.06803Z" fill="#FFF5D9"/>
</g>
<defs>
<clipPath id="clip0_6_651">
<rect width="121" height="80" fill="white" transform="translate(0.5 0.320312)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 431 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#FFF5D9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 9l6 6 6-6"/>
</svg>

Before

Width:  |  Height:  |  Size: 188 B

View File

@ -1,18 +1,10 @@
import classNames from "classnames"
import useHomepageStore from "../store/homepageStore"
import { isMobileCheck } from "../utils/dimensions"
import AppDisplay from "./AppDisplay"
const AllApps: React.FC<{ expanded: boolean }> = ({ expanded }) => {
const AllApps: React.FC<{ expanded: boolean }> = () => {
const { apps } = useHomepageStore()
const isMobile = isMobileCheck()
return <div className={classNames('flex-center flex-wrap overflow-y-auto fixed h-screen w-screen backdrop-blur-md transition transition-all ease-in-out duration-500', {
'top-[100vh]': !expanded,
'top-0': expanded,
'gap-4 p-8': isMobile,
'gap-8 p-16': !isMobile,
})}>
return <div>
{apps.length === 0
? <div>Loading apps...</div>
: apps.map(app => <AppDisplay key={app.package_name} app={app} />)}

View File

@ -1,9 +1,5 @@
import classNames from "classnames"
import { HomepageApp } from "../store/homepageStore"
import { FaHeart, FaRegHeart, } from "react-icons/fa6"
import { useState } from "react"
import usePersistentStore from "../store/persistentStore"
import { isMobileCheck } from "../utils/dimensions"
import AppIconPlaceholder from "./AppIconPlaceholder"
interface AppDisplayProps {
@ -11,42 +7,25 @@ interface AppDisplayProps {
}
const AppDisplay: React.FC<AppDisplayProps> = ({ app }) => {
const { favoriteApp, favoriteApps } = usePersistentStore();
const [isHovered, setIsHovered] = useState(false)
const isMobile = isMobileCheck()
return <a
className={classNames("flex-col-center gap-2 relative hover:opacity-90 transition-opacity", {
'cursor-pointer': app?.path,
'cursor-not-allowed': !app?.path,
})}
id={app?.package_name}
href={app?.path}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{app?.base64_icon
? <img
src={app.base64_icon}
className={classNames('rounded', {
'h-8 w-8': isMobile,
'h-16 w-16': !isMobile
})}
/>
? <img src={app.base64_icon} />
: <AppIconPlaceholder
text={app?.state?.our_version || '0'}
size={'small'}
className="h-16 w-16"
/>}
<h6>{app?.label || app?.package_name}</h6>
{app?.path && isHovered && <button
className="absolute p-2 -top-2 -right-2 clear text-sm"
onClick={(e) => {
e.preventDefault()
favoriteApp(app.package_name)
}}
>
{favoriteApps[app.package_name]?.favorite ? <FaHeart /> : <FaRegHeart />}
</button>}
</a>
}

View File

@ -1,9 +1,6 @@
import React from 'react';
import { isMobileCheck } from '../utils/dimensions';
import classNames from 'classnames';
const AppIconPlaceholder: React.FC<{ text: string, className?: string, size: 'small' | 'medium' | 'large' }> = ({ text, className, size }) => {
const AppIconPlaceholder: React.FC<{ text: string }> = ({ text }) => {
const index = text.split('').pop()?.toUpperCase() || '0'
const derivedFilename = `/icons/${index}`
@ -11,16 +8,7 @@ const AppIconPlaceholder: React.FC<{ text: string, className?: string, size: 'sm
return null
}
const isMobile = isMobileCheck()
return <img
src={derivedFilename}
className={classNames('m-0 align-self-center rounded-full', {
'h-32 w-32': !isMobile && size === 'large',
'h-18 w-18': !isMobile && size === 'medium',
'h-12 w-12': isMobile || size === 'small',
}, className)}
/>
return <img src={derivedFilename} />
}
export default AppIconPlaceholder

View File

@ -1,23 +1,14 @@
import useHomepageStore, { HomepageApp } from "../store/homepageStore"
import AppDisplay from "./AppDisplay"
import usePersistentStore from "../store/persistentStore"
import { useEffect, useState } from "react"
import { isMobileCheck } from "../utils/dimensions"
import classNames from "classnames"
import { DragDropContext, Draggable, DropResult, Droppable } from 'react-beautiful-dnd'
import { getFetchUrl } from "../utils/fetch"
const AppsDock: React.FC = () => {
const { apps } = useHomepageStore()
const { favoriteApps, setFavoriteApps } = usePersistentStore()
const [dockedApps, setDockedApps] = useState<HomepageApp[]>([])
useEffect(() => {
let final: HomepageApp[] = []
const dockedApps = Object.entries(favoriteApps)
.filter(([_, { favorite }]) => favorite)
.map(([name, { order }]) => ({ ...apps.find(a => a.package_name === name), order }))
.filter(a => a) as HomepageApp[]
const orderedApps = dockedApps.filter(a => a.order !== undefined && a.order !== null)
const unorderedApps = dockedApps.filter(a => a.order === undefined || a.order === null)
@ -27,11 +18,8 @@ const AppsDock: React.FC = () => {
final = final.filter(a => a)
unorderedApps.forEach(a => final.push(a))
// console.log({ final })
setDockedApps(final)
}, [apps, favoriteApps])
const isMobile = isMobileCheck()
}, [apps])
// a little function to help us with reordering the result
const reorder = (list: HomepageApp[], startIndex: number, endIndex: number) => {
@ -56,18 +44,7 @@ const AppsDock: React.FC = () => {
const packageNames = items.map(app => app.package_name);
const faves = { ...favoriteApps }
packageNames.forEach((name, i) => {
// console.log('setting order for', name, 'to', i)
faves[name].order = i
})
setFavoriteApps(faves)
console.log({ favoriteApps })
fetch(getFetchUrl('/order'), {
fetch('/order', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -84,13 +61,6 @@ const AppsDock: React.FC = () => {
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={classNames('flex-center flex-wrap border border-orange bg-orange/25 p-2 rounded !rounded-xl', {
'gap-8': !isMobile && dockedApps.length > 0,
'gap-4': !isMobile && dockedApps.length === 0,
'mb-4': !isMobile,
'gap-4 mb-2': isMobile,
'flex-col': dockedApps.length === 0
})}
>
{/*dockedApps.length === 0
? <AppDisplay app={apps.find(app => app.package_name === 'app_store')!} />

View File

@ -1,51 +0,0 @@
import classNames from 'classnames'
import React from 'react'
import { hexToRgb, hslToRgb, rgbToHex, rgbToHsl } from '../utils/colors'
import { isMobileCheck } from '../utils/dimensions'
interface ColorDotProps extends React.HTMLAttributes<HTMLSpanElement> {
num: string,
}
const ColorDot: React.FC<ColorDotProps> = ({
num,
...props
}) => {
const isMobile = isMobileCheck()
num = (num || '').replace(/(0x|\.)/g, '')
while (num.length < 6) {
num = '0' + num
}
const leftHsl = rgbToHsl(hexToRgb(num.slice(0, 6)))
const rightHsl = rgbToHsl(hexToRgb(num.length > 6 ? num.slice(num.length - 6) : num))
leftHsl.s = rightHsl.s = 1
const leftColor = rgbToHex(hslToRgb(leftHsl))
const rightColor = rgbToHex(hslToRgb(rightHsl))
const angle = (parseInt(num, 16) % 360) || -45
return (
<div {...props} className={classNames('flex', props.className)}>
<div
className={classNames('m-0 align-self-center border rounded-full outline-black', {
'h-16 w-16 border-4': !isMobile,
'h-8 w-8 border-2': isMobile
})}
style={{
borderTopColor: leftColor,
borderRightColor: rightColor,
borderBottomColor: rightColor,
borderLeftColor: leftColor,
background: `linear-gradient(${angle}deg, ${leftColor} 0 50%, ${rightColor} 50% 100%)`,
filter: 'saturate(0.25)',
opacity: '0.75'
}} />
{props.children}
</div>
)
}
export default ColorDot

View File

@ -1,6 +1,4 @@
import { FaX } from "react-icons/fa6"
import { isMobileCheck } from "../utils/dimensions"
import classNames from "classnames"
interface Props extends React.HTMLAttributes<HTMLDivElement> {
title: string
@ -8,19 +6,12 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
}
export const Modal: React.FC<Props> = ({ title, onClose, children }) => {
const isMobile = isMobileCheck()
return (
<div className="flex fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 place-items-center place-content-center backdrop-blur-md">
<div className={classNames("flex flex-col rounded-lg bg-black py-4 shadow-lg max-h-screen overflow-y-auto", {
'min-w-[500px] px-8 w-1/2': !isMobile,
'px-4 w-full': isMobile
})}>
<div className="flex">
<h1 className="grow">{title}</h1>
<button
className="icon self-start"
onClick={onClose}
>
<div>
<div>
<div>
<h1>{title}</h1>
<button onClick={onClose}>
<FaX />
</button>
</div>

View File

@ -1,8 +1,5 @@
import classNames from "classnames"
import { useEffect, useState } from "react"
import usePersistentStore from "../store/persistentStore"
import useHomepageStore from "../store/homepageStore"
import { isMobileCheck } from "../utils/dimensions"
interface WidgetProps {
package_name: string,
@ -10,32 +7,20 @@ interface WidgetProps {
forceLarge?: boolean
}
const Widget: React.FC<WidgetProps> = ({ package_name, widget, forceLarge }) => {
const Widget: React.FC<WidgetProps> = ({ package_name, widget }) => {
const { apps } = useHomepageStore()
const { widgetSettings } = usePersistentStore()
const isMobile = isMobileCheck()
const isLarge = forceLarge || widgetSettings[package_name]?.size === "large"
const isSmall = !widgetSettings[package_name]?.size || widgetSettings[package_name]?.size === "small"
const [tallScreen, setTallScreen] = useState(window.innerHeight > window.innerWidth)
const [_tallScreen, setTallScreen] = useState(window.innerHeight > window.innerWidth)
useEffect(() => {
setTallScreen(window.innerHeight > window.innerWidth)
}, [window.innerHeight, window.innerWidth])
return <div
className={classNames("self-stretch flex-col-center shadow-lg rounded-lg relative", {
"max-w-1/2 min-w-1/2": isLarge && !isMobile,
"min-w-1/4": isSmall && !isMobile,
"max-w-1/4": isSmall && !tallScreen,
'w-full': isMobile
})}
>
<h6 className="flex-center my-2">
return <div>
<h6>
{apps.find(app => app.package_name === package_name)?.label || package_name}
</h6>
<iframe
srcDoc={widget || ""}
className="grow self-stretch"
data-widget-code={widget}
/>
</div>

View File

@ -1,18 +1,12 @@
import useHomepageStore from "../store/homepageStore"
import Widget from "./Widget"
import usePersistentStore from "../store/persistentStore"
import { isMobileCheck } from "../utils/dimensions"
import classNames from "classnames"
const Widgets = () => {
const { apps } = useHomepageStore()
const { widgetSettings } = usePersistentStore();
const isMobile = isMobileCheck()
return <div className={classNames("flex-center flex-wrap flex-grow self-stretch", {
'gap-2 m-2': isMobile,
'gap-4 m-4': !isMobile
})}>
return <div>
{apps
.filter(app => app.widget)
.map(({ widget, package_name }, _i, _appsWithWidgets) => !widgetSettings[package_name]?.hide && <Widget

File diff suppressed because one or more lines are too long

View File

@ -2,9 +2,6 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import Homepage from './pages/Homepage.tsx'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import '@unocss/reset/tailwind.css'
import 'uno.css'
import './index.css'
import { Settings } from './pages/Settings.tsx'
ReactDOM.createRoot(document.getElementById('root')!).render(

View File

@ -6,12 +6,8 @@ import { FaChevronDown, FaChevronUp, FaScrewdriverWrench } from 'react-icons/fa6
import AppsDock from '../components/AppsDock'
import AllApps from '../components/AllApps'
import Widgets from '../components/Widgets'
import { isMobileCheck } from '../utils/dimensions'
import classNames from 'classnames'
import WidgetsSettingsModal from '../components/WidgetsSettingsModal'
import { getFetchUrl } from '../utils/fetch'
interface AppStoreApp {
package: string,
publisher: string,
@ -23,28 +19,26 @@ function Homepage() {
const [our, setOur] = useState('')
const [version, setVersion] = useState('')
const [allAppsExpanded, setAllAppsExpanded] = useState(false)
const { setApps, fetchHostedStatus, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const isMobile = isMobileCheck()
const { setApps, showWidgetsSettings, setShowWidgetsSettings } = useHomepageStore()
const getAppPathsAndIcons = () => {
Promise.all([
fetch(getFetchUrl('/apps'), { credentials: 'include' }).then(res => res.json() as any as HomepageApp[]).catch(() => []),
fetch(getFetchUrl('/main:app_store:sys/apps'), { credentials: 'include' }).then(res => res.json()).catch(() => []),
fetch(getFetchUrl('/version'), { credentials: 'include' }).then(res => res.text()).catch(() => '')
fetch('/apps', { credentials: 'include' }).then(res => res.json() as any as HomepageApp[]).catch(() => []),
fetch('/main:app_store:sys/apps', { credentials: 'include' }).then(res => res.json()).catch(() => []),
fetch('/version', { credentials: 'include' }).then(res => res.text()).catch(() => '')
]).then(([appsData, appStoreData, version]) => {
// console.log({ appsData, appStoreData, version })
setVersion(version)
const appz = appsData.map(app => ({
const apps = appsData.map(app => ({
...app,
is_favorite: false, // Assuming initial state for all apps
}));
appStoreData.forEach((appStoreApp: AppStoreApp) => {
const existingAppIndex = appz.findIndex(a => a.package_name === appStoreApp.package);
const existingAppIndex = apps.findIndex(a => a.package_name === appStoreApp.package);
if (existingAppIndex === -1) {
appz.push({
apps.push({
package_name: appStoreApp.package,
path: '',
label: appStoreApp.package,
@ -52,19 +46,19 @@ function Homepage() {
is_favorite: false
});
} else {
appz[existingAppIndex] = {
...appz[existingAppIndex],
apps[existingAppIndex] = {
...apps[existingAppIndex],
state: appStoreApp.state
};
}
});
setApps(appz);
setApps(apps);
// TODO: be less dumb about this edge case!
for (
let i = 0;
i < 5 && appz.find(a => a.package_name === 'app_store' && !a.base64_icon);
i < 5 && apps.find(a => a.package_name === 'app_store' && !a.base64_icon);
i++
) {
getAppPathsAndIcons();
@ -77,53 +71,33 @@ function Homepage() {
}, [our]);
useEffect(() => {
fetch(getFetchUrl('/our'), { credentials: 'include' })
fetch('/our', { credentials: 'include' })
.then(res => res.text())
.then(data => {
if (data.match(/^[a-zA-Z0-9\-\.]+\.[a-zA-Z]+$/)) {
setOur(data)
fetchHostedStatus(data)
}
})
}, [our])
return (
<div className={classNames("flex-col-center relative w-screen overflow-hidden special-bg-homepage min-h-screen", {
})}>
<h5 className={classNames('absolute flex gap-4 c', {
'top-8 left-8 right-8': !isMobile,
'top-2 left-2 right-2': isMobile
})}>
<span>{our}</span>
<span className='bg-white/10 rounded p-1'>v{version}</span>
<button
className="icon ml-auto"
onClick={() => setShowWidgetsSettings(true)}
>
<div>
<h5>
<span>Hello, {our}</span>
<span>v{version}</span>
<button onClick={() => setShowWidgetsSettings(true)}>
<FaScrewdriverWrench />
</button>
</h5>
{isMobile
? <div className='flex-center gap-4 p-8 mt-8 max-w-screen'>
<KinodeBird />
<KinodeText />
</div>
: <div className={classNames("flex-col-center mx-0 gap-4 mt-8 mb-4")}>
<h3 className='text-center'>Welcome to</h3>
<KinodeText />
<KinodeBird />
</div>}
<div>
<KinodeBird />
<KinodeText />
</div>
<AppsDock />
<Widgets />
<button
className={classNames("fixed alt clear flex-center self-center z-20", {
'bottom-2 right-2': isMobile,
'bottom-8 right-8': !isMobile,
})}
onClick={() => setAllAppsExpanded(!allAppsExpanded)}
>
<button onClick={() => setAllAppsExpanded(!allAppsExpanded)}>
{allAppsExpanded ? <FaChevronDown /> : <FaChevronUp />}
<span className="ml-2">{allAppsExpanded ? 'Collapse' : 'All apps'}</span>
<span>{allAppsExpanded ? 'Collapse' : 'All apps'}</span>
</button>
<AllApps expanded={allAppsExpanded} />
{showWidgetsSettings && <WidgetsSettingsModal />}

View File

@ -1,3 +1 @@
export const Settings = () => <div>
Settings go here!
</div>
export const Settings = () => <div></div>

View File

@ -17,10 +17,6 @@ export interface HomepageStore {
get: () => HomepageStore
set: (partial: HomepageStore | Partial<HomepageStore>) => void
isHosted: boolean
setIsHosted: (isHosted: boolean) => void
fetchHostedStatus: (our: string) => Promise<void>
apps: HomepageApp[]
setApps: (apps: HomepageApp[]) => void
showWidgetsSettings: boolean
@ -37,19 +33,6 @@ const useHomepageStore = create<HomepageStore>()(
setApps: (apps: HomepageApp[]) => set({ apps }),
showWidgetsSettings: false,
setShowWidgetsSettings: (showWidgetsSettings: boolean) => set({ showWidgetsSettings }),
isHosted: false,
setIsHosted: (isHosted: boolean) => set({ isHosted }),
fetchHostedStatus: async (our: string) => {
let hosted = false
try {
const res = await fetch(`https://${our.replace('.os', '')}.hosting.kinode.net/`)
hosted = res.status === 200
} catch (error) {
// do nothing
} finally {
set({ isHosted: hosted })
}
},
}),
{
name: 'homepage_store', // unique name

View File

@ -13,14 +13,6 @@ export interface PersistentStore {
setWidgetSettings: (widgetSettings: PersistentStore['widgetSettings']) => void
toggleWidgetVisibility: (package_name: string) => void
setWidgetSize: (package_name: string, size: 'small' | 'large') => void,
favoriteApps: {
[key: string]: {
favorite: boolean
order?: number
}
}
setFavoriteApps: (favoriteApps: PersistentStore['favoriteApps']) => void
favoriteApp: (package_name: string) => void
}
const usePersistentStore = create<PersistentStore>()(
@ -29,9 +21,7 @@ const usePersistentStore = create<PersistentStore>()(
get,
set,
widgetSettings: {},
favoriteApps: {},
setWidgetSettings: (widgetSettings: PersistentStore['widgetSettings']) => set({ widgetSettings }),
setFavoriteApps: (favoriteApps: PersistentStore['favoriteApps']) => set({ favoriteApps }),
toggleWidgetVisibility: (package_name: string) => {
const { widgetSettings } = get()
set({
@ -56,18 +46,6 @@ const usePersistentStore = create<PersistentStore>()(
}
})
},
favoriteApp: async (package_name: string) => {
const { favoriteApps } = get()
set({
favoriteApps: {
...favoriteApps,
[package_name]: {
...favoriteApps[package_name],
favorite: !favoriteApps[package_name]?.favorite
}
}
})
},
}),
{
name: 'homepage_persistent_store', // unique name for the store

View File

@ -1,106 +0,0 @@
export type RgbType = { r: number, g: number, b: number }
export type HslType = { h: number, s: number, l: number }
export const rgbToHsl: (rgb: RgbType) => HslType = ({ r, g, b }) => {
r /= 255; g /= 255; b /= 255;
let max = Math.max(r, g, b);
let min = Math.min(r, g, b);
let d = max - min;
let h = 0;
if (d === 0) h = 0;
else if (max === r) h = ((((g - b) / d) % 6) + 6) % 6; // the javascript modulo operator handles negative numbers differently than most other languages
else if (max === g) h = (b - r) / d + 2;
else if (max === b) h = (r - g) / d + 4;
let l = (min + max) / 2;
let s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
return { h: h * 60, s, l };
}
export const hslToRgb: (hsl: HslType) => RgbType = ({ h, s, l }) => {
let c = (1 - Math.abs(2 * l - 1)) * s;
let hp = h / 60.0;
let x = c * (1 - Math.abs((hp % 2) - 1));
let rgb1 = [0, 0, 0];
if (isNaN(h)) rgb1 = [0, 0, 0];
else if (hp <= 1) rgb1 = [c, x, 0];
else if (hp <= 2) rgb1 = [x, c, 0];
else if (hp <= 3) rgb1 = [0, c, x];
else if (hp <= 4) rgb1 = [0, x, c];
else if (hp <= 5) rgb1 = [x, 0, c];
else if (hp <= 6) rgb1 = [c, 0, x];
let m = l - c * 0.5;
return {
r: Math.round(255 * (rgb1[0] + m)),
g: Math.round(255 * (rgb1[1] + m)),
b: Math.round(255 * (rgb1[2] + m))
};
}
export const hexToRgb: (hex: string) => RgbType = (hex) => {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
export const rgbToHex: (rgb: RgbType) => string = ({ r, g, b }) => {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
export const generateDistinguishableColors = (numColors: number, mod?: number, sat?: number, val?: number) => {
const colors: string[] = [];
const saturation = sat || 0.75;
const value = val || 0.75;
for (let i = 0; i < numColors; i++) {
const hue = i / numColors * (+(mod || 1) || 1);
const [r, g, b] = hsvToRgb(hue, saturation, value);
const hexColor = rgbToHex2(r, g, b);
colors.push(hexColor);
}
return colors;
}
export const hsvToRgb = (h: number, s: number, v: number) => {
let [r, g, b] = [0, 0, 0];
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
(r = v), (g = t), (b = p); // eslint-disable-line
break;
case 1:
(r = q), (g = v), (b = p); // eslint-disable-line
break;
case 2:
(r = p), (g = v), (b = t); // eslint-disable-line
break;
case 3:
(r = p), (g = q), (b = v); // eslint-disable-line
break;
case 4:
(r = t), (g = p), (b = v); // eslint-disable-line
break;
case 5:
(r = v), (g = p), (b = q); // eslint-disable-line
break;
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
export const componentToHex = (c: number) => {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
export const rgbToHex2 = (r: number, g: number, b: number) => {
return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b);
}

View File

@ -1 +0,0 @@
export const isMobileCheck = () => window.innerWidth <= 600

View File

@ -1,12 +0,0 @@
/**
* Prepends or strips '/api/' based on the environment.
* @param {string} path The original path.
* @return {string} The modified path.
*/
export function getFetchUrl(path: string) {
const isDevelopment = import.meta.env.DEV;
if (isDevelopment) {
return `/api${path}`;
}
return path.replace(/^\/api/, '');
}

View File

@ -1,34 +0,0 @@
import { defineConfig, presetIcons, presetUno, presetWind, UserConfig, transformerDirectives } from 'unocss'
const config = {
presets: [presetUno(), presetWind(), presetIcons()],
shortcuts: [
{
'flex-center': 'flex justify-center items-center',
'flex-col-center': 'flex flex-col justify-center items-center',
},
],
rules: [
],
theme: {
colors: {
'white': '#FFF5D9',
'black': '#22211F',
'orange': '#F35422',
'transparent': 'transparent',
'gray': '#7E7E7E',
},
font: {
'sans': ['Barlow', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
'serif': ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
'mono': ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'],
'heading': ['OpenSans', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
'display': ['Futura', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
},
},
transformers: [
transformerDirectives()
],
}
export default defineConfig(config) as UserConfig<(typeof config)['theme']>