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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimelight-components",
3
- "version": "2.1.15",
3
+ "version": "2.1.17",
4
4
  "docs": "https://rimelight.com/tools/rimelight-components",
5
5
  "configKey": "rimelightComponents",
6
6
  "compatibility": {
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.15";
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;
@@ -10,7 +10,7 @@ const {} = defineProps({
10
10
 
11
11
  <template>
12
12
  <UCard>
13
- <NuxtImg :src="src" :alt="alt" />
13
+ <RCImage :src="src" :alt="alt" />
14
14
  <div class="flex flex-col gap-xs">
15
15
  <h3 class="text-xl font-bold">
16
16
  {{ name }}
@@ -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
- <NuxtImg
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
- <NuxtImg
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
- <NuxtImg
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
- <h3>
48
- {{ getLocalizedContent(page.title, locale) }}
49
- </h3>
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
- <NuxtImg :src="item.img.src" :alt="item.img.alt" class="w-full object-cover" />
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
- <NuxtImg
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
- <NuxtImg
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
- <NuxtImg :src="item.img.src" :alt="item.img.alt" class="w-full object-cover" />
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
- <NuxtImg
118
- :src="page.images[0].src"
119
- :alt="page.images[0].alt"
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
- <NuxtImg
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
- <NuxtImg
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 { ref, computed } from "vue";
2
- const layers = ref([]);
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 { ref, computed } from "vue";
2
- const layers = ref([]);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimelight-components",
3
- "version": "2.1.15",
3
+ "version": "2.1.17",
4
4
  "description": "A component library by Rimelight Entertainment.",
5
5
  "keywords": [
6
6
  "nuxt",