mirror of
https://github.com/tcsenpai/pensieve.git
synced 2025-06-06 19:25:24 +00:00
feat(web): ui update
This commit is contained in:
parent
955ec76638
commit
42e8421ab4
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
6
web/src/lib/components/LucideIcon.svelte
Normal file
6
web/src/lib/components/LucideIcon.svelte
Normal file
@ -0,0 +1,6 @@
|
||||
<script>
|
||||
import * as icons from 'lucide-svelte';
|
||||
export let name;
|
||||
</script>
|
||||
|
||||
<svelte:component this="{icons[name]}" {...$$props} />
|
@ -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",
|
||||
};
|
||||
|
@ -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 |
Loading…
x
Reference in New Issue
Block a user