Skip to content

Commit f68b415

Browse files
committed
[IMP] dms: side-pane polish (slide-in, row accent, ESC, registry docs)
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
1 parent 7742fc4 commit f68b415

5 files changed

Lines changed: 203 additions & 29 deletions

File tree

dms/static/src/js/components/preview/file_preview_pane.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@
5050
</t>
5151
<t t-elif="!state.file">
5252
<div class="o_dms_preview_pane__empty">
53-
<i class="fa fa-mouse-pointer me-2" />
54-
<span>Select a file to preview.</span>
53+
<i class="fa fa-eye" />
54+
<div>
55+
<div class="fw-medium">Click any row to preview.</div>
56+
<small class="text-muted d-block mt-1">Press <kbd
57+
>Esc</kbd> to dismiss this pane.</small>
58+
</div>
5559
</div>
5660
</t>
5761
<t t-elif="HandlerComponent">

dms/static/src/js/components/preview/preview_registry.esm.js

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,65 @@
11
// Copyright 2026 ledoent — Don Kendall
22
// License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
33

4-
// Extension point for DMS file preview handlers.
4+
// ===========================================================================
5+
// Extension point for DMS file preview handlers
6+
// ===========================================================================
57
//
6-
// Modules like `dms_onlyoffice` register additional handlers via:
8+
// The `dms` module ships in-browser preview handlers for the formats that
9+
// every modern browser already understands: images, PDF, audio, video. Office
10+
// formats (.doc/.docx/.odt/.xlsx/...) fall through to a download + Google
11+
// Viewer affordance because there is no in-browser viewer in base. Modules
12+
// like a future `dms_onlyoffice 19.0` plug in here to add native handlers.
713
//
14+
// ---------------------------------------------------------------------------
15+
// Contract
16+
// ---------------------------------------------------------------------------
17+
//
18+
// A handler is a plain object registered against this category:
19+
//
20+
// {
21+
// component: OwlComponent, // receives {file: {id, name, mimetype,
22+
// // write_date, human_size}} as a prop
23+
// match: (mimetype) => bool, // OPTIONAL — predicate; if omitted,
24+
// // the registry key is used as an
25+
// // exact mimetype match
26+
// score: 10, // OPTIONAL — higher wins on ties;
27+
// // built-ins use 0; download is -100
28+
// }
29+
//
30+
// Registering from an external module looks like this:
31+
//
32+
// // dms_onlyoffice/static/src/js/onlyoffice_preview_handler.esm.js
833
// import {registry} from "@web/core/registry";
9-
// registry.category("dms.preview_handlers").add("application/msword", {
34+
// import {OnlyOfficePreview} from "./onlyoffice_preview.esm";
35+
//
36+
// const OFFICE_MIMETYPES = new Set([
37+
// "application/msword",
38+
// "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
39+
// "application/vnd.oasis.opendocument.text",
40+
// // ... etc
41+
// ]);
42+
//
43+
// registry.category("dms.preview_handlers").add("onlyoffice", {
1044
// component: OnlyOfficePreview,
11-
// match: (mt) => mt === "application/msword",
12-
// score: 10,
45+
// match: (mt) => OFFICE_MIMETYPES.has(mt),
46+
// score: 10, // beats the built-in Google Viewer fallback (score 0)
1347
// });
1448
//
15-
// Lookup is by mimetype. `match` is an optional predicate for glob-style
16-
// matching ("image/*"). `score` breaks ties (higher wins); built-ins use 0,
17-
// so any extending module's handler with score>=0 will override the default.
49+
// The registered component must:
50+
// - declare `static template = "..."` and `static props = {file: Object}`
51+
// - render content that fits inside `.o_dms_preview_pane__body`
52+
// (no min-height required; the container handles overflow)
53+
//
54+
// ---------------------------------------------------------------------------
55+
// Lookup semantics
56+
// ---------------------------------------------------------------------------
57+
//
58+
// `getPreviewHandler(mimetype)` returns the highest-scored handler whose
59+
// `match` predicate accepts the mimetype (or whose registry key equals it,
60+
// if no `match` is provided). The built-in `__download__` handler matches
61+
// everything at score -100, so the function always returns something for any
62+
// non-empty mimetype.
1863

1964
import {registry} from "@web/core/registry";
2065

dms/static/src/js/views/file_list_renderer.esm.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import {FilePreviewPane} from "../components/preview/file_preview_pane.esm";
88
import {ListRenderer} from "@web/views/list/list_renderer";
9-
import {useState} from "@odoo/owl";
9+
import {onMounted, onWillUnmount, useState} from "@odoo/owl";
1010

1111
// Side-pane toggle persists in localStorage so it survives navigation.
1212
const DMS_LIST_PREVIEW_KEY = "dms_list_preview_pane";
@@ -26,6 +26,18 @@ export class FileListRenderer extends ListRenderer {
2626
open: _readStoredPreview(),
2727
recordId: null,
2828
});
29+
// ESC closes the pane — global listener registered on mount.
30+
this._onKeyDown = (ev) => {
31+
if (ev.key === "Escape" && this.previewState.open) {
32+
if (this.previewState.recordId) {
33+
this.closePreview();
34+
} else {
35+
this.togglePreview();
36+
}
37+
}
38+
};
39+
onMounted(() => window.addEventListener("keydown", this._onKeyDown));
40+
onWillUnmount(() => window.removeEventListener("keydown", this._onKeyDown));
2941
}
3042

3143
get previewOpen() {
@@ -36,6 +48,10 @@ export class FileListRenderer extends ListRenderer {
3648
return this.previewState.recordId;
3749
}
3850

51+
isPreviewSelected(record) {
52+
return this.previewState.recordId === record.resId;
53+
}
54+
3955
togglePreview() {
4056
this.previewState.open = !this.previewState.open;
4157
try {
@@ -65,6 +81,16 @@ export class FileListRenderer extends ListRenderer {
6581
}
6682
return super.onCellClicked(record, column, ev);
6783
}
84+
85+
// Adds an accent class to the row whose record is currently in the
86+
// preview pane. Called from the template via `t-att-class`.
87+
getRowClass(record) {
88+
const base = super.getRowClass(record);
89+
if (this.isPreviewSelected(record)) {
90+
return `${base || ""} o_dms_preview_selected_row`.trim();
91+
}
92+
return base;
93+
}
6894
}
6995

7096
FileListRenderer.components = {

dms/static/src/js/views/file_list_renderer.xml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,20 @@
2424
/>
2525
<button
2626
type="button"
27-
class="o_dms_list_split__toggle btn btn-sm"
27+
class="o_dms_list_split__toggle btn btn-sm d-flex align-items-center gap-1"
2828
t-att-class="{
29-
'btn-secondary': previewOpen,
30-
'btn-outline-secondary': !previewOpen,
29+
'o_dms_list_split__toggle--on': previewOpen,
30+
'o_dms_list_split__toggle--off': !previewOpen,
3131
}"
32-
t-att-title="previewOpen ? 'Hide preview pane' : 'Show preview pane'"
32+
t-att-title="previewOpen ? 'Hide preview pane (Esc)' : 'Show side-by-side preview'"
3333
t-att-aria-pressed="previewOpen"
3434
t-on-click="() => this.togglePreview()"
3535
>
36-
<i class="fa fa-columns" />
36+
<i t-attf-class="fa #{previewOpen ? 'fa-times' : 'fa-columns'}" />
37+
<span class="d-none d-md-inline">
38+
<t t-if="previewOpen">Hide preview</t>
39+
<t t-else="">Preview pane</t>
40+
</span>
3741
</button>
3842
</div>
3943
</t>

0 commit comments

Comments
 (0)