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
|
// Linked files
|
||||||
const files_list = qs(content, '.detail-files-list');
|
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));
|
const linked_files = all_pdfs.filter(p => (comp.file_ids ?? []).includes(p.id));
|
||||||
if (linked_files.length === 0) {
|
const rows = linked_files.map(pdf => {
|
||||||
const note = document.createElement('p');
|
const row = document.createElement('div');
|
||||||
note.className = 'detail-empty-note';
|
row.className = 'detail-file-row';
|
||||||
note.textContent = 'No files linked.';
|
|
||||||
files_list.replaceChildren(note);
|
|
||||||
} else {
|
|
||||||
files_list.replaceChildren(...linked_files.map(pdf => {
|
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.className = 'detail-file-link';
|
a.className = 'detail-file-link';
|
||||||
a.href = `/pdf/${pdf.filename}`;
|
a.href = `/pdf/${pdf.filename}`;
|
||||||
a.target = '_blank';
|
a.target = '_blank';
|
||||||
a.rel = 'noopener';
|
a.rel = 'noopener';
|
||||||
a.textContent = pdf.display_name;
|
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
|
// Inventory entries
|
||||||
const inv_list = qs(content, '.detail-inventory-list');
|
const inv_list = qs(content, '.detail-inventory-list');
|
||||||
@@ -1421,42 +1457,6 @@ function open_component_dialog(comp = null) {
|
|||||||
desc_input.value = comp?.description ?? '';
|
desc_input.value = comp?.description ?? '';
|
||||||
|
|
||||||
const active_fields = new Map(Object.entries(comp?.fields ?? {}));
|
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() {
|
function rebuild_field_rows() {
|
||||||
field_rows_el.replaceChildren(...[...active_fields.entries()].map(([fid, val]) => {
|
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()) {
|
for (const [fid, val] of active_fields.entries()) {
|
||||||
if (val.trim()) fields[fid] = val.trim();
|
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) {
|
if (comp) {
|
||||||
const result = await api.update_component(comp.id, body);
|
const result = await api.update_component(comp.id, body);
|
||||||
const idx = all_components.findIndex(c => c.id === comp.id);
|
const idx = all_components.findIndex(c => c.id === comp.id);
|
||||||
|
|||||||
@@ -1571,6 +1571,12 @@ nav {
|
|||||||
gap: 0.3rem;
|
gap: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-file-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-file-link {
|
.detail-file-link {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@@ -58,7 +58,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-block">
|
<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 class="detail-files-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -401,11 +404,6 @@
|
|||||||
<button type="button" class="btn btn-secondary btn-sm" id="c-new-field">New…</button>
|
<button type="button" class="btn btn-secondary btn-sm" id="c-new-field">New…</button>
|
||||||
</div>
|
</div>
|
||||||
</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="button" class="btn btn-secondary" id="c-cancel">Cancel</button>
|
||||||
<button type="submit" class="btn btn-primary" id="c-save">Save</button>
|
<button type="submit" class="btn btn-primary" id="c-save">Save</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user