source: Klonkt/src/views/partials/playlist-editor.ejs@ 23da947

main
Last change on this file since 23da947 was 6ee289a, checked in by roboburr <roboburr@…>, 5 weeks ago

De audio- en afspeellijst-editors (shaer-bqr, stap 3f)

admin-audio (435 regels, 18 vertaalsleutels) en playlist-editor (406 regels, het
csrf-token). Daarmee zijn 23 van de 24 bestanden om; alleen post-edit rest.

De achttien vertalingen van admin-audio stonden verspreid door de code, midden in
stringconcatenaties. Ze gaan nu als een tabel via pageData en worden opgezocht in
plaats van geinterpoleerd. Steekproefsgewijs nagelopen op de plekken waar de tekst
de HELE string was: daar bleef anders een leeg begin of eind over.

admin-playlists neemt de playlist-editor op, dus die route vraagt nu om
'admin-playlists playlist-editor' -- een module hoort bij het onderdeel, niet bij
de pagina die het toevallig opneemt.

Templates compileren, 22 modules schoon, suite 565/565.

  • Property mode set to 100644
File size: 9.1 KB
Line 
1<%
2// Playlist editor modal — included once per page (in admin-playlists, and in
3// post-edit when present). Exposes `window.openPlaylistEditor({ mode, id?, onSaved? })`.
4//
5// mode: 'create' or 'edit'
6// id : (edit only) playlist id to load
7// onSaved: callback receiving { id, playlist } — used by post-editor
8// to insert [[playlist:id]] at the cursor.
9//
10// The modal itself is appended on first call; subsequent calls reuse the
11// same DOM. CSS is included once. JS lives below.
12
13const _csrf = (typeof csrfToken !== 'undefined' && csrfToken) || '';
14%>
15
16<style>
17/* ─── Editor modal ─────────────────────────────────────────────── */
18.pl-modal-backdrop {
19 position: fixed; inset: 0; z-index: 9000;
20 background: color-mix(in srgb, #000 50%, transparent);
21 display: flex; align-items: center; justify-content: center;
22 padding: 1rem;
23 -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
24}
25.pl-modal {
26 background: var(--paper);
27 border: 1px solid var(--rule); border-radius: 16px;
28 width: 100%; max-width: 960px;
29 max-height: 92vh;
30 display: flex; flex-direction: column;
31 box-shadow: 0 24px 60px rgba(0, 0, 0, .35);
32 overflow: hidden;
33}
34.pl-modal-header {
35 padding: 1rem 1.25rem;
36 border-bottom: 1px solid var(--rule);
37 display: flex; align-items: center; justify-content: space-between;
38}
39.pl-modal-header h3 { margin: 0; font-size: 1.15rem; }
40.pl-modal-close {
41 background: transparent; border: 0;
42 font-size: 1.5rem; line-height: 1; cursor: pointer;
43 width: 36px; height: 36px; border-radius: 8px;
44 display: inline-flex; align-items: center; justify-content: center;
45 color: var(--ink-soft);
46}
47.pl-modal-close:hover { background: var(--paper-2); color: var(--ink); }
48.pl-modal-body {
49 flex: 1 1 auto; min-height: 0;
50 overflow: hidden;
51 padding: 1.25rem;
52 /* Become a flex container so .pl-editor-cols (grid) can flex into the
53 full available height. Without this, .pl-editor-cols had no bounded
54 parent height and inner overflow:auto never fired. */
55 display: flex; flex-direction: column;
56}
57.pl-modal-footer {
58 padding: .75rem 1.25rem;
59 border-top: 1px solid var(--rule);
60 display: flex; justify-content: flex-end; gap: .5rem;
61}
62
63.pl-editor-cols {
64 flex: 1 1 auto; min-height: 0;
65 display: grid;
66 grid-template-columns: 1fr 1fr;
67 gap: 1.5rem;
68 overflow: hidden;
69}
70@media (max-width: 720px) {
71 /* On mobile, abandon the inner-scroll-container model entirely. Two
72 scrolling boxes side-by-side don't fit, and stacked scroll boxes inside
73 a fixed modal create a scroll-trap (touch scroll easily lands on the
74 wrong layer). Instead: let the modal-body scroll the whole content,
75 and let lists flow at their natural height. */
76 .pl-editor-cols {
77 grid-template-columns: 1fr;
78 gap: 1rem;
79 height: auto;
80 overflow: visible;
81 }
82 .pl-modal-body { overflow: auto; -webkit-overflow-scrolling: touch; }
83 .pl-editor-left, .pl-editor-right { overflow: visible; min-height: 0; }
84 .pl-selected-list, .pl-available-list { overflow: visible; flex: 0 0 auto; }
85}
86
87.pl-editor-left,
88.pl-editor-right {
89 display: flex; flex-direction: column;
90 min-height: 0;
91 overflow: hidden;
92}
93
94/* Meta grid (title/artist/year/kind/cover) */
95.pl-meta-grid {
96 display: grid; gap: .5rem .75rem;
97 grid-template-columns: 1fr 1fr;
98 margin-bottom: 1rem;
99}
100.pl-field { display: flex; flex-direction: column; gap: .25rem; }
101.pl-field-full { grid-column: 1 / -1; }
102.pl-field > span {
103 font-size: .8rem; color: var(--ink-soft); font-weight: 500;
104}
105.pl-field input,
106.pl-field select {
107 width: 100%;
108 padding: .5rem .75rem;
109 border: 1px solid var(--rule); border-radius: 8px;
110 background: var(--paper); color: var(--ink);
111 font-family: var(--font-ui); font-size: .95rem;
112}
113.pl-field input:focus,
114.pl-field select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
115
116.pl-cover-row {
117 display: flex; align-items: center; gap: .5rem;
118}
119.pl-cover-thumb {
120 width: 48px; height: 48px; border-radius: 8px;
121 background: var(--paper-2);
122 border: 1px solid var(--rule);
123 display: flex; align-items: center; justify-content: center;
124 cursor: pointer; padding: 0; overflow: hidden;
125}
126.pl-cover-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
127.pl-cover-empty { font-size: 1.25rem; color: var(--ink-muted); }
128
129/* File upload row + status — only renders in edit mode */
130.pl-cover-upload-row {
131 display: flex; align-items: center; gap: .5rem;
132 margin-top: .35rem; flex-wrap: wrap;
133}
134.pl-btn-small {
135 background: var(--paper-2); color: var(--ink);
136 border: 1px solid var(--rule); border-radius: 999px;
137 padding: .35rem .75rem; font-size: .8rem;
138 cursor: pointer; transition: border-color 120ms;
139 -webkit-tap-highlight-color: transparent;
140}
141.pl-btn-small:hover { border-color: var(--accent); }
142.pl-cover-status { font-size: .8rem; color: var(--ink-muted); }
143.pl-cover-status.is-error { color: #dc2626; }
144.pl-cover-status.is-ok { color: #2a9d5e; }
145.pl-cover-note { font-size: .75rem; color: var(--ink-muted); margin-top: .25rem; display: block; }
146
147/* Section titles */
148.pl-section-title {
149 display: flex; align-items: baseline; gap: .5rem;
150 font-size: .8rem; font-weight: 600;
151 text-transform: uppercase; letter-spacing: .05em;
152 color: var(--ink-soft);
153 padding: .5rem 0;
154 margin-bottom: .5rem;
155 border-bottom: 1px solid var(--rule);
156 flex-shrink: 0;
157}
158.pl-section-title small {
159 font-size: .8em; font-weight: 400; text-transform: none; letter-spacing: 0;
160 color: var(--ink-muted);
161}
162.pl-track-count {
163 background: var(--accent); color: #fff;
164 padding: .1em .55em; border-radius: 999px; font-size: .75rem;
165 font-weight: 600; margin-left: .25rem;
166}
167
168/* Selected + available lists */
169.pl-selected-list,
170.pl-available-list {
171 flex: 1; overflow-y: auto; min-height: 0;
172 padding-right: .25rem;
173}
174.pl-empty {
175 color: var(--ink-muted); font-style: italic;
176 padding: 1.5rem; text-align: center; font-size: .9rem;
177}
178
179/* Search input on right pane */
180.pl-search-input {
181 width: 100%;
182 padding: .5rem .75rem;
183 border: 1px solid var(--rule); border-radius: 8px;
184 background: var(--paper); color: var(--ink);
185 font-family: var(--font-ui); font-size: .9rem;
186 margin-bottom: .5rem;
187 flex-shrink: 0;
188}
189
190/* Selected row (sortable) */
191.pl-row {
192 display: flex; align-items: center; gap: .5rem;
193 padding: .5rem;
194 border: 1px solid transparent;
195 border-radius: 8px;
196 margin-bottom: .25rem;
197 background: var(--paper-2);
198 user-select: none;
199}
200.pl-row.is-dragging { opacity: 0.5; }
201.pl-row.drop-above { box-shadow: 0 -2px 0 var(--accent); }
202.pl-row.drop-below { box-shadow: 0 2px 0 var(--accent); }
203.pl-row-handle {
204 cursor: grab; padding: .25rem .5rem;
205 color: var(--ink-muted);
206 touch-action: none;
207}
208.pl-row-handle:active { cursor: grabbing; }
209.pl-row-num {
210 width: 22px; text-align: right;
211 font-family: var(--font-mono, monospace);
212 font-size: .8rem; color: var(--ink-muted);
213}
214.pl-row-cover {
215 width: 32px; height: 32px; border-radius: 4px;
216 flex-shrink: 0;
217 background: var(--paper); border: 1px solid var(--rule);
218 display: inline-flex; align-items: center; justify-content: center;
219 overflow: hidden;
220}
221.pl-row-cover img { width: 100%; height: 100%; object-fit: cover; }
222.pl-row-cover-empty { color: var(--ink-muted); font-size: .8rem; }
223.pl-row-info {
224 flex: 1; min-width: 0;
225 display: flex; flex-direction: column;
226 font-size: .85rem;
227}
228.pl-row-title {
229 font-weight: 500;
230 white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
231}
232.pl-row-artist {
233 font-size: .8em; color: var(--ink-soft);
234 white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
235}
236.pl-row-x {
237 background: transparent; border: 0;
238 width: 28px; height: 28px;
239 border-radius: 6px;
240 cursor: pointer;
241 color: var(--ink-muted);
242 font-size: 1.1rem;
243}
244.pl-row-x:hover { background: var(--paper); color: #dc2626; }
245
246/* Available row (clickable) */
247.pl-avail-row {
248 display: flex; align-items: center; gap: .5rem;
249 padding: .5rem; border-radius: 8px;
250 cursor: pointer; margin-bottom: .25rem;
251 transition: background 120ms ease;
252}
253.pl-avail-row:hover { background: var(--paper-2); }
254.pl-avail-row.is-added { opacity: .55; }
255.pl-avail-row.is-unplayable { opacity: .4; cursor: not-allowed; }
256.pl-avail-action {
257 width: 22px; height: 22px;
258 border-radius: 50%; display: flex; align-items: center; justify-content: center;
259 font-size: .9rem; font-weight: 600;
260 background: var(--paper-2); color: var(--ink-soft);
261 flex-shrink: 0;
262}
263.pl-avail-row.is-added .pl-avail-action {
264 background: var(--accent); color: #fff;
265}
266
267/* Cover-picker grid (sub-modal) */
268.pl-cover-picker-grid {
269 display: grid;
270 grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
271 gap: .5rem; max-height: 60vh; overflow-y: auto;
272}
273.pl-cover-picker-grid button {
274 border: 1px solid var(--rule); border-radius: 8px;
275 background: var(--paper-2); padding: 0; cursor: pointer;
276 aspect-ratio: 1; overflow: hidden;
277}
278.pl-cover-picker-grid button:hover { border-color: var(--accent); }
279.pl-cover-picker-grid img { width: 100%; height: 100%; object-fit: cover; display: block; }
280</style>
281
282<%# Het script staat in assets/js/mod/playlist-editor.js; de gegevens via partials/page-data.ejs (shaer-bqr). %>
Note: See TracBrowser for help on using the repository browser.