mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 13:14:04 +02:00
feat: better implementation of info rows
This commit is contained in:
parent
c42141f7c7
commit
5943fc86fb
3 changed files with 190 additions and 150 deletions
299
pages/index.vue
299
pages/index.vue
|
@ -129,130 +129,10 @@
|
||||||
class="information"
|
class="information"
|
||||||
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
||||||
<div class="title">Information</div>
|
<div class="title">Information</div>
|
||||||
<div class="info-content" v-if="selectedItem">
|
<div class="info-content" v-if="selectedItem && getInfo">
|
||||||
<!-- Common Information -->
|
<div class="info-row" v-for="(row, index) in infoRows" :key="index">
|
||||||
<div class="info-row">
|
<p class="label">{{ row.label }}</p>
|
||||||
<p class="label">Source</p>
|
<span>{{ row.value }}</span>
|
||||||
<span>{{ selectedItem.source }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Content Type</p>
|
|
||||||
<span>{{
|
|
||||||
selectedItem.content_type.charAt(0).toUpperCase() +
|
|
||||||
selectedItem.content_type.slice(1)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Text Information -->
|
|
||||||
<template v-if="selectedItem.content_type === ContentType.Text">
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Characters</p>
|
|
||||||
<span>{{ getCharacterCount }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Words</p>
|
|
||||||
<span>{{ getWordCount }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Image Information -->
|
|
||||||
<template v-if="selectedItem.content_type === ContentType.Image">
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Dimensions</p>
|
|
||||||
<span>{{ imageDimensions[selectedItem.id] || "Loading..." }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Image size</p>
|
|
||||||
<span>{{ imageSizes[selectedItem.id] || "Loading..." }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- File Information -->
|
|
||||||
<template v-if="selectedItem.content_type === ContentType.File">
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Path</p>
|
|
||||||
<span>{{ selectedItem.content }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Link Information -->
|
|
||||||
<template v-if="selectedItem.content_type === ContentType.Link">
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">URL</p>
|
|
||||||
<span>{{ selectedItem.content }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Characters</p>
|
|
||||||
<span>{{ getCharacterCount }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Color Information -->
|
|
||||||
<template v-if="selectedItem.content_type === ContentType.Color">
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Hex Code</p>
|
|
||||||
<span>{{ selectedItem.content }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">RGB</p>
|
|
||||||
<span>{{
|
|
||||||
selectedItem.content.startsWith("#")
|
|
||||||
? `rgb(${parseInt(
|
|
||||||
selectedItem.content.slice(1, 3),
|
|
||||||
16
|
|
||||||
)}, ${parseInt(
|
|
||||||
selectedItem.content.slice(3, 5),
|
|
||||||
16
|
|
||||||
)}, ${parseInt(selectedItem.content.slice(5, 7), 16)})`
|
|
||||||
: selectedItem.content
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">HSL</p>
|
|
||||||
<span>{{
|
|
||||||
selectedItem.content.startsWith("#")
|
|
||||||
? (() => {
|
|
||||||
const r =
|
|
||||||
parseInt(selectedItem.content.slice(1, 3), 16) / 255;
|
|
||||||
const g =
|
|
||||||
parseInt(selectedItem.content.slice(3, 5), 16) / 255;
|
|
||||||
const b =
|
|
||||||
parseInt(selectedItem.content.slice(5, 7), 16) / 255;
|
|
||||||
const max = Math.max(r, g, b);
|
|
||||||
const min = Math.min(r, g, b);
|
|
||||||
const l = (max + min) / 2;
|
|
||||||
const d = max - min;
|
|
||||||
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
||||||
let h = 0;
|
|
||||||
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
||||||
if (max === g) h = (b - r) / d + 2;
|
|
||||||
if (max === b) h = (r - g) / d + 4;
|
|
||||||
h = Math.round(h * 60);
|
|
||||||
return `hsl(${h}, ${Math.round(s * 100)}%, ${Math.round(
|
|
||||||
l * 100
|
|
||||||
)}%)`;
|
|
||||||
})()
|
|
||||||
: selectedItem.content
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Code Information -->
|
|
||||||
<template v-if="selectedItem.content_type === ContentType.Code">
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Language</p>
|
|
||||||
<span>{{ selectedItem.language }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Lines</p>
|
|
||||||
<span>{{ getLineCount }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Common Information -->
|
|
||||||
<div class="info-row">
|
|
||||||
<p class="label">Copied</p>
|
|
||||||
<span>{{ getFormattedDate }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
|
@ -269,7 +149,9 @@ import { platform } from "@tauri-apps/plugin-os";
|
||||||
import { enable, isEnabled } from "@tauri-apps/plugin-autostart";
|
import { enable, isEnabled } from "@tauri-apps/plugin-autostart";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { useNuxtApp } from "#app";
|
import { useNuxtApp } from "#app";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { HistoryItem, ContentType } from "~/types/types";
|
import { HistoryItem, ContentType } from "~/types/types";
|
||||||
|
import type { InfoText, InfoImage, InfoFile, InfoLink, InfoColor, InfoCode } from "~/types/types";
|
||||||
|
|
||||||
interface GroupedHistory {
|
interface GroupedHistory {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -299,6 +181,8 @@ const imageSizes = shallowRef<Record<string, string>>({});
|
||||||
const lastUpdateTime = ref<number>(Date.now());
|
const lastUpdateTime = ref<number>(Date.now());
|
||||||
const imageLoadError = ref<boolean>(false);
|
const imageLoadError = ref<boolean>(false);
|
||||||
const imageLoading = ref<boolean>(false);
|
const imageLoading = ref<boolean>(false);
|
||||||
|
const pageTitle = ref<string>('');
|
||||||
|
const pageOgImage = ref<string>('');
|
||||||
|
|
||||||
const keyboard = useKeyboard();
|
const keyboard = useKeyboard();
|
||||||
|
|
||||||
|
@ -385,7 +269,9 @@ const loadHistoryChunk = async (): Promise<void> => {
|
||||||
item.source,
|
item.source,
|
||||||
item.content_type,
|
item.content_type,
|
||||||
item.content,
|
item.content,
|
||||||
item.favicon
|
item.favicon,
|
||||||
|
item.source_icon,
|
||||||
|
item.language
|
||||||
);
|
);
|
||||||
Object.assign(historyItem, {
|
Object.assign(historyItem, {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
@ -776,11 +662,6 @@ watch([selectedGroupIndex, selectedItemIndex], () =>
|
||||||
scrollToSelectedItem(false)
|
scrollToSelectedItem(false)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getComputedImageUrl = (item: HistoryItem | null): string => {
|
|
||||||
if (!item) return "";
|
|
||||||
return imageUrls.value[item.id] || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCharacterCount = computed(() => {
|
const getCharacterCount = computed(() => {
|
||||||
return selectedItem.value?.content.length ?? 0;
|
return selectedItem.value?.content.length ?? 0;
|
||||||
});
|
});
|
||||||
|
@ -808,6 +689,164 @@ const formatFileSize = (bytes: number): string => {
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchPageMeta = async (url: string) => {
|
||||||
|
try {
|
||||||
|
console.log('Fetching metadata for:', url);
|
||||||
|
const [title, ogImage] = await invoke('fetch_page_meta', { url }) as [string, string | null];
|
||||||
|
console.log('Received title:', title);
|
||||||
|
pageTitle.value = title;
|
||||||
|
if (ogImage) {
|
||||||
|
pageOgImage.value = ogImage;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching page meta:', error);
|
||||||
|
pageTitle.value = 'Error loading title';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => selectedItem.value, (newItem) => {
|
||||||
|
if (newItem?.content_type === ContentType.Link) {
|
||||||
|
pageTitle.value = 'Loading...';
|
||||||
|
pageOgImage.value = '';
|
||||||
|
fetchPageMeta(newItem.content);
|
||||||
|
} else {
|
||||||
|
pageTitle.value = '';
|
||||||
|
pageOgImage.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getInfo = computed(() => {
|
||||||
|
if (!selectedItem.value) return null;
|
||||||
|
|
||||||
|
const baseInfo = {
|
||||||
|
source: selectedItem.value.source,
|
||||||
|
copied: selectedItem.value.timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
const infoMap: Record<ContentType, () => InfoText | InfoImage | InfoFile | InfoLink | InfoColor | InfoCode> = {
|
||||||
|
[ContentType.Text]: () => ({
|
||||||
|
...baseInfo,
|
||||||
|
content_type: ContentType.Text,
|
||||||
|
characters: selectedItem.value!.content.length,
|
||||||
|
words: selectedItem.value!.content.trim().split(/\s+/).length,
|
||||||
|
}),
|
||||||
|
[ContentType.Image]: () => ({
|
||||||
|
...baseInfo,
|
||||||
|
content_type: ContentType.Image,
|
||||||
|
dimensions: imageDimensions.value[selectedItem.value!.id] || "Loading...",
|
||||||
|
size: parseInt(imageSizes.value[selectedItem.value!.id] || "0"),
|
||||||
|
}),
|
||||||
|
[ContentType.File]: () => ({
|
||||||
|
...baseInfo,
|
||||||
|
content_type: ContentType.File,
|
||||||
|
path: selectedItem.value!.content,
|
||||||
|
filesize: 0,
|
||||||
|
}),
|
||||||
|
[ContentType.Link]: () => ({
|
||||||
|
...baseInfo,
|
||||||
|
content_type: ContentType.Link,
|
||||||
|
title: pageTitle.value,
|
||||||
|
url: selectedItem.value!.content,
|
||||||
|
characters: selectedItem.value!.content.length,
|
||||||
|
}),
|
||||||
|
[ContentType.Color]: () => {
|
||||||
|
const hex = selectedItem.value!.content;
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16);
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16);
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16);
|
||||||
|
|
||||||
|
const rNorm = r / 255;
|
||||||
|
const gNorm = g / 255;
|
||||||
|
const bNorm = b / 255;
|
||||||
|
|
||||||
|
const max = Math.max(rNorm, gNorm, bNorm);
|
||||||
|
const min = Math.min(rNorm, gNorm, bNorm);
|
||||||
|
let h = 0, s = 0;
|
||||||
|
const l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max !== min) {
|
||||||
|
const d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case rNorm:
|
||||||
|
h = (gNorm - bNorm) / d + (gNorm < bNorm ? 6 : 0);
|
||||||
|
break;
|
||||||
|
case gNorm:
|
||||||
|
h = (bNorm - rNorm) / d + 2;
|
||||||
|
break;
|
||||||
|
case bNorm:
|
||||||
|
h = (rNorm - gNorm) / d + 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseInfo,
|
||||||
|
content_type: ContentType.Color,
|
||||||
|
hex: hex,
|
||||||
|
rgb: `rgb(${r}, ${g}, ${b})`,
|
||||||
|
hsl: `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ContentType.Code]: () => ({
|
||||||
|
...baseInfo,
|
||||||
|
content_type: ContentType.Code,
|
||||||
|
language: selectedItem.value!.language ?? "Unknown",
|
||||||
|
lines: selectedItem.value!.content.split('\n').length,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return infoMap[selectedItem.value.content_type]();
|
||||||
|
});
|
||||||
|
|
||||||
|
const infoRows = computed(() => {
|
||||||
|
if (!getInfo.value) return [];
|
||||||
|
|
||||||
|
const commonRows = [
|
||||||
|
{ label: "Source", value: getInfo.value.source },
|
||||||
|
{ label: "Content Type", value: getInfo.value.content_type.charAt(0).toUpperCase() + getInfo.value.content_type.slice(1) },
|
||||||
|
];
|
||||||
|
|
||||||
|
const typeSpecificRows: Record<ContentType, Array<{ label: string; value: string | number }>> = {
|
||||||
|
[ContentType.Text]: [
|
||||||
|
{ label: "Characters", value: (getInfo.value as InfoText).characters },
|
||||||
|
{ label: "Words", value: (getInfo.value as InfoText).words },
|
||||||
|
],
|
||||||
|
[ContentType.Image]: [
|
||||||
|
{ label: "Dimensions", value: (getInfo.value as InfoImage).dimensions },
|
||||||
|
{ label: "Image size", value: formatFileSize((getInfo.value as InfoImage).size) },
|
||||||
|
],
|
||||||
|
[ContentType.File]: [
|
||||||
|
{ label: "Path", value: (getInfo.value as InfoFile).path },
|
||||||
|
],
|
||||||
|
[ContentType.Link]: [
|
||||||
|
{ label: "Title", value: (getInfo.value as InfoLink).title || "No Title Found" },
|
||||||
|
{ label: "URL", value: (getInfo.value as InfoLink).url },
|
||||||
|
{ label: "Characters", value: (getInfo.value as InfoLink).characters },
|
||||||
|
],
|
||||||
|
[ContentType.Color]: [
|
||||||
|
{ label: "Hex Code", value: (getInfo.value as InfoColor).hex },
|
||||||
|
{ label: "RGB", value: (getInfo.value as InfoColor).rgb },
|
||||||
|
{ label: "HSL", value: (getInfo.value as InfoColor).hsl },
|
||||||
|
],
|
||||||
|
[ContentType.Code]: [
|
||||||
|
{ label: "Language", value: (getInfo.value as InfoCode).language },
|
||||||
|
{ label: "Lines", value: (getInfo.value as InfoCode).lines },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const specificRows = typeSpecificRows[getInfo.value.content_type]
|
||||||
|
.filter(row => row.value !== "");
|
||||||
|
|
||||||
|
return [
|
||||||
|
...commonRows,
|
||||||
|
...specificRows,
|
||||||
|
{ label: "Copied", value: getFormattedDate.value },
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub enum ContentType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Text {
|
pub struct InfoText {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub characters: i32,
|
pub characters: i32,
|
||||||
|
@ -36,7 +36,7 @@ pub struct Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Image {
|
pub struct InfoImage {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub dimensions: String,
|
pub dimensions: String,
|
||||||
|
@ -45,7 +45,7 @@ pub struct Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct File {
|
pub struct InfoFile {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
@ -54,26 +54,26 @@ pub struct File {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Link {
|
pub struct InfoLink {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
pub link: String,
|
pub url: String,
|
||||||
pub characters: i32,
|
pub characters: i32,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Color {
|
pub struct InfoColor {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub hexcode: String,
|
pub hex: String,
|
||||||
pub rgba: String,
|
pub rgb: String,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Code {
|
pub struct InfoCode {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub language: String,
|
pub language: String,
|
||||||
|
|
|
@ -65,7 +65,7 @@ export interface Settings {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Text {
|
export interface InfoText {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Text;
|
content_type: ContentType.Text;
|
||||||
characters: number;
|
characters: number;
|
||||||
|
@ -73,7 +73,7 @@ export interface Text {
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Image {
|
export interface InfoImage {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Image;
|
content_type: ContentType.Image;
|
||||||
dimensions: string;
|
dimensions: string;
|
||||||
|
@ -81,7 +81,7 @@ export interface Image {
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface File {
|
export interface InfoFile {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.File;
|
content_type: ContentType.File;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -89,24 +89,25 @@ export interface File {
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Link {
|
export interface InfoLink {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Link;
|
content_type: ContentType.Link;
|
||||||
title: string;
|
title?: string;
|
||||||
link: string;
|
url: string;
|
||||||
characters: number;
|
characters: number;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Color {
|
export interface InfoColor {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Color;
|
content_type: ContentType.Color;
|
||||||
hexcode: string;
|
hex: string;
|
||||||
rgba: string;
|
rgb: string;
|
||||||
|
hsl: string;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Code {
|
export interface InfoCode {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Code;
|
content_type: ContentType.Code;
|
||||||
language: string;
|
language: string;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue