Refactor tags to categories

This commit is contained in:
Georgi Gardev
2023-11-19 09:45:33 +02:00
parent 4d56c1e169
commit 99e98aeb41
8 changed files with 78 additions and 163 deletions
+8 -16
View File
@@ -1,20 +1,12 @@
import { createResource } from 'solid-js'; import { createMemo, createResource } from 'solid-js';
import { AppDefinition } from '~/types'; import { CategoryDefinition } from '~/types';
async function getApps(): Promise<AppDefinition[]> { async function getCategories(): Promise<CategoryDefinition[]> {
return await import('../../data/apps.yml').then((m) => m.default.apps); return await import('../../data/apps.yml').then((m) => m.default.categories);
} }
export function createApps() { export function createCategories() {
const [apps] = createResource<AppDefinition[]>(getApps); const [categories] = createResource<CategoryDefinition[]>(getCategories);
return apps; const apps = createMemo(() => categories()?.flatMap((c) => c.apps));
} return { categories, apps };
export async function getTagOrder(): Promise<string[]> {
return await import('../../data/tags.yml').then((m) => m.default.tags);
}
export function createTagOrder() {
const [tagOrder] = createResource<string[]>(getTagOrder);
return tagOrder;
} }
+17 -37
View File
@@ -1,51 +1,31 @@
import { groupBy } from 'ramda';
import { Accessor } from 'solid-js'; import { Accessor } from 'solid-js';
import { createTagOrder } from '~/api/get-apps'; import { CategoryDefinition } from '~/types';
import { AppDefinition } from '~/types';
import { Pill } from './Pill'; import { Pill } from './Pill';
import style from './AppList.module.css'; import style from './AppList.module.css';
export function AppList({ export function AppList({
apps, category,
tag, resetCategory: resetCategory,
resetTag,
}: { }: {
apps: AppDefinition[]; category?: Accessor<CategoryDefinition | undefined>;
tag?: Accessor<string | null>; resetCategory?(): void;
resetTag?(): void;
}) { }) {
const tagOrder = createTagOrder();
const grouped = groupBy((app) => app.tags, apps);
return ( return (
<div class={style.AppListWrap}> <div class={style.AppListWrap}>
{tag?.() ? ( <div style={{ width: '100%' }}>
<div style={{ width: '100%' }}> <h2 class={style.Header}>
<h2 class={style.Header}> {category?.()?.name}
{tag()} <span class={style.Close} onClick={resetCategory}>
<span class={style.Close} onClick={resetTag}> ×
× </span>
</span> </h2>
</h2> <div class={style.AppList}>
<div class={style.AppList}> {category?.()?.apps.map((app) => (
{grouped[tag()!]!.map((app, i) => ( <Pill {...app} />
<Pill {...app} /> ))}
))}
</div>
</div> </div>
) : ( </div>
tagOrder()?.map((tag) => (
<div>
<h2 class={style.Header}>{tag}</h2>
<div class={style.AppList}>
{grouped[tag]!.map((app, i) => (
<Pill {...app} />
))}
</div>
</div>
))
)}
</div> </div>
); );
} }
-10
View File
@@ -1,10 +0,0 @@
.Name {
display: flex;
gap: 1rem;
align-items: center;
}
.Icon {
max-height: 24px;
max-width: 24px;
}
-64
View File
@@ -1,64 +0,0 @@
import { Tag, TagProps } from '@hope-ui/solid';
import { getAppIcon } from '~/api/get-app-icon';
import { createTagOrder } from '~/api/get-apps';
import { useSettings } from '~/state/SettingsProvider';
import { AppDefinition } from '~/types';
import style from './AppTable.module.css';
export function AppTable({ apps }: { apps: AppDefinition[] }) {
const { appSettings } = useSettings();
const tagOrder = createTagOrder();
const TAG_TO_COLOR: Record<string, TagProps['colorScheme']> = {
media: 'primary',
productivity: 'accent',
infra: 'neutral',
'smart home': 'success',
'*arr': 'danger',
social: 'warning',
};
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>URL</th>
<th>Tags</th>
{appSettings.showLocations && <th>Location</th>}
<th>Shortuct</th>
</tr>
</thead>
<tbody>
{apps
.sort(
(tagA, tagB) =>
(tagOrder()?.indexOf(tagA.tags) ?? 0) - (tagOrder()?.indexOf(tagB.tags) ?? 0)
)
.map((app) => (
<tr>
<td>
<a href={app.url} target="_blank" rel="noopener noreferrer" class={style.Name}>
<img class={style.Icon} src={getAppIcon(app.icon)} alt={app.name} />
{app.name}
</a>
</td>
<td>
<a href={app.url} target="_blank" rel="noopener noreferrer">
{app.url.split('://')[1]}
</a>
</td>
<td>
{app.tags.split(',').map((tag) => (
<Tag colorScheme={TAG_TO_COLOR[tag]}>{tag}</Tag>
))}
</td>
{appSettings.showLocations && <td>{app.location}</td>}
<td>{app.shortcut}</td>
</tr>
))}
</tbody>
</table>
);
}
+10 -11
View File
@@ -1,31 +1,30 @@
import { Tooltip } from '@hope-ui/solid'; import { Tooltip } from '@hope-ui/solid';
import { IoSave } from 'solid-icons/io'; import { HiOutlineStar, HiSolidStar } from 'solid-icons/hi';
import { createTagOrder } from '~/api/get-apps'; import { Accessor } from 'solid-js';
import { useSettings } from '~/state/SettingsProvider'; 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 { HiOutlineStar, HiSolidStar } from 'solid-icons/hi';
type SidebarProps = { type SidebarProps = {
activeTag?: string | null; categories: Accessor<string[] | undefined>;
onSelectTag?(tag: string, mode: 'permanent' | 'temporary'): void; activeCategory?: string | null;
selectCategory?(category: string, mode: 'permanent' | 'temporary'): void;
}; };
export function Sidebar(props: SidebarProps) { export function Sidebar(props: SidebarProps) {
const tagOrder = createTagOrder();
const { onSaveBackground, onUnsaveBackground, isBackgroundSaved } = useSettings(); const { onSaveBackground, onUnsaveBackground, isBackgroundSaved } = useSettings();
return ( return (
<div class={style.Sidebar}> <div class={style.Sidebar}>
<ul class={style.Menu}> <ul class={style.Menu}>
{tagOrder()?.map((tag) => ( {props.categories()?.map((category) => (
<li <li
class={props.activeTag === tag ? style.Active : ''} class={props.activeCategory === category ? style.Active : ''}
onClick={() => props.onSelectTag?.(tag, 'permanent')} onClick={() => props.selectCategory?.(category, 'permanent')}
onMouseEnter={() => props.onSelectTag?.(tag, 'temporary')} onMouseEnter={() => props.selectCategory?.(category, 'temporary')}
> >
{tag} {category}
</li> </li>
))} ))}
</ul> </ul>
+16 -25
View File
@@ -1,5 +1,5 @@
import { createSignal, onCleanup, onMount } from 'solid-js'; import { createMemo, createSignal, onCleanup, onMount } from 'solid-js';
import { createApps } from '~/api/get-apps'; import { createCategories } from '~/api/get-apps';
import { createSearchProviders } from '~/api/get-search-providers'; import { createSearchProviders } from '~/api/get-search-providers';
import { AppList } from '~/components/apps/AppList'; import { AppList } from '~/components/apps/AppList';
import { DateView } from '~/components/widgets/Date'; import { DateView } from '~/components/widgets/Date';
@@ -8,9 +8,12 @@ import { Weather } from '~/components/widgets/Weather';
import { Sidebar } from '../components/ui/Sidebar'; import { Sidebar } from '../components/ui/Sidebar';
import style from './index.module.css'; import style from './index.module.css';
import { createActiveCategory } from '~/state/create-active-category';
export default function Home() { export default function Home() {
const searchProviders = createSearchProviders(); const searchProviders = createSearchProviders();
const { categories, apps } = createCategories();
const { activeCategory, selectCategory, resetCategory } = createActiveCategory();
onMount(() => { onMount(() => {
parent.addEventListener('keydown', handleKeypress); parent.addEventListener('keydown', handleKeypress);
@@ -20,30 +23,11 @@ export default function Home() {
parent.removeEventListener('keydown', handleKeypress); parent.removeEventListener('keydown', handleKeypress);
}); });
const apps = createApps();
const [activeTag, setActiveTag] = createSignal<string | null>(null);
const [timeout, storeTimeout] = createSignal<NodeJS.Timeout | null>(null);
const switchToTag = (tag: string, mode: 'permanent' | 'temporary') => {
setActiveTag(tag);
clearTimeout(timeout() as NodeJS.Timeout);
if (mode === 'temporary') {
storeTimeout(setTimeout(() => setActiveTag(null), 3000));
}
};
const resetTag = () => {
setActiveTag(null);
clearTimeout(timeout() as NodeJS.Timeout);
};
function handleKeypress(e: KeyboardEvent) { function handleKeypress(e: KeyboardEvent) {
switch (e.key) { switch (e.key) {
case 'Escape': { case 'Escape': {
document.getElementById('search')?.blur(); document.getElementById('search')?.blur();
resetTag(); resetCategory();
break; break;
} }
case '/': { case '/': {
@@ -65,11 +49,18 @@ export default function Home() {
return ( return (
<main class={style.SidebarPage}> <main class={style.SidebarPage}>
<Sidebar onSelectTag={switchToTag} activeTag={activeTag()} /> <Sidebar
categories={createMemo(() => categories()?.flatMap((c) => c.name))}
selectCategory={selectCategory}
activeCategory={activeCategory()}
/>
<div class={style.ContentWrapper}> <div class={style.ContentWrapper}>
<div class={style.Widgets}> <div class={style.Widgets}>
{activeTag() ? ( {activeCategory() ? (
<AppList apps={apps() ?? []} tag={activeTag} resetTag={resetTag} /> <AppList
category={createMemo(() => categories()?.find((c) => c.name === activeCategory()))}
resetCategory={resetCategory}
/>
) : ( ) : (
<> <>
<DateView /> <DateView />
+22
View File
@@ -0,0 +1,22 @@
import { createSignal } from 'solid-js';
export function createActiveCategory() {
const [activeCategory, setActiveCategory] = createSignal<string | null>(null);
const [timeout, storeTimeout] = createSignal<NodeJS.Timeout | null>(null);
const selectCategory = (category: string, mode: 'permanent' | 'temporary') => {
setActiveCategory(category);
clearTimeout(timeout() as NodeJS.Timeout);
if (mode === 'temporary') {
storeTimeout(setTimeout(() => setActiveCategory(null), 3000));
}
};
const resetCategory = () => {
setActiveCategory(null);
clearTimeout(timeout() as NodeJS.Timeout);
};
return { activeCategory, selectCategory, resetCategory };
}
+5
View File
@@ -1,5 +1,10 @@
import { Random } from 'unsplash-js/dist/methods/photos/types'; import { Random } from 'unsplash-js/dist/methods/photos/types';
export type CategoryDefinition = {
name: string;
apps: AppDefinition[];
};
export type AppDefinition = { export type AppDefinition = {
name: string; name: string;
url: string; url: string;