Add read-only Synology Photos people filtering
This commit is contained in:
@@ -105,10 +105,24 @@ async function officialConnectionPanel(info){
|
||||
try{render(await api(path));}catch{panel.textContent=zh?'官方账号连接由管理员管理':'Official account connection is managed by an administrator';}
|
||||
}
|
||||
|
||||
async function photosPeoplePicker(selected){
|
||||
const target=$('#source-people-panel'),path='/api/v1/integrations/synology-photos';
|
||||
const status=await api(path);
|
||||
if(!status.configured){target.innerHTML=`<p class="hint">${t('photosConfigure')}</p>`;return;}
|
||||
if(!status.connected){
|
||||
target.innerHTML=`<div class="field"><label for="photos-username">${t('username')}</label><input id="photos-username" autocomplete="username"></div><div class="field"><label for="photos-password">${t('password')}</label><input id="photos-password" type="password" autocomplete="current-password"></div><p class="hint">${t('photosSessionHint')}</p><button type="button" id="photos-connect">${t('photosConnect')}</button>`;
|
||||
$('#photos-connect').onclick=busy(async()=>{const password=$('#photos-password').value;$('#photos-password').value='';await api(path,{method:'POST',body:{username:$('#photos-username').value,password}});await photosPeoplePicker(selected);});return;
|
||||
}
|
||||
const people=await api(path+'/people');
|
||||
target.innerHTML=`<div class="field"><label for="people-search">${t('peopleSearch')}</label><input id="people-search" type="search"></div><label class="check-row"><input id="people-unnamed" type="checkbox">${t('peopleUnnamed')}</label><div id="people-list" style="max-height:260px;overflow:auto"></div><p id="people-selected" class="hint"></p><button type="button" id="photos-disconnect">${t('photosDisconnect')}</button>`;
|
||||
const render=()=>{const query=$('#people-search').value.toLowerCase();const rows=people.filter(p=>(p.named||selected.has(p.id)||$('#people-unnamed').checked)&&(p.name.toLowerCase().includes(query)||String(p.id).includes(query)));$('#people-list').innerHTML=rows.map(p=>`<label class="check-row"><input type="checkbox" data-person="${p.id}" ${selected.has(p.id)?'checked':''}>${esc(p.name)} · ${p.count}</label>`).join('');$('#people-selected').textContent=t('peopleSelected')+selected.size;$('#people-list').querySelectorAll('[data-person]').forEach(box=>box.onchange=()=>{const id=Number(box.dataset.person);box.checked?selected.add(id):selected.delete(id);$('#people-selected').textContent=t('peopleSelected')+selected.size;});};
|
||||
$('#people-search').oninput=render;$('#people-unnamed').onchange=render;$('#photos-disconnect').onclick=busy(async()=>{await api(path,{method:'DELETE'});await photosPeoplePicker(selected);});render();
|
||||
}
|
||||
|
||||
async function photoSourcesPanel(){
|
||||
const section=document.createElement('section');section.className='card photo-sources-panel';$('#main').append(section);
|
||||
const sources=await api('/api/v1/photo-sources');if(!section.isConnected)return;
|
||||
section.innerHTML=`<div class="section-heading"><div><h3>${t('photoSources')}</h3><p class="hint">${t('sourceHint')}</p></div>${buttons('add-photo-source','addSource')}</div><div class="source-list">${sources.length?sources.map(s=>`<article class="source-card"><h4>${esc(s.name)}</h4><p class="hint">${esc(s.kind)} · ${t(s.enabled?'enabled':'disabled')}</p><div class="actions"><button data-source-browse="${esc(s.id)}">${t('browse')}</button><button data-source-random="${esc(s.id)}" ${s.enabled?'':'disabled'}>${t('randomPreview')}</button><button data-source-job="${esc(s.id)}">${t('sourceJob')}</button><button data-source-edit="${esc(s.id)}">${t('edit')}</button><button class="danger" data-source-delete="${esc(s.id)}">${t('remove')}</button></div></article>`).join(''):`<p class="hint">${t('noSources')}</p>`}</div><div id="source-browser"></div>`;
|
||||
section.innerHTML=`<div class="section-heading"><div><h3>${t('photoSources')}</h3><p class="hint">${t('sourceHint')}</p></div>${buttons('add-photo-source','addSource')}</div><div class="source-list">${sources.length?sources.map(s=>`<article class="source-card"><h4>${esc(s.name)}</h4><p class="hint">${esc(s.kind)} · ${t(s.enabled?'enabled':'disabled')}${s.people_filter?' · '+t('peopleFilter'):''}</p><div class="actions"><button data-source-browse="${esc(s.id)}">${t('browse')}</button><button data-source-random="${esc(s.id)}" ${s.enabled?'':'disabled'}>${t('randomPreview')}</button><button data-source-job="${esc(s.id)}">${t('sourceJob')}</button><button data-source-edit="${esc(s.id)}">${t('edit')}</button><button class="danger" data-source-delete="${esc(s.id)}">${t('remove')}</button></div></article>`).join(''):`<p class="hint">${t('noSources')}</p>`}</div><div id="source-browser"></div>`;
|
||||
bind('add-photo-source',()=>photoSourceForm());
|
||||
section.querySelectorAll('[data-source-edit]').forEach(b=>b.onclick=()=>photoSourceForm(sources.find(s=>s.id===b.dataset.sourceEdit)));
|
||||
section.querySelectorAll('[data-source-delete]').forEach(b=>b.onclick=()=>confirmDialog(t('removeConfirm'),async()=>{await api('/api/v1/photo-sources/'+b.dataset.sourceDelete,{method:'DELETE'});await navigate('gallery');}));
|
||||
@@ -116,10 +130,10 @@ async function photoSourcesPanel(){
|
||||
section.querySelectorAll('[data-source-browse]').forEach(b=>b.onclick=busy(()=>browseSource(b.dataset.sourceBrowse)));
|
||||
section.querySelectorAll('[data-source-job]').forEach(b=>b.onclick=()=>sourceJobForm(b.dataset.sourceJob));
|
||||
}
|
||||
function photoSourceForm(source=null){
|
||||
showModal(`<h2>${t(source?'edit':'addSource')}</h2><form id="photo-source-form"><div class="field"><label for="source-id">ID</label><input id="source-id" pattern="[a-z0-9-]{1,60}" required value="${esc(source?.id||'')}" ${source?'readonly':''}></div><div class="field"><label for="source-name">${t('name')}</label><input id="source-name" required maxlength="120" value="${esc(source?.name||'')}"></div><div class="field"><label for="source-kind">${t('sourceKind')}</label><select id="source-kind"><option value="local">${t('nasDirectory')}</option><option value="webdav">WebDAV</option></select></div><div class="field"><label for="source-location">${t('sourceLocation')}</label><input id="source-location" required maxlength="2048" value="${esc(source?.location||'.')}"><p class="hint">${t('sourceLocationHint')}</p></div><div class="field"><label for="source-credential">${t('sourceCredential')}</label><input id="source-credential" pattern="FRAME_FLOW_SOURCE_[A-Z0-9_]+" value="${esc(source?.credential_env||'')}" placeholder="FRAME_FLOW_SOURCE_CLOUD"><p class="hint">${t('sourceCredentialHint')}</p></div><div class="field"><label class="check-row"><input id="source-organized" type="checkbox" ${source?.organized_only?'checked':''}>${t('organizedPhotos')}</label><label class="check-row"><input id="source-caption" type="checkbox" ${source?.folder_caption?'checked':''}>${t('folderCaption')}</label><label class="check-row"><input id="source-synology-previews" type="checkbox" ${source?.synology_previews?'checked':''}>${t('synologyPreviews')}</label><p class="hint">${t('folderCaptionHint')}</p></div><div class="field"><label class="check-row"><input id="source-recursive" type="checkbox" ${source?.recursive===false?'':'checked'}>${t('sourceRecursive')}</label><label class="check-row"><input id="source-enabled" type="checkbox" ${source?.enabled===false?'':'checked'}>${t('enabled')}</label></div><div class="modal-actions">${buttons('modal-cancel','cancel')}<button type="submit" class="primary">${t('save')}</button></div></form>`);
|
||||
$('#source-kind').value=source?.kind||'local';const update=()=>{const dav=$('#source-kind').value==='webdav';$('#source-credential').disabled=!dav;$('#source-recursive').disabled=dav;$('#source-synology-previews').disabled=dav;};update();$('#source-kind').onchange=update;
|
||||
$('#photo-source-form').onsubmit=busy(async e=>{e.preventDefault();const dav=$('#source-kind').value==='webdav';await api('/api/v1/photo-sources/'+$('#source-id').value,{method:'PUT',body:{name:$('#source-name').value,kind:$('#source-kind').value,location:$('#source-location').value,recursive:!dav&&$('#source-recursive').checked,synology_previews:!dav&&$('#source-synology-previews').checked,catalog_limit:$('#source-organized').checked?100000:(source?.catalog_limit||10000),organized_only:$('#source-organized').checked,folder_caption:$('#source-caption').checked,enabled:$('#source-enabled').checked,credential_env:dav?($('#source-credential').value||null):null}});$('#modal').close();await navigate('gallery');});
|
||||
async function photoSourceForm(source=null){
|
||||
showModal(`<h2>${t(source?'edit':'addSource')}</h2><form id="photo-source-form"><div class="field"><label for="source-id">ID</label><input id="source-id" pattern="[a-z0-9-]{1,60}" required value="${esc(source?.id||'')}" ${source?'readonly':''}></div><div class="field"><label for="source-name">${t('name')}</label><input id="source-name" required maxlength="120" value="${esc(source?.name||'')}"></div><div class="field"><label for="source-kind">${t('sourceKind')}</label><select id="source-kind"><option value="local">${t('nasDirectory')}</option><option value="webdav">WebDAV</option></select></div><div class="field"><label for="source-location">${t('sourceLocation')}</label><input id="source-location" required maxlength="2048" value="${esc(source?.location||'.')}"><p class="hint">${t('sourceLocationHint')}</p></div><div class="field"><label for="source-credential">${t('sourceCredential')}</label><input id="source-credential" pattern="FRAME_FLOW_SOURCE_[A-Z0-9_]+" value="${esc(source?.credential_env||'')}" placeholder="FRAME_FLOW_SOURCE_CLOUD"><p class="hint">${t('sourceCredentialHint')}</p></div><div class="field"><label class="check-row"><input id="source-organized" type="checkbox" ${source?.organized_only?'checked':''}>${t('organizedPhotos')}</label><label class="check-row"><input id="source-caption" type="checkbox" ${source?.folder_caption?'checked':''}>${t('folderCaption')}</label><label class="check-row"><input id="source-synology-previews" type="checkbox" ${source?.synology_previews?'checked':''}>${t('synologyPreviews')}</label><p class="hint">${t('folderCaptionHint')}</p></div><div class="field"><label class="check-row"><input id="source-people-filter" type="checkbox" ${source?.people_filter?'checked':''}>${t('peopleFilter')}</label><p class="hint">${t('peopleHint')}</p><button id="source-people-load" type="button">${t('choosePeople')}</button><div id="source-people-panel"><p class="hint">${t('peopleSelected')}${(source?.person_ids||[]).length}</p></div><label for="source-person-match">${t('personMatch')}</label><select id="source-person-match"><option value="any">${t('personAny')}</option><option value="all">${t('personAll')}</option></select></div><div class="field"><label class="check-row"><input id="source-recursive" type="checkbox" ${source?.recursive===false?'':'checked'}>${t('sourceRecursive')}</label><label class="check-row"><input id="source-enabled" type="checkbox" ${source?.enabled===false?'':'checked'}>${t('enabled')}</label></div><div class="modal-actions">${buttons('modal-cancel','cancel')}<button type="submit" class="primary">${t('save')}</button></div></form>`);
|
||||
const selectedPeople=new Set(source?.person_ids||[]);$('#source-person-match').value=source?.person_match||'any';$('#source-people-load').onclick=busy(async()=>{await photosPeoplePicker(selectedPeople);});$('#source-kind').value=source?.kind||'local';const update=()=>{const dav=$('#source-kind').value==='webdav';$('#source-credential').disabled=!dav;$('#source-recursive').disabled=dav;$('#source-synology-previews').disabled=dav;$('#source-people-filter').disabled=dav;$('#source-people-load').disabled=dav;};update();$('#source-kind').onchange=update;
|
||||
$('#photo-source-form').onsubmit=busy(async e=>{e.preventDefault();const dav=$('#source-kind').value==='webdav';await api('/api/v1/photo-sources/'+$('#source-id').value,{method:'PUT',body:{name:$('#source-name').value,kind:$('#source-kind').value,location:$('#source-location').value,recursive:!dav&&$('#source-recursive').checked,synology_previews:!dav&&$('#source-synology-previews').checked,catalog_limit:$('#source-organized').checked?100000:(source?.catalog_limit||10000),organized_only:$('#source-organized').checked,folder_caption:$('#source-caption').checked,people_filter:!dav&&$('#source-people-filter').checked,person_ids:[...selectedPeople],person_match:$('#source-person-match').value,enabled:$('#source-enabled').checked,credential_env:dav?($('#source-credential').value||null):null}});$('#modal').close();await navigate('gallery');});
|
||||
}
|
||||
async function browseSource(id,offset=0,refresh=false){
|
||||
const result=await api('/api/v1/photo-sources/'+id+'/images?offset='+offset+'&limit=50&refresh='+refresh);const box=$('#source-browser');if(!box)return;
|
||||
|
||||
Reference in New Issue
Block a user