feat(web): ui update

This commit is contained in:
arkohut 2024-10-16 00:22:31 +08:00
parent 955ec76638
commit 42e8421ab4
6 changed files with 184 additions and 52 deletions

View File

@ -4,7 +4,9 @@
import CopyToClipboard from "$lib/components/CopyToClipboard.svelte" import CopyToClipboard from "$lib/components/CopyToClipboard.svelte"
import OCRTable from './OCRTable.svelte'; import OCRTable from './OCRTable.svelte';
import { marked } from 'marked'; import { marked } from 'marked';
import { ChevronLeft, ChevronRight, X } from 'lucide-svelte'; import { ChevronLeft, ChevronRight, X, Hash, Library, Folder, FileClock } from 'lucide-svelte';
import { appIconMap } from '$lib/utils';
import LucideIcon from '$lib/components/LucideIcon.svelte';
/** /**
* @type {string} * @type {string}
@ -38,6 +40,7 @@
* @type {string} * @type {string}
*/ */
export let title; export let title;
export let app_name;
/** /**
* @type {Array<string>} * @type {Array<string>}
*/ */
@ -47,6 +50,16 @@
* @type {Array<{key: string, source: string, value: any}>} * @type {Array<{key: string, source: string, value: any}>}
*/ */
export let metadata_entries = []; export let metadata_entries = [];
// Remove items with key "timestamp" or "sequence" and sort metadata_entries, placing "ocr_result" at the end
$: sortedMetadataEntries = [...metadata_entries]
.filter(entry => entry.key !== "timestamp" && entry.key !== "sequence" && entry.key !== "active_app" && entry.key !== "active_window")
.sort((a, b) => {
if (a.key === "ocr_result") return 1;
if (b.key === "ocr_result") return -1;
return 0;
});
/** /**
* @type {any} * @type {any}
*/ */
@ -111,55 +124,68 @@
</div> </div>
<div class="flex flex-col md:flex-row h-full"> <div class="flex flex-col md:flex-row h-full">
<!-- Image container --> <!-- Image container -->
<div class="flex-none w-full md:w-1/2 h-full"> <div class="flex-none w-full md:w-1/2 flex flex-col">
<a href={video} target="_blank" rel="noopener noreferrer"> <div class="mb-4">
<img class="w-full h-full object-contain" src={image} alt={title} /> <div class="flex items-center space-x-2 text-lg leading-tight font-medium text-black hover:underline">
</a> <LucideIcon name={appIconMap[app_name] || 'Image'} size={24} />
</div> <p>{title}</p>
<!-- Description container --> </div>
<ScrollArea class="mt-4 md:mt-0 md:ml-6 overflow-y-auto max-h-full"> </div>
<div class="mb-2 mr-2 pb-2 border-b border-gray-300"> <div class="mb-2 mr-2 pb-2 border-b border-gray-300">
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold">ID</span>
<span class="mt-1 text-sm leading-tight font-medium text-gray-500 font-mono"> <span class="mt-1 text-sm leading-tight font-medium text-gray-500 font-mono">
{id} <span class="inline-flex mr-4">
</span> <Library size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold ml-4" {library_id}
>Library ID</span </span>
>
<span class="mt-1 text-sm leading-tight font-medium text-gray-500 font-mono"> <span class="inline-flex mr-4">
{library_id} <Folder size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
</span> {folder_id}
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold ml-4" </span>
>Folder ID</span
> <span class="inline-flex mr-4">
<span class="mt-1 text-sm leading-tight font-medium text-gray-500 font-mono"> <Hash size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
{folder_id} {id}
</span> </span>
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold ml-4"
>DATETIME</span <span class="inline-flex mr-4">
> <FileClock size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
<span class="mt-1 text-xs leading-tight font-xs text-gray-500 font-mono"> {new Date(created_at).toLocaleString()}
{new Date(created_at).toLocaleString()} </span>
</span> </span>
<div> <div>
<span class="mt-1 text-xs leading-tight font-xs text-gray-500 font-mono"> <span class="mt-1 text-xs leading-tight font-xs text-gray-500 font-mono">
{filepath} {filepath}
</span> </span>
</div> </div>
</div> </div>
<div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">Image Title</div> <div class="flex-grow overflow-hidden">
<p class="block mt-1 text-lg leading-tight font-medium text-black hover:underline"> <a href={video} target="_blank" rel="noopener noreferrer" class="block h-full">
{title} <img
</p> class="w-full h-full object-contain rounded-lg drop-shadow-md"
<div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">TAGS</div> src={image}
<div class="mt-2 text-gray-600"> alt={title}
{#each tags as tag} />
<span class="text-base text-gray-500 inline-block">{tag}</span> </a>
{/each}
</div> </div>
</div>
<!-- Description container -->
<ScrollArea class="mt-4 md:mt-0 md:ml-6 overflow-y-auto max-h-full">
{#if tags.length > 0}
<div class="mb-4">
<div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">TAGS</div>
<div class=" text-gray-600">
{#each tags as tag}
<span class="text-base text-gray-500 inline-block">{tag}</span>
{/each}
</div>
</div>
{/if}
<div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">METADATA</div> <div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">METADATA</div>
<div class="mt-2 text-gray-600"> <div class="mt-2 text-gray-600">
{#each metadata_entries as entry} {#each sortedMetadataEntries as entry}
<div class="mb-2"> <div class="mb-2">
<span class="font-bold flex items-center"> <span class="font-bold flex items-center">
{entry.key} {entry.key}
@ -195,4 +221,4 @@
</button> </button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,7 +8,61 @@
export let class_ = ''; export let class_ = '';
export let withBorder = true; export let withBorder = true;
function prepareMatrixFromRandomColors(withBorder: boolean): string[][] { function prepareMatrixFromFixedIndexAndLittleRadom(withBorder: boolean): string[][] {
const bgColors = ['#f2f2f2', '#e9e9e9', '#d8d8d8']
// const colors = ['#d0e8ff', '#F2295F', '#E0A0F2', '#F2B705'];
const colors = ['#d0e8ff', '#BF244E', '#8C2685', '#21A650'];
const mShape = withBorder
? [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3, 0],
[0, 1, 1, 1, 2, 0, 0, 0, 2, 3, 3, 3, 0],
[0, 1, 1, 1, 2, 2, 0, 2, 2, 3, 3, 3, 0],
[0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 0],
[0, 1, 1, 1, 0, 2, 2, 2, 0, 3, 3, 3, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3, 0],
[0, 1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
: [
[1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 3],
[1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3],
[1, 1, 1, 2, 0, 0, 0, 2, 3, 3, 3],
[1, 1, 1, 2, 2, 0, 2, 2, 3, 3, 3],
[1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3],
[1, 1, 1, 0, 2, 2, 2, 0, 3, 3, 3],
[1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3],
[1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3],
[1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3],
[1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3],
[1, 1, 1, 0, 0, 0, 0, 0, 3, 3, 3]
];
const gridSize = withBorder ? 13 : 11;
let seed = 42;
const matrix: string[][] = [];
for (let row = 0; row < gridSize; row++) {
const rowColors: string[] = [];
const bgSize = bgColors.length;
for (let col = 0; col < gridSize; col++) {
if (mShape[row][col] === 0) {
rowColors.push(bgColors[Math.floor(seededRandom(seed++) * bgSize)]);
} else {
rowColors.push(colors[mShape[row][col]]);
}
}
matrix.push(rowColors);
}
return matrix;
}
function prepareMatrixFromRandomColors(withBorder: boolean): string[][] {
const colors = ['#d0e8ff', '#a1d2ff', '#64b5f6', '#1565c0', '#0d47a1']; const colors = ['#d0e8ff', '#a1d2ff', '#64b5f6', '#1565c0', '#0d47a1'];
const mShape = withBorder const mShape = withBorder
? [ ? [
@ -83,7 +137,7 @@
} }
function generateMemosLogo(size: number, withBorder: boolean): string { function generateMemosLogo(size: number, withBorder: boolean): string {
const matrix = prepareMatrixFromRandomColors(withBorder); const matrix = prepareMatrixFromFixedIndexAndLittleRadom(withBorder);
return generateSvg(matrix, size); return generateSvg(matrix, size);
} }

View File

@ -0,0 +1,6 @@
<script>
import * as icons from 'lucide-svelte';
export let name;
</script>
<svelte:component this="{icons[name]}" {...$$props} />

View File

@ -59,4 +59,32 @@ export const flyAndScale = (
}, },
easing: cubicOut easing: cubicOut
}; };
}; };
export const appIconMap: Record<string, string> = {
"Cursor": "Code",
"Google Chrome": "Chrome",
"IINA": "Youtube",
"微信": "MessageSquareCode",
"预览": "Eye",
"iTerm2": "SquareTerminal",
"企业微信": "MessageSquareCode",
"IntelliJ IDEA": "Code",
"Microsoft Edge": "Globe",
"腾讯会议": "MessagesSquare",
"访达": "Folder",
"邮件": "Mail",
"备忘录": "NotebookTabs",
"日历": "CalendarFold",
"UserNotificationCenter": "Bell",
"Electron": "Atom",
"Firefox": "Globe",
"Safari浏览器": "Compass",
"熊掌记": "NotebookTabs",
"Alacritty": "SquareTerminal",
"系统设置": "Settings",
"股市": "CircleDollarSign",
"活动监视器": "Activity",
"Brave Browser": "Globe",
"Code": "Code",
};

View File

@ -8,6 +8,8 @@
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import Logo from '$lib/components/Logo.svelte'; import Logo from '$lib/components/Logo.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { appIconMap } from '$lib/utils';
import LucideIcon from '$lib/components/LucideIcon.svelte';
let searchString = ''; let searchString = '';
/** /**
@ -255,6 +257,24 @@
); );
} }
} }
// Add this function near the top of the <script> section
function getEntityTitle(document: any): string {
if (document.metadata_entries &&
document.metadata_entries.some((entry: any) => entry.key === 'active_window')) {
return document.metadata_entries.find((entry: any) => entry.key === 'active_window').value;
}
return filename(document.filepath);
}
function getAppName(document: any): string | null {
if (document.metadata_entries && document.metadata_entries.some((entry) => entry.key === 'active_app')) {
return document.metadata_entries.find((entry) => entry.key === 'active_app').value;
} else {
return null;
}
}
</script> </script>
<svelte:head> <svelte:head>
@ -330,11 +350,7 @@
> >
<div class="px-4 pt-4"> <div class="px-4 pt-4">
<h2 class="line-clamp-2 h-12"> <h2 class="line-clamp-2 h-12">
{hit.document.metadata_entries && {getEntityTitle(hit.document)}
hit.document.metadata_entries.some((entry) => entry.key === 'active_window')
? hit.document.metadata_entries.find((entry) => entry.key === 'active_window')
.value
: filename(hit.document.filepath)}
</h2> </h2>
<p class="text-gray-700 text-xs"> <p class="text-gray-700 text-xs">
{formatDistanceToNow(new Date(hit.document.file_created_at * 1000), { {formatDistanceToNow(new Date(hit.document.file_created_at * 1000), {
@ -348,11 +364,12 @@
src={`${apiEndpoint}/files/${hit.document.filepath}`} src={`${apiEndpoint}/files/${hit.document.filepath}`}
alt="" alt=""
/> />
{#if hit.document.metadata_entries && hit.document.metadata_entries.some((entry) => entry.key === 'active_app')} {#if getAppName(hit.document)}
<div <div
class="absolute bottom-2 left-6 bg-white bg-opacity-75 px-2 py-1 rounded-full text-xs font-semibold border border-gray-200" class="absolute bottom-2 left-6 bg-white bg-opacity-75 px-2 py-1 rounded-full text-xs font-semibold border border-gray-200 flex items-center space-x-2"
> >
{hit.document.metadata_entries.find((entry) => entry.key === 'active_app').value} <LucideIcon name={appIconMap[getAppName(hit.document)] || 'Hexagon'} size={16} />
<span>{getAppName(hit.document)}</span>
</div> </div>
{/if} {/if}
</figure> </figure>
@ -376,7 +393,8 @@
video={`${apiEndpoint}/files/video/${searchResult.hits[selectedImage].document.filepath}`} video={`${apiEndpoint}/files/video/${searchResult.hits[selectedImage].document.filepath}`}
created_at={searchResult.hits[selectedImage].document.file_created_at * 1000} created_at={searchResult.hits[selectedImage].document.file_created_at * 1000}
filepath={searchResult.hits[selectedImage].document.filepath} filepath={searchResult.hits[selectedImage].document.filepath}
title={filename(searchResult.hits[selectedImage].document.filepath)} title={getEntityTitle(searchResult.hits[selectedImage].document)}
app_name={getAppName(searchResult.hits[selectedImage].document)}
tags={searchResult.hits[selectedImage].document.tags} tags={searchResult.hits[selectedImage].document.tags}
metadata_entries={searchResult.hits[selectedImage].document.metadata_entries} metadata_entries={searchResult.hits[selectedImage].document.metadata_entries}
onClose={closeModal} onClose={closeModal}
@ -396,4 +414,4 @@
<a href="/changelog">Changelog</a> <a href="/changelog">Changelog</a>
</div> </div>
</div> </div>
</footer> </footer>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 18 KiB