Move file linking to component detail view, consistent with images
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -336,23 +336,59 @@ function render_detail_panel() {
|
||||
|
||||
// Linked files
|
||||
const files_list = qs(content, '.detail-files-list');
|
||||
|
||||
async function save_file_ids(new_ids) {
|
||||
const result = await api.update_component(comp.id, { file_ids: new_ids });
|
||||
const idx = all_components.findIndex(c => c.id === comp.id);
|
||||
if (idx !== -1) all_components[idx] = result.component;
|
||||
render_detail_panel();
|
||||
}
|
||||
|
||||
function rebuild_detail_files() {
|
||||
const linked_files = all_pdfs.filter(p => (comp.file_ids ?? []).includes(p.id));
|
||||
if (linked_files.length === 0) {
|
||||
const note = document.createElement('p');
|
||||
note.className = 'detail-empty-note';
|
||||
note.textContent = 'No files linked.';
|
||||
files_list.replaceChildren(note);
|
||||
} else {
|
||||
files_list.replaceChildren(...linked_files.map(pdf => {
|
||||
const rows = linked_files.map(pdf => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'detail-file-row';
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.className = 'detail-file-link';
|
||||
a.href = `/pdf/${pdf.filename}`;
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener';
|
||||
a.textContent = pdf.display_name;
|
||||
return a;
|
||||
}));
|
||||
|
||||
const unlink_btn = document.createElement('button');
|
||||
unlink_btn.type = 'button';
|
||||
unlink_btn.className = 'btn-icon btn-danger';
|
||||
unlink_btn.textContent = '✕';
|
||||
unlink_btn.title = 'Unlink file';
|
||||
unlink_btn.addEventListener('click', () => {
|
||||
const new_ids = (comp.file_ids ?? []).filter(id => id !== pdf.id);
|
||||
save_file_ids(new_ids);
|
||||
});
|
||||
|
||||
row.append(a, unlink_btn);
|
||||
return row;
|
||||
});
|
||||
|
||||
if (rows.length === 0) {
|
||||
const note = document.createElement('p');
|
||||
note.className = 'detail-empty-note';
|
||||
note.textContent = 'No files linked.';
|
||||
files_list.replaceChildren(note);
|
||||
} else {
|
||||
files_list.replaceChildren(...rows);
|
||||
}
|
||||
}
|
||||
|
||||
rebuild_detail_files();
|
||||
|
||||
qs(content, '.detail-link-file-btn').addEventListener('click', () => {
|
||||
open_file_picker((pdf) => {
|
||||
const new_ids = [...new Set([...(comp.file_ids ?? []), pdf.id])];
|
||||
save_file_ids(new_ids);
|
||||
});
|
||||
});
|
||||
|
||||
// Inventory entries
|
||||
const inv_list = qs(content, '.detail-inventory-list');
|
||||
@@ -1421,42 +1457,6 @@ function open_component_dialog(comp = null) {
|
||||
desc_input.value = comp?.description ?? '';
|
||||
|
||||
const active_fields = new Map(Object.entries(comp?.fields ?? {}));
|
||||
const active_file_ids = new Set(comp?.file_ids ?? []);
|
||||
const file_rows_el = qs(dlg, '#c-file-rows');
|
||||
|
||||
function rebuild_file_rows() {
|
||||
const linked = all_pdfs.filter(p => active_file_ids.has(p.id));
|
||||
file_rows_el.replaceChildren(...linked.map(pdf => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'c-field-input-row';
|
||||
|
||||
const name_el = document.createElement('span');
|
||||
name_el.className = 'c-field-input-label';
|
||||
name_el.textContent = pdf.display_name;
|
||||
|
||||
const remove_btn = document.createElement('button');
|
||||
remove_btn.type = 'button';
|
||||
remove_btn.className = 'btn-icon btn-danger';
|
||||
remove_btn.textContent = '✕';
|
||||
remove_btn.title = 'Unlink file';
|
||||
remove_btn.addEventListener('click', () => {
|
||||
active_file_ids.delete(pdf.id);
|
||||
rebuild_file_rows();
|
||||
});
|
||||
|
||||
row.append(name_el, remove_btn);
|
||||
return row;
|
||||
}));
|
||||
}
|
||||
|
||||
rebuild_file_rows();
|
||||
|
||||
qs(dlg, '#c-link-file').addEventListener('click', () => {
|
||||
open_file_picker((pdf) => {
|
||||
active_file_ids.add(pdf.id);
|
||||
rebuild_file_rows();
|
||||
});
|
||||
});
|
||||
|
||||
function rebuild_field_rows() {
|
||||
field_rows_el.replaceChildren(...[...active_fields.entries()].map(([fid, val]) => {
|
||||
@@ -1552,7 +1552,7 @@ function open_component_dialog(comp = null) {
|
||||
for (const [fid, val] of active_fields.entries()) {
|
||||
if (val.trim()) fields[fid] = val.trim();
|
||||
}
|
||||
const body = { name, description: desc_input.value.trim(), fields, file_ids: [...active_file_ids] };
|
||||
const body = { name, description: desc_input.value.trim(), fields };
|
||||
if (comp) {
|
||||
const result = await api.update_component(comp.id, body);
|
||||
const idx = all_components.findIndex(c => c.id === comp.id);
|
||||
|
||||
@@ -1571,6 +1571,12 @@ nav {
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.detail-file-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-file-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
|
||||
@@ -58,7 +58,10 @@
|
||||
</div>
|
||||
|
||||
<div class="detail-block">
|
||||
<div class="detail-block-label">Files</div>
|
||||
<div class="detail-block-label">
|
||||
Files
|
||||
<button class="btn btn-secondary detail-link-file-btn">+ Link file</button>
|
||||
</div>
|
||||
<div class="detail-files-list"></div>
|
||||
</div>
|
||||
|
||||
@@ -401,12 +404,7 @@
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="c-new-field">New…</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section-label">Files</div>
|
||||
<div id="c-file-rows"></div>
|
||||
<div class="form-row">
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="c-link-file">Link file…</button>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<div class="dialog-actions">
|
||||
<button type="button" class="btn btn-secondary" id="c-cancel">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" id="c-save">Save</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user