rimelight-components 2.1.15 → 2.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/app/Image.d.vue.ts +11 -0
- package/dist/runtime/components/app/Image.vue +174 -0
- package/dist/runtime/components/app/Image.vue.d.ts +11 -0
- package/dist/runtime/components/cards/TeamCard.vue +1 -1
- package/dist/runtime/components/page/PageEditor.vue +2 -2
- package/dist/runtime/components/page/PagePropertiesEditor.vue +11 -6
- package/dist/runtime/components/page/PagePropertiesRenderer.vue +5 -7
- package/dist/runtime/components/page/PageRenderer.vue +2 -2
- package/dist/runtime/composables/useHeaderStack.d.ts +7 -5
- package/dist/runtime/composables/useHeaderStack.js +3 -2
- package/dist/runtime/composables/useHeaderStack.mjs +3 -2
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { readdirSync } from 'node:fs';
|
|
|
4
4
|
import { basename } from 'node:path';
|
|
5
5
|
|
|
6
6
|
const name = "rimelight-components";
|
|
7
|
-
const version = "2.1.
|
|
7
|
+
const version = "2.1.17";
|
|
8
8
|
const homepage = "https://rimelight.com/tools/rimelight-components";
|
|
9
9
|
|
|
10
10
|
const defaultOptions = {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ImageWrapperProps {
|
|
2
|
+
src: string;
|
|
3
|
+
alt?: string;
|
|
4
|
+
height?: string | number;
|
|
5
|
+
width?: string | number;
|
|
6
|
+
loading?: 'lazy' | 'eager';
|
|
7
|
+
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
8
|
+
}
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<ImageWrapperProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageWrapperProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, reactive, onMounted, useTemplateRef, watch, nextTick } from "vue";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
import { useImage } from "#imports";
|
|
5
|
+
const { src, alt = "Image", height, width, loading = "lazy", fit = "cover" } = defineProps({
|
|
6
|
+
src: { type: String, required: true },
|
|
7
|
+
alt: { type: String, required: false },
|
|
8
|
+
height: { type: [String, Number], required: false },
|
|
9
|
+
width: { type: [String, Number], required: false },
|
|
10
|
+
loading: { type: String, required: false },
|
|
11
|
+
fit: { type: String, required: false }
|
|
12
|
+
});
|
|
13
|
+
const img = useImage();
|
|
14
|
+
const isOpen = ref(false);
|
|
15
|
+
const imgRef = ref(null);
|
|
16
|
+
const imgElement = useTemplateRef("imgRef");
|
|
17
|
+
const metadata = reactive({
|
|
18
|
+
width: 0,
|
|
19
|
+
height: 0,
|
|
20
|
+
size: "",
|
|
21
|
+
format: "",
|
|
22
|
+
mimeType: ""
|
|
23
|
+
});
|
|
24
|
+
const imageStyles = tv({
|
|
25
|
+
base: "cursor-pointer transition-transform duration-300",
|
|
26
|
+
variants: {
|
|
27
|
+
isExpanded: {
|
|
28
|
+
true: "w-full h-full object-contain mx-auto block rounded-lg",
|
|
29
|
+
false: "w-full h-full object-cover hover:scale-[1.02] active:scale-95"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
function formatBytes(bytes) {
|
|
34
|
+
if (bytes === 0) return "0 Bytes";
|
|
35
|
+
const k = 1024;
|
|
36
|
+
const sizes = ["Bytes", "KB", "MB"];
|
|
37
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
38
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
39
|
+
}
|
|
40
|
+
async function fetchExtendedMetadata() {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(src, {
|
|
43
|
+
method: "GET",
|
|
44
|
+
mode: "cors"
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) throw new Error("Network response was not ok");
|
|
47
|
+
const blob = await response.blob();
|
|
48
|
+
metadata.size = formatBytes(blob.size);
|
|
49
|
+
const type = response.headers.get("content-type") || blob.type;
|
|
50
|
+
if (type) {
|
|
51
|
+
metadata.mimeType = type;
|
|
52
|
+
metadata.format = type.split("/")[1]?.split("+")[0]?.toUpperCase() || "IMG";
|
|
53
|
+
}
|
|
54
|
+
if (metadata.format === "SVG") {
|
|
55
|
+
const tempImg = new Image();
|
|
56
|
+
tempImg.src = URL.createObjectURL(blob);
|
|
57
|
+
await tempImg.decode();
|
|
58
|
+
metadata.width = tempImg.naturalWidth;
|
|
59
|
+
metadata.height = tempImg.naturalHeight;
|
|
60
|
+
URL.revokeObjectURL(tempImg.src);
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error("Metadata fetch failed:", e);
|
|
64
|
+
metadata.format = src.split(".").pop()?.toUpperCase() || "IMG";
|
|
65
|
+
metadata.size = "Unknown";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function updateMetadata(el) {
|
|
69
|
+
if (!import.meta.client || !el) return;
|
|
70
|
+
if (el.naturalWidth > 0) {
|
|
71
|
+
metadata.width = el.naturalWidth;
|
|
72
|
+
metadata.height = el.naturalHeight;
|
|
73
|
+
fetchExtendedMetadata();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function handleImageLoad(event) {
|
|
77
|
+
updateMetadata(event.currentTarget);
|
|
78
|
+
}
|
|
79
|
+
async function downloadImage() {
|
|
80
|
+
try {
|
|
81
|
+
const response = await fetch(src);
|
|
82
|
+
const blob = await response.blob();
|
|
83
|
+
const url = window.URL.createObjectURL(blob);
|
|
84
|
+
const link = document.createElement("a");
|
|
85
|
+
link.href = url;
|
|
86
|
+
link.download = `file-${Date.now()}.${metadata.format.toLowerCase()}`;
|
|
87
|
+
document.body.appendChild(link);
|
|
88
|
+
link.click();
|
|
89
|
+
document.body.removeChild(link);
|
|
90
|
+
window.URL.revokeObjectURL(url);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("Download failed", error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
onMounted(() => {
|
|
96
|
+
nextTick(() => {
|
|
97
|
+
const el = imgElement.value?.$el;
|
|
98
|
+
if (el) {
|
|
99
|
+
if (el.complete) {
|
|
100
|
+
updateMetadata(el);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
watch(() => imgElement.value, (newVal) => {
|
|
106
|
+
const el = newVal?.$el;
|
|
107
|
+
if (el && el.complete && el.naturalWidth > 0) {
|
|
108
|
+
updateMetadata(el);
|
|
109
|
+
}
|
|
110
|
+
}, { immediate: true });
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<UModal v-model="isOpen" title="Image Viewer" :description="`${src}`" :ui="{
|
|
115
|
+
content: 'w-fit max-w-[98vw] sm:max-w-[95vw] mx-auto'
|
|
116
|
+
}">
|
|
117
|
+
<template #default>
|
|
118
|
+
<div class="relative">
|
|
119
|
+
<NuxtImg
|
|
120
|
+
ref="imgRef"
|
|
121
|
+
:src="src"
|
|
122
|
+
:alt="alt"
|
|
123
|
+
:height="height"
|
|
124
|
+
:width="width"
|
|
125
|
+
:loading="loading"
|
|
126
|
+
:class="imageStyles({ isExpanded: false })"
|
|
127
|
+
@click="isOpen = true"
|
|
128
|
+
@load="handleImageLoad"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</template>
|
|
132
|
+
|
|
133
|
+
<template #body>
|
|
134
|
+
<div class="flex flex-col gap-md">
|
|
135
|
+
<div class="flex-1 min-h-0 w-full flex items-center">
|
|
136
|
+
<NuxtImg
|
|
137
|
+
:src="src"
|
|
138
|
+
:alt="alt"
|
|
139
|
+
:class="imageStyles({ isExpanded: true })"
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<USeparator />
|
|
144
|
+
|
|
145
|
+
<div class="flex items-center justify-between gap-xl">
|
|
146
|
+
<div class="flex flex-col gap-xs">
|
|
147
|
+
<div class="flex flex-row gap-xs items-center">
|
|
148
|
+
<UIcon name="lucide:image" class="size-4" />
|
|
149
|
+
<p class="text-sm">Source: <span class="text-dimmed">{{ src }}</span></p>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="flex flex-row gap-xs items-center">
|
|
152
|
+
<UIcon name="lucide:file-question-mark" class="size-4" />
|
|
153
|
+
<p class="text-sm">Format: <span class="text-dimmed">{{ metadata.format }}</span></p>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="flex flex-row gap-xs items-center">
|
|
156
|
+
<UIcon name="lucide:weight" class="size-4" />
|
|
157
|
+
<p class="text-sm">Size: <span class="text-dimmed">{{ metadata.size || "Unknown" }}</span></p>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="flex flex-row gap-xs items-center">
|
|
160
|
+
<UIcon name="lucide:image-upscale" class="size-4" />
|
|
161
|
+
<p class="text-sm">Dimensions: <span class="text-dimmed">{{ metadata.width }} × {{ metadata.height }}</span></p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<UButton
|
|
166
|
+
icon="lucide:download"
|
|
167
|
+
label="Download Original"
|
|
168
|
+
@click="downloadImage"
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</template>
|
|
173
|
+
</UModal>
|
|
174
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ImageWrapperProps {
|
|
2
|
+
src: string;
|
|
3
|
+
alt?: string;
|
|
4
|
+
height?: string | number;
|
|
5
|
+
width?: string | number;
|
|
6
|
+
loading?: 'lazy' | 'eager';
|
|
7
|
+
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
8
|
+
}
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<ImageWrapperProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageWrapperProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -190,7 +190,7 @@ const handleDeleteConfirm = async () => {
|
|
|
190
190
|
</RCPageTOC>
|
|
191
191
|
<RCPagePropertiesEditor v-model="page" class="order-1 lg:order-2 lg:col-span-6" />
|
|
192
192
|
<div class="order-2 lg:order-1 lg:col-span-14 flex flex-col gap-xl">
|
|
193
|
-
<
|
|
193
|
+
<RCImage
|
|
194
194
|
v-if="page.banner?.src"
|
|
195
195
|
:src="page.banner?.src"
|
|
196
196
|
:alt="page.banner?.alt"
|
|
@@ -203,7 +203,7 @@ const handleDeleteConfirm = async () => {
|
|
|
203
203
|
>
|
|
204
204
|
<template #title>
|
|
205
205
|
<div class="flex flex-row gap-sm">
|
|
206
|
-
<
|
|
206
|
+
<RCImage
|
|
207
207
|
v-if="page.icon?.src"
|
|
208
208
|
:src="page.icon?.src"
|
|
209
209
|
:alt="page.icon?.alt"
|
|
@@ -37,16 +37,21 @@ const updateTextArray = (schema, vals) => {
|
|
|
37
37
|
>
|
|
38
38
|
<template #header>
|
|
39
39
|
<div class="flex flex-col gap-xs items-center">
|
|
40
|
-
<
|
|
40
|
+
<RCImage
|
|
41
41
|
v-if="page.icon?.src"
|
|
42
42
|
:src="page.icon?.src"
|
|
43
43
|
:alt="page.icon?.alt"
|
|
44
44
|
class="rounded-full w-12 h-12 object-cover"
|
|
45
45
|
/>
|
|
46
46
|
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
<UInput
|
|
48
|
+
v-model="page.title.en"
|
|
49
|
+
variant="subtle"
|
|
50
|
+
placeholder="Enter page title..."
|
|
51
|
+
size="xl"
|
|
52
|
+
:ui="{ base: 'text-center font-bold text-lg' }"
|
|
53
|
+
class="w-full"
|
|
54
|
+
/>
|
|
50
55
|
|
|
51
56
|
<span class="text-sm">{{ t(getTypeLabelKey(page.type)) }}</span>
|
|
52
57
|
|
|
@@ -73,12 +78,12 @@ const updateTextArray = (schema, vals) => {
|
|
|
73
78
|
class="w-full"
|
|
74
79
|
>
|
|
75
80
|
<template #content="{ item }">
|
|
76
|
-
<
|
|
81
|
+
<RCImage :src="item.img.src" :alt="item.img.alt" class="w-full object-cover" />
|
|
77
82
|
</template>
|
|
78
83
|
</UTabs>
|
|
79
84
|
|
|
80
85
|
<div v-else-if="page.images[0]">
|
|
81
|
-
<
|
|
86
|
+
<RCImage
|
|
82
87
|
:src="page.images[0].src"
|
|
83
88
|
:alt="page.images[0].alt"
|
|
84
89
|
class="w-full object-cover"
|
|
@@ -73,7 +73,7 @@ const imageTabs = computed(() => {
|
|
|
73
73
|
>
|
|
74
74
|
<template #header>
|
|
75
75
|
<div class="flex flex-col gap-xs items-center">
|
|
76
|
-
<
|
|
76
|
+
<RCImage
|
|
77
77
|
v-if="page.icon?.src"
|
|
78
78
|
:src="page.icon?.src"
|
|
79
79
|
:alt="page.icon?.alt"
|
|
@@ -109,16 +109,14 @@ const imageTabs = computed(() => {
|
|
|
109
109
|
class="w-full"
|
|
110
110
|
>
|
|
111
111
|
<template #content="{ item }">
|
|
112
|
-
<
|
|
112
|
+
<RCImage :src="item.img.src" :alt="item.img.alt" class="w-full object-cover" />
|
|
113
113
|
</template>
|
|
114
114
|
</UTabs>
|
|
115
115
|
|
|
116
116
|
<div v-else-if="page.images[0]">
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class="w-full object-cover"
|
|
121
|
-
/>
|
|
117
|
+
<RCImage :src="page.images[0].src"
|
|
118
|
+
:alt="page.images[0].alt"
|
|
119
|
+
class="w-full object-cover" />
|
|
122
120
|
</div>
|
|
123
121
|
</div>
|
|
124
122
|
</div>
|
|
@@ -32,7 +32,7 @@ const hasSurround = computed(() => !!(surround?.previous || surround?.next));
|
|
|
32
32
|
</RCPageTOC>
|
|
33
33
|
<RCPagePropertiesRenderer v-model="page" class="order-1 lg:order-2 lg:col-span-6" />
|
|
34
34
|
<div class="order-2 lg:order-1 lg:col-span-14 flex flex-col gap-xl">
|
|
35
|
-
<
|
|
35
|
+
<RCImage
|
|
36
36
|
v-if="page.banner?.src"
|
|
37
37
|
:src="page.banner?.src"
|
|
38
38
|
:alt="page.banner?.alt"
|
|
@@ -45,7 +45,7 @@ const hasSurround = computed(() => !!(surround?.previous || surround?.next));
|
|
|
45
45
|
>
|
|
46
46
|
<template #title>
|
|
47
47
|
<div class="flex flex-row gap-sm">
|
|
48
|
-
<
|
|
48
|
+
<RCImage
|
|
49
49
|
v-if="page.icon?.src"
|
|
50
50
|
:src="page.icon?.src"
|
|
51
51
|
:alt="page.icon?.alt"
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
interface HeaderLayer {
|
|
2
|
+
id: string;
|
|
3
|
+
height: number;
|
|
4
|
+
order: number;
|
|
5
|
+
}
|
|
1
6
|
export declare const useHeaderStack: () => {
|
|
2
7
|
registerHeader: (id: string, height: number, order?: number) => void;
|
|
3
8
|
unregisterHeader: (id: string) => void;
|
|
4
9
|
totalOffset: import("vue").ComputedRef<number>;
|
|
5
10
|
getOffsetFor: (id: string) => number;
|
|
6
|
-
layers: import("vue").ComputedRef<
|
|
7
|
-
id: string;
|
|
8
|
-
height: number;
|
|
9
|
-
order: number;
|
|
10
|
-
}[]>;
|
|
11
|
+
layers: import("vue").ComputedRef<HeaderLayer[]>;
|
|
11
12
|
};
|
|
13
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { useState } from "#app";
|
|
3
3
|
export const useHeaderStack = () => {
|
|
4
|
+
const layers = useState("header-layers", () => []);
|
|
4
5
|
const registerHeader = (id, height, order = 10) => {
|
|
5
6
|
const existingLayer = layers.value.find((l) => l.id === id);
|
|
6
7
|
if (existingLayer) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { useState } from "#app";
|
|
3
3
|
export const useHeaderStack = () => {
|
|
4
|
+
const layers = useState("header-layers", () => []);
|
|
4
5
|
const registerHeader = (id, height, order = 10) => {
|
|
5
6
|
const existingLayer = layers.value.find((l) => l.id === id);
|
|
6
7
|
if (existingLayer) {
|