Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions dms/static/src/js/components/preview/file_preview_pane.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
<t t-name="dms.FilePreviewPane">
<aside
class="o_dms_preview_pane"
role="complementary"
aria-label="File preview"
t-att-aria-busy="state.loading ? 'true' : undefined"
t-att-class="{'o_dms_preview_pane--empty': !state.file and !state.loading and !state.error}"
t-att-data-ext="state.file and state.file.name and state.file.name.includes('.') ? state.file.name.split('.').pop().toLowerCase() : false"
>
<header class="o_dms_preview_pane__header">
<div class="o_dms_preview_pane__title">
Expand Down Expand Up @@ -88,7 +92,11 @@
</div>
</header>

<div class="o_dms_preview_pane__body">
<div
class="o_dms_preview_pane__body"
aria-live="polite"
aria-atomic="false"
>
<t t-if="state.loading">
<div class="o_dms_preview_pane__empty">
<i class="fa fa-spinner fa-spin" />
Expand All @@ -105,9 +113,9 @@
</t>
<t t-elif="!state.file">
<div class="o_dms_preview_pane__empty">
<i class="fa fa-eye" />
<i class="fa fa-file-o" />
<div>
<div class="fw-medium">Click any row to preview.</div>
<div class="fw-medium">Click any file to preview.</div>
<small class="text-muted d-block mt-1">Press <kbd
>Esc</kbd> to dismiss this pane.</small>
</div>
Expand Down
10 changes: 10 additions & 0 deletions dms/static/src/js/views/file_kanban_record.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export class FileKanbanRecord extends KanbanRecord {
*/
onGlobalClick(ev) {
if (this.env.dmsKanbanPreview && this.props.record.resId) {
// Transfer the --previewing ring to this card. Clear any existing
// selection first so only one card ever carries the ring at a time.
this.el
?.closest(".o_kanban_renderer")
?.querySelectorAll(".o_kanban_dms_card--previewing")
.forEach((el) => el.classList.remove("o_kanban_dms_card--previewing"));
this.el
?.querySelector(".o_kanban_dms_card")
?.classList.add("o_kanban_dms_card--previewing");

this.env.dmsKanbanPreview.select(this.props.record.resId);
ev.preventDefault?.();
ev.stopPropagation?.();
Expand Down
250 changes: 142 additions & 108 deletions dms/static/src/scss/dms_directory.scss
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
// Copyright 2026 ledoent — Don Kendall
// License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

// Directories landing dashboard stat bar. Sits above the kanban grid as a
// flow-layout block (not absolutely positioned), so it pushes the grid down
// rather than overlapping it.
// Directory dashboard: stat bar + directory kanban card chrome.

// The renderer injects the stat bar as a sibling of the searchpanel + the
// kanban renderer inside `<main class="o_content o_component_with_search_panel">`,
// ---- Stat bar absolute positioning ----
//
// The renderer injects the stat bar as a sibling of the searchpanel + kanban
// renderer inside `<main class="o_content o_component_with_search_panel">`,
// which is `display: flex; flex-direction: row`. Float the bar absolutely
// above the row + give the parent top padding so the searchpanel + kanban
// shift down to make room. This keeps the row layout intact (kanban still
// fills 1220px next to the 220px searchpanel) while showing the bar full
// width at the top.
// above the row + give the parent top padding so the content shifts down.
main.o_content.o_component_with_search_panel:has(.o_dms_stat_bar) {
position: relative;
padding-top: 88px;
Expand Down Expand Up @@ -46,11 +43,75 @@ main.o_content.o_component_with_search_panel:has(.o_dms_stat_bar) {
padding: 10px 12px;
background-color: var(--bs-body-bg, #fff);
border: 1px solid var(--bs-border-color-translucent, rgba(0, 0, 0, 0.075));
border-radius: 6px;
// Left accent rail — mirrors the top-bar treatment on file cards so
// the stat bar and the grid below read as part of the same system.
border-left: 3px solid var(--tile-accent, var(--bs-primary, #714b67));
border-radius: 0 6px 6px 0; // Square left (rail flush), rounded right
min-width: 0;
position: relative;
}

// Per-tile tint — distinct colour families so the bar reads as 3 facets.
&__tile[data-tint="files"] {
--tile-accent: #1971c2;
--tile-bg: rgba(25, 113, 194, 0.1);
}
&__tile[data-tint="storage"] {
--tile-accent: #2f9e44;
--tile-bg: rgba(47, 158, 68, 0.1);
}
&__tile[data-tint="fresh"] {
--tile-accent: #e8590c;
--tile-bg: rgba(232, 89, 12, 0.1);
}

&__icon {
flex: 0 0 32px;
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
color: var(--tile-accent, var(--bs-primary, #714b67));
background-color: var(--tile-bg, var(--bs-tertiary-bg, #f4f5f7));
border-radius: 50%;
}

&__body {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1 1 auto;
}

&__value {
font-size: 1.25rem; // up from 1.125rem — values need to anchor the tile
font-weight: 700;
line-height: 1.2;
color: var(--bs-emphasis-color, #212529);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

&__label {
font-size: 0.7rem;
font-weight: 500;
color: var(--bs-secondary-color, #6c757d);
text-transform: uppercase;
letter-spacing: 0.05em;
}

&__delta {
font-size: 0.7rem;
color: var(--bs-secondary-color, #6c757d);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

&__spark {
width: 80px;
height: 26px;
Expand All @@ -68,7 +129,7 @@ main.o_content.o_component_with_search_panel:has(.o_dms_stat_bar) {

&__spark_area {
fill: currentColor;
opacity: 0.12;
opacity: 0.16; // slightly more vivid than before
}

&__spark_dot {
Expand All @@ -77,129 +138,102 @@ main.o_content.o_component_with_search_panel:has(.o_dms_stat_bar) {

&__spark_bar {
fill: currentColor;
opacity: 0.65;
opacity: 0.55;

&:last-of-type {
opacity: 1;
}
}
}

&__delta {
font-size: 0.7rem;
color: var(--bs-secondary-color, #6c757d);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

&__icon {
flex: 0 0 32px;
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
color: var(--tile-accent, var(--bs-primary, #714b67));
background-color: var(--tile-bg, var(--bs-tertiary-bg, #f4f5f7));
border-radius: 50%;
// ---- Bucket colour map — single source of truth for backend ----
//
// Portal (web.assets_frontend) mirrors these values in portal.scss.
// Both files must stay in sync; CSS custom properties can't bridge
// the compile-time boundary between the two asset bundles.
$dms-dir-buckets: (
1: #4263eb,
2: #1098ad,
3: #2f9e44,
4: #f08c00,
5: #d6336c,
6: #ae3ec9,
7: #5f3dc4,
8: #495057,
);

// ---- Directory kanban card — top accent bar via :has() ----
//
// CSS variables don't cascade upward, so we use `:has()` to reach the chip
// from the parent card and set a matching top border.
// Also establishes `--dir-chip-color` on the record for the hover shadow.
.mk_directory_kanban_view {
@each $n, $color in $dms-dir-buckets {
.o_kanban_record:has(.o_kanban_dms_dir_chip[data-bucket="#{$n}"]) {
border-top: 3px solid #{$color} !important;
border-radius: 0 0 6px 6px !important;
--dir-chip-color: #{$color};
}
}

// Per-tile tint — each semantic gets its own colour family so the bar
// reads as 3 distinct facets, not 3 copies of the same icon.
&__tile[data-tint="files"] {
--tile-accent: #1971c2;
--tile-bg: rgba(25, 113, 194, 0.1);
}
&__tile[data-tint="storage"] {
--tile-accent: #2f9e44;
--tile-bg: rgba(47, 158, 68, 0.1);
}
&__tile[data-tint="fresh"] {
--tile-accent: #e8590c;
--tile-bg: rgba(232, 89, 12, 0.1);
}
.o_kanban_record {
border-top-style: solid;
border-top-width: 3px;
transition:
transform 160ms ease,
box-shadow 160ms ease;

&__body {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1 1 auto;
&:hover {
transform: translateY(-2px);
box-shadow:
0 4px 16px rgba(0, 0, 0, 0.08),
0 2px 4px rgba(0, 0, 0, 0.05);
}
}

&__value {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.25;
color: var(--bs-emphasis-color, #212529);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// Creator avatar — match the file card's 20px circular treatment.
.oe_kanban_avatar {
width: 20px;
height: 20px;
border-radius: 50%;
object-fit: cover;
}

&__label {
font-size: 0.75rem;
color: var(--bs-secondary-color, #6c757d);
text-transform: uppercase;
letter-spacing: 0.04em;
// Tag pills — tighten slightly, keep color-coding from many2many widget.
.o_tag {
font-size: 0.68rem;
padding: 1px 6px;
font-weight: 500;
}
}

// Round the creator avatar in the directory kanban footer to match the file
// card's avatar treatment. Core `oe_kanban_avatar` doesn't include radius in
// this context, so we enforce it explicitly + match the 20px max-width set
// inline on the <img>.
.mk_directory_kanban_view .oe_kanban_avatar {
width: 20px;
height: 20px;
border-radius: 50%;
object-fit: cover;
}

// ---- Directory initials chip ----
//
// Hash-bucketed coloured circle with the directory's first 2 letters. Stable
// per-name colour across sorts. Buckets computed at render time via QWeb
// expression on the kanban card (see view_dms_directory_kanban in
// views/dms_directory.xml). Eight palette buckets keeps the visual vocabulary
// small enough to remember.
// Hash-bucketed coloured badge with the directory's first 2 letters.
// Stable per-name colour across sorts (computed at render time in QWeb).
.o_kanban_dms_dir_chip {
width: 56px;
height: 56px;
border-radius: 12px;
width: 52px;
height: 52px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 1.25rem;
font-weight: 700;
font-size: 1.2rem;
color: #fff;
text-transform: uppercase;
letter-spacing: 0.02em;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
// Slightly richer shadow so the chip reads as tactile against the card bg.
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18);
transition: transform 160ms ease;

&[data-bucket="1"] {
background-color: #4263eb;
}
&[data-bucket="2"] {
background-color: #1098ad;
.o_kanban_record:hover & {
transform: scale(1.05);
}
&[data-bucket="3"] {
background-color: #2f9e44;
}
&[data-bucket="4"] {
background-color: #f08c00;
}
&[data-bucket="5"] {
background-color: #d6336c;
}
&[data-bucket="6"] {
background-color: #ae3ec9;
}
&[data-bucket="7"] {
background-color: #5f3dc4;
}
&[data-bucket="8"] {
background-color: #495057;

@each $n, $color in $dms-dir-buckets {
&[data-bucket="#{$n}"] {
background-color: #{$color};
}
}
}
Loading
Loading