Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ packages/*/deno_dependency_cache
node_modules

# release output (binaries, changelog.md)
releases
/releases
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
On the /browse page, we have a "Group" search param that lets you group by a particular tag group.

This is a small fraction of what we should be doing with this.

I want to:
- Group by day, month, year, decade source_created_at
- Group by day, month, year, decade created_at
- Group by day, month, year, decade updated_at

I want to combine a group by username (tag_group: username), then group by month uploaded (source_created_at: desc)

the current Group text box search mode should be replaced with an UI that conveys to the user they are "applying" another dimension on top of their current browse view. We can do this with a "< + Split >" button.
15 changes: 14 additions & 1 deletion packages/web/src/lib/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as forager from '@forager/core'

const LogLevel = z.enum(['SILENT', 'ERROR', 'WARN', 'INFO', 'DEBUG'])

const MediaTileInfoTableTypes = z.enum(['tag_group', 'sort_top'])

const Keybind = (default_keybind: string) => z.union([z.string(), z.string().array()]).default(default_keybind).transform(keybind => {
if (Array.isArray(keybind)) return keybind
else return [keybind]
Expand Down Expand Up @@ -38,7 +40,18 @@ export const PackagesConfig = z.object({

media_list: z.object({
thumbnail_size: z.number().default(110),
thumbnail_shape: z.enum(['square', 'original']).prefault('original')
thumbnail_shape: z.enum(['square', 'original']).prefault('original'),

info_tiles: z.object({
tag_group: z.object({
enabled: z.boolean().default(true),
order: z.number().optional(), // TBD if needed. We may be able to get away with yaml ordering if the serialization is consistent
}).prefault({}),
sort_top: z.object({
enabled: z.boolean().default(true),
order: z.number().optional(), // TBD if needed. We may be able to get away with yaml ordering if the serialization is consistent
}).prefault({}),
}).prefault({}),
}).strict().prefault({}),

sidebar: z.object({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import type { BrowseController } from '../controller.ts'
import * as icons from '$lib/icons/mod.ts'
import * as theme from '$lib/theme.ts'
import Icon from '$lib/components/Icon.svelte'
import SearchLink from './SearchLink.svelte'

interface InfoEntry {
name: string
icon: string
title?: string
stat?: object
queryparams?: string
value: object
enabled: boolean
order: number
}
interface Props {
entries: InfoEntry[]
controller: BrowseController
}
let props: Props = $props()
let {controller} = props
let entries = $derived(props.entries
.filter(entry => entry.enabled)
.sort((a, b) => a.order - b.order)
)
const {queryparams, settings, media_selections, media_list} = controller.runes

const icon_size = 14
const icon_color = theme.colors.green[200]

const row_colors = [
theme.colors.gray[800],
theme.colors.gray[700],
]
</script>


<table class="w-full">
<tbody>
{#each entries as entry, index}
<tr class={[
"grid grid-cols-[auto_auto_auto] justify-between px-3 py-1",
index !== entries.length - 1 && "border-b-gray-500 border-b",
]}
>
<!-- style={`background-color: ${row_colors[index % 2]}`} -->
<td>
<Icon data={entry.icon} fill={icon_color} stroke="none" size={icon_size} title={entry.title} />
</td>
<td>
{#if entry.queryparams}
<SearchLink
class="hover:text-green-500 hover:bg-gray-800 bg-gray-700 px-2 rounded-sm"
{controller} params={entry.queryparams}> {entry.value}
</SearchLink>
{:else}
<span>{entry.value} </span>
{/if}
</td>
<td>{entry.stat}</td>
</tr>
{/each}
</tbody>
</table>
2 changes: 0 additions & 2 deletions packages/web/src/routes/browse/components/SearchLink.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
let {params, controller, children, ...props}: Props = $props()
const {queryparams} = controller.runes

console.log({params})

</script>

<a
Expand Down
47 changes: 41 additions & 6 deletions packages/web/src/routes/browse/components/SearchResults.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import { focusable, scrollable } from '$lib/actions/mod.ts'
import Icon from '$lib/components/Icon.svelte'
import SearchLink from './SearchLink.svelte'
import * as datetime from '@std/datetime'
import Datetime from '$lib/components/Datetime.svelte'
import * as icons from '$lib/icons/mod.ts'
import MediaTileInfoTable from './MediaTileInfoTable.svelte';

interface Props {
controller: BrowseController
Expand All @@ -29,6 +32,22 @@
const hours = minutes / 60
return `${hours.toFixed(2)}h`
}

function format_human_readable_datetime(date_str: string) {
const date = new Date(date_str)
return datetime.format(date, 'yyyy-MM-dd')
}
interface SearchResultGroupMetadata {}
function format_human_readable_grouped_datetime(group_metadata: SearchResultGroupMetadata) {
const date_str = group_metadata[grouped_datetime(group_metadata)]
return format_human_readable_datetime(date_str)
}

function grouped_datetime(group_metadata: SearchResultGroupMetadata) {
return typeof group_metadata[controller.runes.queryparams.current.sort] === 'string'
? controller.runes.queryparams.current.sort
: 'created_at'
}
</script>

<style>
Expand Down Expand Up @@ -127,12 +146,28 @@
<Icon data={icons.Copy} fill={icon_color} stroke="none" size={icon_size} />
<span>({result.media_reference.media_series_length})</span>
{:else if result.media_type === 'grouped'}
<Icon data={icons.Copy} fill={icon_color} stroke="none" size={icon_size} />
<SearchLink
class="hover:text-green-500 hover:bg-gray-700 px-2 rounded-sm"
{controller} params={queryparams.merge({mode: 'media', tags: `${queryparams.current.group_by ?? ''}:${result.group_metadata.value}`})}> {result.group_metadata.value}
</SearchLink>
<span>{result.group_metadata.count}</span>
<MediaTileInfoTable
{controller}
entries={[
{ name: 'tag_group',
title: 'Tag Group',
icon: icons.Copy,
value:result.group_metadata.value,
stat: result.group_metadata.count,
queryparams: queryparams.merge({mode: 'media', tags: `${queryparams.current.group_by ?? ''}:${result.group_metadata.value}`}),
enabled: controller.runes.settings.ui.media_list.info_tiles.tag_group.enabled,
order: controller.runes.settings.ui.media_list.info_tiles.tag_group.order,
},
{
name: 'sort_top',
title: `Top ${grouped_datetime(result.group_metadata)} date`,
icon: controller.runes.queryparams.current.order === 'desc' ? icons.ArrowDown : icons.ArrowUp,
value: format_human_readable_grouped_datetime(result.group_metadata),
enabled: controller.runes.settings.ui.media_list.info_tiles.sort_top.enabled,
order: controller.runes.settings.ui.media_list.info_tiles.sort_top.order,
},
]}
/>
{:else}
UNEXPECTED MEDIA TYPE {result.media_type}
{/if}
Expand Down
Loading