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 OCRTable from './OCRTable.svelte';
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}
@ -38,6 +40,7 @@
* @type {string}
*/
export let title;
export let app_name;
/**
* @type {Array<string>}
*/
@ -47,6 +50,16 @@
* @type {Array<{key: string, source: string, value: any}>}
*/
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}
*/
@ -111,55 +124,68 @@
</div>
<div class="flex flex-col md:flex-row h-full">
<!-- Image container -->
<div class="flex-none w-full md:w-1/2 h-full">
<a href={video} target="_blank" rel="noopener noreferrer">
<img class="w-full h-full object-contain" src={image} alt={title} />
</a>
<div class="flex-none w-full md:w-1/2 flex flex-col">
<div class="mb-4">
<div class="flex items-center space-x-2 text-lg leading-tight font-medium text-black hover:underline">
<LucideIcon name={appIconMap[app_name] || 'Image'} size={24} />
<p>{title}</p>
</div>
</div>
<!-- Description container -->
<ScrollArea class="mt-4 md:mt-0 md:ml-6 overflow-y-auto max-h-full">
<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">
{id}
</span>
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold ml-4"
>Library ID</span
>
<span class="mt-1 text-sm leading-tight font-medium text-gray-500 font-mono">
<span class="inline-flex mr-4">
<Library size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
{library_id}
</span>
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold ml-4"
>Folder ID</span
>
<span class="mt-1 text-sm leading-tight font-medium text-gray-500 font-mono">
<span class="inline-flex mr-4">
<Folder size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
{folder_id}
</span>
<span class="uppercase tracking-wide text-sm text-indigo-600 font-bold ml-4"
>DATETIME</span
>
<span class="mt-1 text-xs leading-tight font-xs text-gray-500 font-mono">
<span class="inline-flex mr-4">
<Hash size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
{id}
</span>
<span class="inline-flex mr-4">
<FileClock size={16} class="uppercase tracking-wide text-sm text-indigo-600 font-bold mr-1"/>
{new Date(created_at).toLocaleString()}
</span>
</span>
<div>
<span class="mt-1 text-xs leading-tight font-xs text-gray-500 font-mono">
{filepath}
</span>
</div>
</div>
<div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">Image Title</div>
<p class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">
{title}
</p>
<div class="flex-grow overflow-hidden">
<a href={video} target="_blank" rel="noopener noreferrer" class="block h-full">
<img
class="w-full h-full object-contain rounded-lg drop-shadow-md"
src={image}
alt={title}
/>
</a>
</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="mt-2 text-gray-600">
<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="mt-2 text-gray-600">
{#each metadata_entries as entry}
{#each sortedMetadataEntries as entry}
<div class="mb-2">
<span class="font-bold flex items-center">
{entry.key}

View File

@ -8,6 +8,60 @@
export let class_ = '';
export let withBorder = true;
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 mShape = withBorder
@ -83,7 +137,7 @@
}
function generateMemosLogo(size: number, withBorder: boolean): string {
const matrix = prepareMatrixFromRandomColors(withBorder);
const matrix = prepareMatrixFromFixedIndexAndLittleRadom(withBorder);
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

@ -60,3 +60,31 @@ export const flyAndScale = (
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 Logo from '$lib/components/Logo.svelte';
import { onMount } from 'svelte';
import { appIconMap } from '$lib/utils';
import LucideIcon from '$lib/components/LucideIcon.svelte';
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>
<svelte:head>
@ -330,11 +350,7 @@
>
<div class="px-4 pt-4">
<h2 class="line-clamp-2 h-12">
{hit.document.metadata_entries &&
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)}
{getEntityTitle(hit.document)}
</h2>
<p class="text-gray-700 text-xs">
{formatDistanceToNow(new Date(hit.document.file_created_at * 1000), {
@ -348,11 +364,12 @@
src={`${apiEndpoint}/files/${hit.document.filepath}`}
alt=""
/>
{#if hit.document.metadata_entries && hit.document.metadata_entries.some((entry) => entry.key === 'active_app')}
{#if getAppName(hit.document)}
<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>
{/if}
</figure>
@ -376,7 +393,8 @@
video={`${apiEndpoint}/files/video/${searchResult.hits[selectedImage].document.filepath}`}
created_at={searchResult.hits[selectedImage].document.file_created_at * 1000}
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}
metadata_entries={searchResult.hits[selectedImage].document.metadata_entries}
onClose={closeModal}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 18 KiB