1.2
This commit is contained in:
@@ -6,8 +6,8 @@ liegen ausschließlich im Arbeitsspeicher).
|
|||||||
|
|
||||||
## Starten
|
## Starten
|
||||||
|
|
||||||
chmod +x Lichttisch-1.0.0-x86_64.AppImage
|
chmod +x Lichttisch-1.2.0-x86_64.AppImage
|
||||||
./Lichttisch-1.0.0-x86_64.AppImage
|
./Lichttisch-1.2.0-x86_64.AppImage
|
||||||
|
|
||||||
Falls ein Fehler zu FUSE erscheint (z. B. auf Ubuntu 22.04+):
|
Falls ein Fehler zu FUSE erscheint (z. B. auf Ubuntu 22.04+):
|
||||||
|
|
||||||
@@ -30,6 +30,22 @@ Systems (GNOME Keyring / KWallet) verschlüsselt in
|
|||||||
- Ordner: durch deine Ordner blättern
|
- Ordner: durch deine Ordner blättern
|
||||||
- Alben: Alben aus der Nextcloud-Fotos-App (inklusive geteilter Alben)
|
- Alben: Alben aus der Nextcloud-Fotos-App (inklusive geteilter Alben)
|
||||||
|
|
||||||
|
## Sortierung und Filter
|
||||||
|
|
||||||
|
Oben in der Werkzeugleiste wählst du, wonach sortiert wird:
|
||||||
|
|
||||||
|
- Aufnahmedatum aus den Bild-Metadaten (EXIF), verfügbar ab Nextcloud 28.
|
||||||
|
Bei älteren Servern schaltet die Zeitleiste automatisch aufs Änderungsdatum
|
||||||
|
um und zeigt einen Hinweis.
|
||||||
|
- Änderungsdatum der Datei, so wie die Nextcloud es als „Geändert“ anzeigt.
|
||||||
|
Bei Uploads über den Browser ist das der Zeitpunkt des Hochladens.
|
||||||
|
|
||||||
|
In Ordnern und Alben gibt es beides jeweils aufsteigend und absteigend sowie
|
||||||
|
die Sortierung nach Name.
|
||||||
|
|
||||||
|
„DNG ausblenden“ in der Werkzeugleiste versteckt Rohdaten im DNG-Format in
|
||||||
|
allen Ansichten. Die Einstellung bleibt gespeichert.
|
||||||
|
|
||||||
## Tastatur
|
## Tastatur
|
||||||
|
|
||||||
Im Betrachter: ← / → blättern, Esc schließen, F Vollbild, O Originaldatei
|
Im Betrachter: ← / → blättern, Esc schließen, F Vollbild, O Originaldatei
|
||||||
@@ -40,4 +56,4 @@ F5 neu laden.
|
|||||||
|
|
||||||
npm install
|
npm install
|
||||||
npm start # zum Ausprobieren
|
npm start # zum Ausprobieren
|
||||||
npm run dist # erzeugt dist/Lichttisch-1.0.0-x86_64.AppImage
|
npm run dist # erzeugt dist/Lichttisch-1.2.0-x86_64.AppImage
|
||||||
|
|||||||
@@ -238,7 +238,10 @@ async function listFolder(relPath) {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchMedia(offset) {
|
// order: 'taken' = Aufnahmedatum aus den Metadaten (Nextcloud 28+),
|
||||||
|
// 'mtime' = Änderungs-/Upload-Datum der Datei
|
||||||
|
async function searchMedia(offset, order = 'taken') {
|
||||||
|
const orderProp = order === 'taken' ? '<nc:metadata-photos-original_date_time/>' : '<d:getlastmodified/>';
|
||||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<d:searchrequest ${NS} xmlns:ns="https://github.com/icewind1991/SearchDAV/ns">
|
<d:searchrequest ${NS} xmlns:ns="https://github.com/icewind1991/SearchDAV/ns">
|
||||||
<d:basicsearch>
|
<d:basicsearch>
|
||||||
@@ -250,7 +253,7 @@ async function searchMedia(offset) {
|
|||||||
<d:like><d:prop><d:getcontenttype/></d:prop><d:literal>video/%</d:literal></d:like>
|
<d:like><d:prop><d:getcontenttype/></d:prop><d:literal>video/%</d:literal></d:like>
|
||||||
</d:or>
|
</d:or>
|
||||||
</d:where>
|
</d:where>
|
||||||
<d:orderby><d:order><d:prop><d:getlastmodified/></d:prop><d:descending/></d:order></d:orderby>
|
<d:orderby><d:order><d:prop>${orderProp}</d:prop><d:descending/></d:order></d:orderby>
|
||||||
<d:limit><d:nresults>${PAGE_SIZE}</d:nresults><ns:firstresult>${Number(offset) || 0}</ns:firstresult></d:limit>
|
<d:limit><d:nresults>${PAGE_SIZE}</d:nresults><ns:firstresult>${Number(offset) || 0}</ns:firstresult></d:limit>
|
||||||
</d:basicsearch>
|
</d:basicsearch>
|
||||||
</d:searchrequest>`;
|
</d:searchrequest>`;
|
||||||
@@ -259,7 +262,11 @@ async function searchMedia(offset) {
|
|||||||
headers: { 'Content-Type': 'text/xml; charset=utf-8' },
|
headers: { 'Content-Type': 'text/xml; charset=utf-8' },
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
if (res.status !== 207) throw new Error(`Die Suche nach Fotos ist fehlgeschlagen (Status ${res.status}).`);
|
if (res.status !== 207) {
|
||||||
|
// Ältere Server kennen das Aufnahmedatum nicht und lehnen die Sortierung ab
|
||||||
|
if (order === 'taken') return { unsupported: true, items: [], done: false };
|
||||||
|
throw new Error(`Die Suche nach Fotos ist fehlgeschlagen (Status ${res.status}).`);
|
||||||
|
}
|
||||||
const items = parseMultistatus(await res.text()).map(toItem).filter((i) => !i.isDir);
|
const items = parseMultistatus(await res.text()).map(toItem).filter((i) => !i.isDir);
|
||||||
return { items, done: items.length < PAGE_SIZE };
|
return { items, done: items.length < PAGE_SIZE };
|
||||||
}
|
}
|
||||||
@@ -396,7 +403,7 @@ handle('logout', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handle('folder', (p) => listFolder(p));
|
handle('folder', (p) => listFolder(p));
|
||||||
handle('timeline', (offset) => searchMedia(offset));
|
handle('timeline', (offset, order) => searchMedia(offset, order));
|
||||||
handle('albums', () => listAlbums());
|
handle('albums', () => listAlbums());
|
||||||
handle('album', (href) => listAlbum(href));
|
handle('album', (href) => listAlbum(href));
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "lichttisch",
|
"name": "lichttisch",
|
||||||
"productName": "Lichttisch",
|
"productName": "Lichttisch",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"description": "Fotos aus der Nextcloud per WebDAV ansehen, ohne sie herunterzuladen",
|
"description": "Fotos aus der Nextcloud per WebDAV ansehen, ohne sie herunterzuladen",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ contextBridge.exposeInMainWorld('nc', {
|
|||||||
loginCancel: () => call('login-cancel'),
|
loginCancel: () => call('login-cancel'),
|
||||||
logout: () => call('logout'),
|
logout: () => call('logout'),
|
||||||
folder: (path) => call('folder', path),
|
folder: (path) => call('folder', path),
|
||||||
timeline: (offset) => call('timeline', offset),
|
timeline: (offset, order) => call('timeline', offset, order),
|
||||||
albums: () => call('albums'),
|
albums: () => call('albums'),
|
||||||
album: (href) => call('album', href),
|
album: (href) => call('album', href),
|
||||||
openInNextcloud: (fileId) => call('open-in-nextcloud', fileId),
|
openInNextcloud: (fileId) => call('open-in-nextcloud', fileId),
|
||||||
|
|||||||
+94
-33
@@ -47,11 +47,13 @@ function thumbUrl(item) {
|
|||||||
const state = {
|
const state = {
|
||||||
source: store.get('source', 'timeline'),
|
source: store.get('source', 'timeline'),
|
||||||
path: store.get('path', ''),
|
path: store.get('path', ''),
|
||||||
sort: store.get('sort', 'name'),
|
sort: store.get('sort', 'new'),
|
||||||
|
timelineOrder: store.get('timelineOrder', 'taken'),
|
||||||
|
hideDng: store.get('hideDng', true),
|
||||||
zoom: store.get('zoom', 180),
|
zoom: store.get('zoom', 180),
|
||||||
album: null, // { href, name } wenn ein Album offen ist
|
album: null, // { href, name } wenn ein Album offen ist
|
||||||
media: [], // aktuell sichtbare Bilder/Videos (für den Betrachter)
|
media: [], // aktuell sichtbare Bilder/Videos (für den Betrachter)
|
||||||
timeline: { offset: 0, done: false, loading: false, lastMonth: null, grid: null },
|
timeline: { offset: 0, done: false, loading: false },
|
||||||
loadToken: 0,
|
loadToken: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,6 +195,7 @@ async function load() {
|
|||||||
state.media = [];
|
state.media = [];
|
||||||
renderCrumbs();
|
renderCrumbs();
|
||||||
$('sort').hidden = !(state.source === 'folder' || (state.source === 'albums' && state.album));
|
$('sort').hidden = !(state.source === 'folder' || (state.source === 'albums' && state.album));
|
||||||
|
$('tl-order').hidden = state.source !== 'timeline';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (state.source === 'folder') {
|
if (state.source === 'folder') {
|
||||||
@@ -210,7 +213,7 @@ async function load() {
|
|||||||
renderAlbums(res);
|
renderAlbums(res);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startTimeline(token);
|
startTimeline(token, state.timelineOrder);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (token !== state.loadToken) return;
|
if (token !== state.loadToken) return;
|
||||||
@@ -220,13 +223,22 @@ async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DNG-Dateien (Rohdaten) optional ausblenden
|
||||||
|
function isDng(item) {
|
||||||
|
return /\.dng$/i.test(item.name) || /dng/i.test(item.mime);
|
||||||
|
}
|
||||||
|
const isHidden = (item) => state.hideDng && isDng(item);
|
||||||
|
const visibleMedia = (items) => items.filter((i) => (i.kind === 'image' || i.kind === 'video') && !isHidden(i));
|
||||||
|
|
||||||
function sortItems(items) {
|
function sortItems(items) {
|
||||||
const s = state.sort;
|
const s = state.sort;
|
||||||
const by = (i) => i.taken || i.mtime;
|
const by = photoDate;
|
||||||
const arr = [...items];
|
const arr = [...items];
|
||||||
if (s === 'name') arr.sort((a, b) => collator.compare(a.name, b.name));
|
if (s === 'name') arr.sort((a, b) => collator.compare(a.name, b.name));
|
||||||
if (s === 'new') arr.sort((a, b) => by(b) - by(a));
|
if (s === 'new') arr.sort((a, b) => by(b) - by(a));
|
||||||
if (s === 'old') arr.sort((a, b) => by(a) - by(b));
|
if (s === 'old') arr.sort((a, b) => by(a) - by(b));
|
||||||
|
if (s === 'mnew') arr.sort((a, b) => b.mtime - a.mtime);
|
||||||
|
if (s === 'mold') arr.sort((a, b) => a.mtime - b.mtime);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,11 +281,16 @@ function folderTile(item) {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hiddenNote(items) {
|
||||||
|
const n = items.filter(isHidden).length;
|
||||||
|
return n ? `${n} DNG-${n === 1 ? 'Datei ist' : 'Dateien sind'} ausgeblendet.` : null;
|
||||||
|
}
|
||||||
|
|
||||||
function renderMediaList(items, emptyText) {
|
function renderMediaList(items, emptyText) {
|
||||||
const media = sortItems(items.filter((i) => i.kind === 'image' || i.kind === 'video'));
|
const media = sortItems(visibleMedia(items));
|
||||||
state.media = media;
|
state.media = media;
|
||||||
const content = $('content');
|
const content = $('content');
|
||||||
if (!media.length) { content.replaceChildren(notice(emptyText)); return; }
|
if (!media.length) { content.replaceChildren(notice(emptyText, hiddenNote(items))); return; }
|
||||||
const grid = el('div', 'grid');
|
const grid = el('div', 'grid');
|
||||||
media.forEach((m, i) => grid.append(mediaTile(m, i)));
|
media.forEach((m, i) => grid.append(mediaTile(m, i)));
|
||||||
content.replaceChildren(grid);
|
content.replaceChildren(grid);
|
||||||
@@ -281,7 +298,7 @@ function renderMediaList(items, emptyText) {
|
|||||||
|
|
||||||
function renderFolder(items) {
|
function renderFolder(items) {
|
||||||
const dirs = items.filter((i) => i.isDir).sort((a, b) => collator.compare(a.name, b.name));
|
const dirs = items.filter((i) => i.isDir).sort((a, b) => collator.compare(a.name, b.name));
|
||||||
const media = sortItems(items.filter((i) => i.kind === 'image' || i.kind === 'video'));
|
const media = sortItems(visibleMedia(items));
|
||||||
state.media = media;
|
state.media = media;
|
||||||
const content = $('content');
|
const content = $('content');
|
||||||
content.replaceChildren();
|
content.replaceChildren();
|
||||||
@@ -297,7 +314,7 @@ function renderFolder(items) {
|
|||||||
content.append(g);
|
content.append(g);
|
||||||
}
|
}
|
||||||
if (!dirs.length && !media.length) {
|
if (!dirs.length && !media.length) {
|
||||||
content.append(notice('Hier liegen keine Fotos.', state.path ? 'Über den Pfad oben kommst du zurück.' : null));
|
content.append(notice('Hier liegen keine Fotos.', hiddenNote(items) || (state.path ? 'Über den Pfad oben kommst du zurück.' : null)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,10 +356,16 @@ function renderAlbums({ available, albums }) {
|
|||||||
|
|
||||||
let timelineObserver = null;
|
let timelineObserver = null;
|
||||||
|
|
||||||
function startTimeline(token) {
|
function startTimeline(token, order = 'taken', fellBack = false) {
|
||||||
state.timeline = { offset: 0, done: false, loading: false, lastMonth: null, grid: null, token };
|
$('tl-order').value = order;
|
||||||
|
state.timeline = { offset: 0, done: false, loading: false, token, order, months: new Map(), skipped: 0 };
|
||||||
|
state.media = [];
|
||||||
const content = $('content');
|
const content = $('content');
|
||||||
content.replaceChildren();
|
content.replaceChildren();
|
||||||
|
if (fellBack) {
|
||||||
|
content.append(el('p', 'hint-line',
|
||||||
|
'Dein Server liefert keine Aufnahmedaten, darum ist nach dem Änderungsdatum der Dateien sortiert. Aufnahmedaten gibt es ab Nextcloud 28.'));
|
||||||
|
}
|
||||||
const sentinel = el('div', 'sentinel');
|
const sentinel = el('div', 'sentinel');
|
||||||
const more = el('div', 'loading-more', 'Lade Fotos …');
|
const more = el('div', 'loading-more', 'Lade Fotos …');
|
||||||
content.append(sentinel, more);
|
content.append(sentinel, more);
|
||||||
@@ -355,6 +378,27 @@ function startTimeline(token) {
|
|||||||
loadTimelinePage();
|
loadTimelinePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Datum, nach dem ein Foto einsortiert wird: Aufnahmedatum, sonst Dateidatum
|
||||||
|
const photoDate = (item) => item.taken || item.mtime;
|
||||||
|
|
||||||
|
function monthSection(t, date, sentinel) {
|
||||||
|
const d = new Date(date);
|
||||||
|
const key = `${d.getFullYear()}-${d.getMonth()}`;
|
||||||
|
let m = t.months.get(key);
|
||||||
|
if (!m) {
|
||||||
|
const sec = el('section', 'month');
|
||||||
|
const h = el('h2', null, monthFmt.format(d));
|
||||||
|
const countEl = el('small');
|
||||||
|
h.append(countEl);
|
||||||
|
const grid = el('div', 'grid');
|
||||||
|
sec.append(h, grid);
|
||||||
|
sentinel.before(sec);
|
||||||
|
m = { grid, countEl, count: 0 };
|
||||||
|
t.months.set(key, m);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadTimelinePage() {
|
async function loadTimelinePage() {
|
||||||
const t = state.timeline;
|
const t = state.timeline;
|
||||||
if (t.loading || t.done || t.token !== state.loadToken) return;
|
if (t.loading || t.done || t.token !== state.loadToken) return;
|
||||||
@@ -363,33 +407,36 @@ async function loadTimelinePage() {
|
|||||||
const sentinel = content.querySelector('.sentinel');
|
const sentinel = content.querySelector('.sentinel');
|
||||||
const more = content.querySelector('.loading-more');
|
const more = content.querySelector('.loading-more');
|
||||||
try {
|
try {
|
||||||
const { items, done } = await window.nc.timeline(t.offset);
|
const res = await window.nc.timeline(t.offset, t.order);
|
||||||
if (t.token !== state.loadToken) return;
|
if (t.token !== state.loadToken) return;
|
||||||
t.offset += items.length;
|
|
||||||
t.done = done;
|
// Server kann nicht nach Aufnahmedatum sortieren, oder kein einziges Foto
|
||||||
for (const item of items) {
|
// hat eines: auf Änderungsdatum umschalten
|
||||||
const d = new Date(item.mtime);
|
const noMetadata = t.order === 'taken' && t.offset === 0 && res.items.length && !res.items.some((i) => i.taken);
|
||||||
const key = `${d.getFullYear()}-${d.getMonth()}`;
|
if (res.unsupported || noMetadata) {
|
||||||
if (key !== t.lastMonth) {
|
t.loading = false;
|
||||||
t.lastMonth = key;
|
startTimeline(t.token, 'mtime', true);
|
||||||
const sec = el('section', 'month');
|
return;
|
||||||
const h = el('h2', null, monthFmt.format(d));
|
|
||||||
t.countEl = el('small');
|
|
||||||
t.count = 0;
|
|
||||||
h.append(t.countEl);
|
|
||||||
t.grid = el('div', 'grid');
|
|
||||||
sec.append(h, t.grid);
|
|
||||||
content.insertBefore(sec, sentinel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.offset += res.items.length;
|
||||||
|
t.done = res.done;
|
||||||
|
for (const item of res.items) {
|
||||||
|
if (isHidden(item)) { t.skipped++; continue; }
|
||||||
|
const m = monthSection(t, t.order === 'taken' ? photoDate(item) : item.mtime, sentinel);
|
||||||
const index = state.media.length;
|
const index = state.media.length;
|
||||||
state.media.push(item);
|
state.media.push(item);
|
||||||
t.grid.append(mediaTile(item, index));
|
m.grid.append(mediaTile(item, index));
|
||||||
t.count++;
|
m.count++;
|
||||||
t.countEl.textContent = `${t.count} ${t.count === 1 ? 'Foto' : 'Fotos'}`;
|
m.countEl.textContent = `${m.count} ${m.count === 1 ? 'Foto' : 'Fotos'}`;
|
||||||
}
|
}
|
||||||
if (done) {
|
if (t.done) {
|
||||||
more.remove();
|
more.remove();
|
||||||
if (!state.media.length) content.replaceChildren(notice('Keine Fotos gefunden.', 'In deiner Nextcloud liegen noch keine Bilder oder Videos.'));
|
if (!state.media.length) {
|
||||||
|
content.replaceChildren(t.skipped
|
||||||
|
? notice('Keine Fotos sichtbar.', 'Alle gefundenen Dateien sind DNG-Dateien. Schalte „DNG ausblenden“ oben aus, um sie zu sehen.')
|
||||||
|
: notice('Keine Fotos gefunden.', 'In deiner Nextcloud liegen noch keine Bilder oder Videos.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (t.token !== state.loadToken) return;
|
if (t.token !== state.loadToken) return;
|
||||||
@@ -406,6 +453,13 @@ async function loadTimelinePage() {
|
|||||||
|
|
||||||
// ------------------------------------------------------------------ Werkzeugleiste
|
// ------------------------------------------------------------------ Werkzeugleiste
|
||||||
|
|
||||||
|
$('tl-order').value = state.timelineOrder;
|
||||||
|
$('tl-order').addEventListener('change', () => {
|
||||||
|
state.timelineOrder = $('tl-order').value;
|
||||||
|
store.set('timelineOrder', state.timelineOrder);
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
|
||||||
$('sort').value = state.sort;
|
$('sort').value = state.sort;
|
||||||
$('sort').addEventListener('change', () => {
|
$('sort').addEventListener('change', () => {
|
||||||
state.sort = $('sort').value;
|
state.sort = $('sort').value;
|
||||||
@@ -413,6 +467,13 @@ $('sort').addEventListener('change', () => {
|
|||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('hide-dng').checked = state.hideDng;
|
||||||
|
$('hide-dng').addEventListener('change', () => {
|
||||||
|
state.hideDng = $('hide-dng').checked;
|
||||||
|
store.set('hideDng', state.hideDng);
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
|
||||||
function applyZoom() {
|
function applyZoom() {
|
||||||
document.documentElement.style.setProperty('--thumb', `${state.zoom}px`);
|
document.documentElement.style.setProperty('--thumb', `${state.zoom}px`);
|
||||||
}
|
}
|
||||||
@@ -474,9 +535,9 @@ function showCurrent() {
|
|||||||
msg.hidden = true;
|
msg.hidden = true;
|
||||||
|
|
||||||
$('v-name').textContent = item.name;
|
$('v-name').textContent = item.name;
|
||||||
const when = item.taken || item.mtime;
|
|
||||||
const meta = [`${viewer.index + 1} von ${state.media.length}`];
|
const meta = [`${viewer.index + 1} von ${state.media.length}`];
|
||||||
if (when) meta.push(dateFmt.format(when));
|
if (item.taken) meta.push(`Aufgenommen ${dateFmt.format(item.taken)}`);
|
||||||
|
if (item.mtime) meta.push(`Geändert ${dateFmt.format(item.mtime)}`);
|
||||||
if (item.size) meta.push(fmtSize(item.size));
|
if (item.size) meta.push(fmtSize(item.size));
|
||||||
$('v-meta').replaceChildren(...meta.map((m) => el('span', null, m)));
|
$('v-meta').replaceChildren(...meta.map((m) => el('span', null, m)));
|
||||||
$('v-prev').disabled = viewer.index === 0;
|
$('v-prev').disabled = viewer.index === 0;
|
||||||
|
|||||||
+14
-4
@@ -79,11 +79,21 @@
|
|||||||
<header class="toolbar">
|
<header class="toolbar">
|
||||||
<nav id="crumbs" class="crumbs" aria-label="Pfad"></nav>
|
<nav id="crumbs" class="crumbs" aria-label="Pfad"></nav>
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
<select id="sort" title="Sortierung">
|
<select id="tl-order" title="Wonach die Zeitleiste sortiert wird">
|
||||||
<option value="name">Nach Name</option>
|
<option value="taken">Nach Aufnahmedatum</option>
|
||||||
<option value="new">Neueste zuerst</option>
|
<option value="mtime">Nach Änderungsdatum</option>
|
||||||
<option value="old">Älteste zuerst</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
<select id="sort" title="Sortierung">
|
||||||
|
<option value="new">Aufnahmedatum, neueste zuerst</option>
|
||||||
|
<option value="old">Aufnahmedatum, älteste zuerst</option>
|
||||||
|
<option value="mnew">Änderungsdatum, neueste zuerst</option>
|
||||||
|
<option value="mold">Änderungsdatum, älteste zuerst</option>
|
||||||
|
<option value="name">Nach Name</option>
|
||||||
|
</select>
|
||||||
|
<label class="check tool-check" title="Rohdaten im DNG-Format nicht anzeigen">
|
||||||
|
<input id="hide-dng" type="checkbox">
|
||||||
|
<span>DNG ausblenden</span>
|
||||||
|
</label>
|
||||||
<label class="zoom" title="Vorschaugröße">
|
<label class="zoom" title="Vorschaugröße">
|
||||||
<span class="zoom-dot small" aria-hidden="true"></span>
|
<span class="zoom-dot small" aria-hidden="true"></span>
|
||||||
<input id="zoom" type="range" min="110" max="360" step="10" value="180" aria-label="Vorschaugröße">
|
<input id="zoom" type="range" min="110" max="360" step="10" value="180" aria-label="Vorschaugröße">
|
||||||
|
|||||||
+4
-1
@@ -159,7 +159,10 @@ details button { justify-self: start; }
|
|||||||
.crumbs button:last-child:hover { background: none; }
|
.crumbs button:last-child:hover { background: none; }
|
||||||
.crumbs .sep { color: var(--line); }
|
.crumbs .sep { color: var(--line); }
|
||||||
|
|
||||||
.tools { display: flex; align-items: center; gap: 1rem; }
|
.tools { display: flex; align-items: center; gap: 1.2rem; }
|
||||||
|
.tool-check { font-size: 0.9rem; white-space: nowrap; cursor: pointer; }
|
||||||
|
.tool-check:hover { color: var(--text); }
|
||||||
|
.hint-line { margin: 0 0 1.4rem; color: var(--muted); font-size: 0.9rem; max-width: 60ch; }
|
||||||
.zoom { display: flex; align-items: center; gap: 0.5rem; }
|
.zoom { display: flex; align-items: center; gap: 0.5rem; }
|
||||||
.zoom input { width: 7rem; accent-color: var(--safelight); }
|
.zoom input { width: 7rem; accent-color: var(--safelight); }
|
||||||
.zoom-dot { display: block; border: 1.5px solid var(--muted); border-radius: 2px; }
|
.zoom-dot { display: block; border: 1.5px solid var(--muted); border-radius: 2px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user