@christianriedl/media 1.0.128 → 1.0.129

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christianriedl/media",
3
- "version": "1.0.128",
3
+ "version": "1.0.129",
4
4
  "description": "RIC media interfaces",
5
5
 
6
6
  "main": "dist/index.js",
@@ -3,10 +3,10 @@
3
3
  import { Helper } from '@christianriedl/utils';
4
4
  import { IMediaFolder, MediaService, IPictureFile, getMediaSymbol } from '@christianriedl/media';
5
5
 
6
- const props = defineProps<{ folder: IMediaFolder, photos: IPictureFile[] }>();
6
+ const props = defineProps<{ folder: IMediaFolder, photos?: IPictureFile[] }>();
7
7
  const emits = defineEmits<{ (e: 'close'): void }>();
8
8
 
9
- const title = `Download '${props.folder.Name}' (${props.folder.ChildCount} Bilder)`
9
+ const title = getTitle();
10
10
  const getMedia = inject(getMediaSymbol)!;
11
11
  const mediaService = getMedia();
12
12
 
@@ -36,17 +36,27 @@
36
36
  }
37
37
  async function onDownload() {
38
38
  const folder = await mediaService.getPhotos(props.folder);
39
+ const downloadUrl = mediaService.getPhotosDownloadUrl(folder, height.value, quality.value, getPhotoIds());
40
+ window.open(downloadUrl);
41
+ emits('close');
42
+ }
43
+ function getTitle(): string {
44
+ let count = getPhotoIds().length;
45
+ if (count == 0)
46
+ count = props.folder.ChildCount;
47
+ return `Download '${props.folder.Name}' (${count} Bilder)`
48
+
49
+ }
50
+ function getPhotoIds(): string[] {
39
51
  const photoIds: string[] = [];
40
52
  if (props.photos) {
41
- for (var i = 0; i < props.photos.value.length; i++) {
42
- const photo = props.photos.value[i];
53
+ for (var i = 0; i < props.photos.length; i++) {
54
+ const photo = props.photos[i];
43
55
  if (photo.selected)
44
56
  photoIds.push(photo.DLNAID);
45
57
  }
46
58
  }
47
- const downloadUrl = mediaService.getPhotosDownloadUrl(folder, height.value, quality.value, photoIds);
48
- window.open(downloadUrl);
49
- emits('close');
59
+ return photoIds;
50
60
  }
51
61
  </script>
52
62
 
@@ -1,50 +1,57 @@
1
1
  <script setup lang="ts">
2
2
  import { inject, ref, reactive, computed, onMounted, onUnmounted, nextTick, watch, StyleValue } from 'vue';
3
- import { VDataTableVirtual } from 'vuetify/labs/VDataTable';
4
3
  import type { ComponentPublicInstance } from 'vue';
5
4
  import { useRouter } from 'vue-router';
6
5
  import { IAppState, IAppConfig, EDevice, EBrowser, appStateSymbol, appConfigSymbol, Helper } from '@christianriedl/utils';
7
- import { EItemType, EMediaType, IMediaFolder, IMediaItem, IPhotoSelection, MediaService, getMediaSymbol, IMediaAppConfig } from '@christianriedl/media';
6
+ import { EItemType, EMediaType, IMediaFolder, IMediaItem, IPhotoSelection, MediaService, IMediaService, getMediaSymbol, getMediaBinSymbol, IMediaAppConfig } from '@christianriedl/media';
8
7
  import FileUpload from '../components/FileUpload.vue';
9
8
  import PhotoDownload from '../components/PhotoDownload.vue';
10
9
 
11
10
  const appState = inject(appStateSymbol)!;
12
11
  const appConfig = inject(appConfigSymbol)!;
13
12
  const mediaAppConfig = appConfig as unknown as IMediaAppConfig;
14
- const getMediaService = inject(getMediaSymbol)!;
15
- const mediaService = getMediaService();
16
- const heightStyle = computed<StyleValue>(() => { return { height: appState.bodyHeight.value + 'px', overflowY: 'auto' } });
13
+ const getMediaService = inject(mediaAppConfig.useMediaBin ? getMediaBinSymbol : getMediaSymbol)!;
14
+ const mediaService = getMediaService() as unknown as IMediaService;
15
+ //const heightStyle = computed<StyleValue>(() => { return { height: appState.bodyHeight.value + 'px', overflowY: 'auto' } });
17
16
  const isMobile = appState.isMobile && (appState.device != EDevice.iPad);
18
17
  const router = useRouter();
19
- const open = ref<string[]>([]);
18
+
19
+ const listHeight = ref(0);
20
+ const listhead = ref<ComponentPublicInstance|null>(null);
20
21
 
21
22
  const items = ref<IMediaFolder[]>([]);
22
23
  const selected = reactive<IPhotoSelection>(mediaService.photoSelection);
23
- const listHeight = ref(appState.bodyHeight.value);
24
- const listhead = ref<ComponentPublicInstance|null>(null);
25
- const table = ref<InstanceType<typeof VDataTableVirtual> | null>(null);
26
24
  const uploadVisible = ref(false);
27
25
 
28
26
  const roots: string[] = ['Jahr', 'Orte', 'Ereignisse', 'Personen'];
29
27
  const criterias: string[] = ['ye', 'lo', 'ev', 'pe'];
30
- const groupBy = ref<any>([{key: 'info', order: 'asc'}]);
31
- const sortBy = ref<any>([{key: 'title', order: 'asc'}]);
32
- const headers= [
33
- { title: 'Title', key: 'title', align: 'start', sortable: false },
34
- { title: 'Actions', key: 'actions', sortable: false }
35
- ];
36
- const grouped = ref(false);
37
28
  const backVisible = ref(false);
29
+ const itemIndex = ref(0);
38
30
  const showDownload = ref(false);
31
+ const showAlbums = ref(false);
39
32
  const downloadFolder = ref<IMediaFolder | null>(null);
40
33
  const selectedYear = ref<number | undefined>(undefined);
41
34
  const selectedName = ref<string | undefined>(undefined);
35
+ const numGridCols = computed(() => computeColumnWidth(selected.selected, appState.pageWidth.value));
36
+ const numRootSelColumns = computed(() => { return 12 - (backVisible.value ? 2 : 0) - 2 - (isMobile ? 0 : 2) });
37
+
38
+ watch(appState.bodyHeight, () => computeListHeight());
42
39
 
43
40
  window.addEventListener('popstate', onPopState);
41
+
42
+ function computeListHeight() {
43
+ nextTick(() => {
44
+ let height = appState.bodyHeight.value;
45
+ if (listhead.value) {
46
+ height = height - (listhead.value.$el as HTMLElement).clientHeight;
47
+ }
48
+ listHeight.value = height;
49
+ })
50
+ }
44
51
  function onPopState(event: any) {
45
52
  if (event.state && event.state.noBackExitsApp && backVisible.value) {
46
53
  event.preventDefault();
47
- listBack();
54
+ goBack();
48
55
  }
49
56
  }
50
57
  onUnmounted(() => {
@@ -54,67 +61,46 @@
54
61
  });
55
62
  onMounted(async () => {
56
63
  mediaService.log.trace('Photos created');
57
- const rc = await mediaService.getLists(EMediaType.Picture);
58
- let root;
64
+ let root: IMediaFolder;
65
+ if (mediaAppConfig.useMediaBin) {
66
+ const rc = await mediaService.initialize(EMediaType.Picture);
67
+ }
68
+ else {
69
+ const rc = await (mediaService as MediaService).getLists(EMediaType.Picture);
70
+ }
59
71
  if (!selected.selected.DLNAID) {
60
- root = await mediaService.initializePhotos(stars(), selected.criteria);
72
+ root = await mediaService.getPhotosByCriteria(stars(), selected.criteria);
61
73
  }
62
74
  else {
63
75
  root = mediaService.getFolder(selected.selected.DLNAID);
64
76
  }
65
- const folders = setSelected(root);
77
+ setSelected(root);
66
78
  if (selected.selectedAlbum)
67
79
  selectedYear.value = selected.selectedAlbum.Year;
68
- const expanded = selected.criteria == 'ye' || root.ItemType == EItemType.PictureCategory;
69
- setGrouped(folders, expanded, selectedYear.value);
70
- const scrollTop = appState.scrollPosition.value;
71
- if (scrollTop > 0 && table.value) {
72
- nextTick(() => {
73
- // Scoll to this position
74
- const el = table.value?._?.vnode?.el;
75
- el?.scrollTo(0, scrollTop);
76
- });
77
- }
78
- mediaService.log.trace(`Photos start with ${selected.selected.DLNAID} scrollTop: ${scrollTop}`);
80
+ const grouped = selected.criteria == 'ye' || root.ItemType == EItemType.PictureCategory;
81
+ mediaService.log.trace(`Photos start with ${selected.selected.DLNAID}`);
79
82
  })
80
- watch(appState.bodyHeight, () => computeListHeight());
81
- function computeListHeight() {
82
- nextTick(() => {
83
- let height = appState.bodyHeight.value;
84
- if (listhead.value) {
85
- height = height - (listhead.value.$el as HTMLElement).clientHeight;
86
- }
87
- listHeight.value = height;
88
- })
89
- }
90
83
  function stars() {
91
84
  return selected.rating ? (selected.rating == 1 ? '*' : '**') : 'all';
92
85
  }
93
86
  function showFolder(folder: IMediaFolder) {
94
- const folders = setSelected(folder);
95
- setGrouped(folders, true);
96
- computeListHeight();
97
- }
98
- function listExpand(folder: IMediaFolder) {
99
- if (folder.Year && folder.Year > 0)
100
- selectedYear.value = folder.Year;
87
+ if (showAlbums.value)
88
+ showPictures(folder);
89
+ else {
90
+ window.history.pushState({ noBackExitsApp: true }, '')
91
+ setSelected(folder);
92
+ }
101
93
  }
102
94
  function showPictures(folder: IMediaFolder) {
103
95
  selected.selectedAlbum = folder;
104
96
  router.push({ path: 'photoalbum', query: { id: folder.DLNAID } });
105
97
  }
106
- function onClick(ev: Event, row: any) {
107
- const folder = row.item.raw as IMediaFolder;
108
- if (folder.info)
109
- showPictures(folder);
110
- else
111
- showFolder(folder);
112
- }
113
- function listBack() {
98
+ function goBack() {
114
99
  const parent = mediaService.getFolder(selected.selected.DLNAParentID);
115
100
  const index = parent.Folders.indexOf(selected.selected);
116
- const folders = setSelected(parent);
117
- setGrouped(folders, false);
101
+ setSelected(parent);
102
+ if (index)
103
+ itemIndex.value = index;
118
104
  }
119
105
  function onLightbox(folder: IMediaFolder) {
120
106
  selected.selectedAlbum = folder;
@@ -125,6 +111,10 @@
125
111
  downloadFolder.value = folder;
126
112
  showDownload.value = true;
127
113
  }
114
+ function onCloseDownload() {
115
+ downloadFolder.value = null;
116
+ showDownload.value = false;
117
+ }
128
118
  async function onShare(folder: IMediaFolder) {
129
119
  const guid = Helper.generateUUID();
130
120
  const url = `https://www.christian-riedl.com/photos?id=${guid}`;
@@ -140,10 +130,6 @@
140
130
  window.alert(`Not Pasted : ${err} !`);
141
131
  }
142
132
  }
143
- function onCloseDownload() {
144
- downloadFolder.value = null;
145
- showDownload.value = false;
146
- }
147
133
  function onRootChange(root: string) {
148
134
  const idx = roots.indexOf(selected.root);
149
135
  selected.criteria = criterias[idx];
@@ -155,77 +141,65 @@
155
141
  initialize().then((v) => { });;
156
142
  }
157
143
  async function initialize(): Promise<boolean> {
158
- let root = await mediaService.initializePhotos(stars(), selected.criteria);
159
- let expanded = selected.criteria == 'ye';
144
+ let root = await mediaService.getPhotosByCriteria(stars(), selected.criteria);
160
145
  if (selected.criteria != 'ye' && selectedName.value) {
161
146
  for (let i = 0; i < root.Folders.length; i++) {
162
147
  if (root.Folders[i].Name == selectedName.value) {
163
148
  root = root.Folders[i];
164
- expanded = true;
165
149
  break;
166
150
  }
167
151
  }
168
152
  }
169
- const folders = setSelected(root);
170
- setGrouped(folders, expanded, selectedYear.value);
153
+ setSelected(root);
171
154
  return true;
172
155
  }
173
- function setSelected(folder: IMediaFolder) : IMediaFolder[] {
156
+ function setSelected(folder: IMediaFolder) {
157
+ mediaService.prepare(folder); // To prepare children
174
158
  selected.selected = folder;
175
159
  if (folder.Year && folder.Year > 0)
176
160
  selectedYear.value = folder.Year;
177
161
  selectedName.value = folder.Name;
178
162
  backVisible.value = selected.selected.ItemType != EItemType.PictureGenreType;
179
-
180
- // calculate list height
163
+ computeColumnWidth(folder, appState.pageWidth.value); // computes showAlbums
181
164
  computeListHeight();
182
- return folder.Folders;
183
- }
184
- function setGrouped(folders: IMediaFolder[], expand: boolean, year?: number) {
185
- grouped.value = expand;
186
- groupBy.value = expand ? [{key: 'info', order: 'asc'}] : [];
187
- const expanded = (expand && folders.length <= 6) ? true : false;
188
- const list: IMediaFolder[] = [];
189
- for (let i = 0; i < folders.length; i++) {
190
- let folder = folders[i];
191
- if (expand) {
192
- if (year && year == folder.Year) {
193
- open.value.push(year.toString());
194
- }
195
- for (let j = 0; j < folder.Folders.length; j++) {
196
- const item = folder.Folders[j];
197
- item.info = folder.title;
198
- list.push(item);
199
- }
200
- }
201
- else {
202
- folder.info = undefined;
203
- list.push(folder);
165
+ items.value = folder.Folders;
166
+ }
167
+ function computeColumnWidth(folder: IMediaFolder, width: number) {
168
+ let nameLength = 9;
169
+ if (folder) {
170
+ const dlnaid = folder.DLNAID;
171
+ if (dlnaid.endsWith('|'))
172
+ nameLength = 9; // Year
173
+ else if (dlnaid.endsWith(".ev"))
174
+ nameLength = 18; // Event
175
+ else if (dlnaid.endsWith(".lo"))
176
+ nameLength = 14; // Location
177
+ else if (dlnaid.endsWith(".pe"))
178
+ nameLength = 12; // Person
179
+ else {
180
+ //nameLength = 32; // Album
181
+ showAlbums.value = true;
182
+ return 12;
204
183
  }
205
184
  }
206
- items.value = list;
207
- }
208
- function onScroll(ev: Event) {
209
- const scrollTop = (ev.target as Element).scrollTop;
210
- appState.scrollPosition.value = scrollTop;
211
- }
212
- function setGroupFunc(toggle:any) : string {
213
- return "";
214
- }
185
+ let numColumns = appState.pageWidth.value / (nameLength * 10);
186
+ let colWidth = Math.ceil(12 / numColumns);
187
+ while (12 % colWidth != 0)
188
+ colWidth++;
189
+ showAlbums.value = false;
190
+ return colWidth;
191
+ }
215
192
  </script>
216
193
  <template>
217
- <v-card ref="listhead" class="bg-media_head">
194
+ <v-card ref="listhead" class="bg-media_head" density="compact">
218
195
  <v-card-title>{{selected.selected.Name}}</v-card-title>
219
196
  <v-card-actions>
220
- <v-btn v-if="backVisible" @click="listBack">
221
- <v-icon size="large" icon="$back" />
197
+ <v-btn v-if="backVisible" @click="goBack">
198
+ <v-icon icon="$back" />
222
199
  </v-btn>
223
200
  <v-rating clearable length="2" v-model="selected.rating" @update:modelValue="onRating" />
224
- <v-select v-model="selected.root"
225
- :items="roots"
226
- persistent-hint
227
- @update:modelValue="onRootChange"
228
- hide-details single-line>
201
+ <v-select v-model="selected.root" :items="roots" density="compact" persistent-hint
202
+ @update:modelValue="onRootChange" hide-details single-line>
229
203
  </v-select>
230
204
  <v-btn v-if="!isMobile" @click="uploadVisible = !uploadVisible">
231
205
  UPLOAD
@@ -234,53 +208,24 @@
234
208
  </v-card-actions>
235
209
  </v-card>
236
210
  <file-upload v-if="uploadVisible" accept="image/jpeg"></file-upload>
237
- <v-data-table-virtual ref="table" :group-by="groupBy" :sort-by="sortBy" :items="items" :headers="headers" class="elevation-1" item-value="title" :height="listHeight"
238
- @click:row="onClick" v-scroll.self="onScroll">
239
- <template v-slot:top="{ toggleGroup, isGroupOpen }">
240
- <p style="display:none">{{setGroupFunc(toggleGroup)}}</p>
241
- </template>
242
- <template v-slot:item.actions="{ item }">
243
- <div v-if="item.raw.info">
244
- <v-icon size="small" icon="$share" @click="onShare(item.raw)"></v-icon>
245
- <v-icon size="small" icon="$download" @click="onDownload(item.raw)"></v-icon>
246
- <v-icon size="small" icon="$grid" @click="onLightbox(item.raw)"></v-icon>
247
- </div>
248
- </template>
249
- </v-data-table-virtual>
250
- <!--
251
- <v-card :max-height="listHeight" class="overflow-y-auto bg-media" ref="scrollElement" v-scroll.self="onScroll">
252
- <v-data-table-virtual ref="table" :group-by="groupBy" :items="items" :headers="headers" class="elevation-1" item-value="title" :height="listHeight" @click:row="onClick">
253
- <template v-slot:item.actions="{ item }">
254
- <v-icon size="small" icon="$share" @click="onShare(item.raw)"></v-icon>
255
- <v-icon size="small" icon="$download" @click="onDownload(item.raw)"></v-icon>
256
- <v-icon size="small" icon="$grid" @click="onLightbox(item.raw)"></v-icon>
257
- </template>
258
- </v-data-table-virtual>
259
- <!-
260
- <v-list v-if="!grouped">
211
+ <v-card :height="listHeight" class="overflow-y-auto bg-media" >
212
+ <v-list v-if="showAlbums" class="bg-media">
261
213
  <v-list-item v-for="item in items" :key="item.DLNAID" :title="item.info" density="compact" @click.stop="showFolder(item)">
262
- </v-list-item>
263
- </v-list>
264
- <v-list v-else v-model:opened="open">
265
- <v-list-group v-for="item in items" :key="item.DLNAID" :value="item.Name" @click.stop="listExpand(item)">
266
- <template v-slot:activator="{ props }">
267
- <v-list-item v-bind="props" :title="item.info" :value="item.info" density="compact"></v-list-item>
214
+ <template v-slot:append>
215
+ <v-btn color="grey" variant="tonal" icon="$share" @click.stop.prevent="onShare(item)"></v-btn>
216
+ <v-btn color="grey" variant="tonal" icon="$download" @click.stop.prevent="onDownload(item)"></v-btn>
217
+ <v-btn color="grey" variant="tonal" icon="$grid" @click.stop.prevent="onLightbox(item)"></v-btn>
268
218
  </template>
269
- <v-list-item v-for="subItem in item.Folders" :key="subItem.DLNAID" :title="subItem.info" density="compact" @click.stop="showPictures(subItem)">
270
- <template v-slot:append>
271
- <v-btn color="grey" variant="tonal" icon="$share" @click.stop.prevent="onShare(subItem)"></v-btn>
272
- <v-btn color="grey" variant="tonal" icon="$download" @click.stop.prevent="onDownload(subItem)"></v-btn>
273
- <v-btn color="grey" variant="tonal" icon="$grid" @click.stop.prevent="onLightbox(subItem)"></v-btn>
274
- </template>
275
- </v-list-item>
276
- </v-list-group>
219
+ </v-list-item>
277
220
  </v-list>
278
- ->
221
+ <v-row v-else class="bg-media pa-1">
222
+ <v-col :cols="numGridCols" v-for="item in items" :key="item.DLNAID" density="compact" @click.stop="showFolder(item)">
223
+ {{item.info}}
224
+ </v-col>
225
+ </v-row>
279
226
  </v-card>
280
- -->
281
-
282
- <v-dialog v-model="showDownload">
283
- <photo-download :folder="downloadFolder!" v-click-outside="onCloseDownload" @close="onCloseDownload"></photo-download>
227
+ <v-dialog v-model="showDownload" v-click-outside="onCloseDownload" >
228
+ <photo-download :folder="downloadFolder!" @close="onCloseDownload"></photo-download>
284
229
  </v-dialog>
285
230
  </template>
286
231
 
@@ -201,7 +201,7 @@
201
201
  <v-card-title>{{selected.selected.Name}}</v-card-title>
202
202
  <v-card-actions>
203
203
  <v-btn v-if="backVisible" @click="listBack">
204
- <v-icon size="large" icon="$back" />
204
+ <v-icon icon="$back" />
205
205
  </v-btn>
206
206
  <v-rating clearable length="2" v-model="selected.rating" @update:modelValue="onRating" />
207
207
  <v-select v-model="selected.root"