Skip to content

Commit ca23ef8

Browse files
committed
feat: refine da-title block for configs and sheets
1 parent 98e7e74 commit ca23ef8

5 files changed

Lines changed: 372 additions & 28 deletions

File tree

blocks/edit/da-title/da-title.css

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ da-dialog {
3333
line-height: 16px;
3434
padding: 5px 14px;
3535
text-decoration: none;
36+
cursor: pointer;
3637
}
3738

3839
.con-button.blue {
@@ -186,12 +187,48 @@ da-dialog {
186187
background: #EFEFEF;
187188
}
188189

190+
.da-title-action {
191+
display: none;
192+
}
193+
189194
.da-title-actions.is-open .da-title-action {
190195
display: unset;
191196
}
192197

193-
.da-title-action {
194-
display: none;
198+
.da-title-actions.save-only .da-title-action {
199+
display: unset;
200+
min-height: 44px;
201+
}
202+
203+
.da-title-actions.save-only .da-title-action:not(.blue) {
204+
background: var(--s2-gray-200);
205+
border-color: var(--s2-gray-200);
206+
color: var(--s2-gray-700);
207+
cursor: default;
208+
}
209+
210+
.da-title-actions.save-only .da-title-action.is-sending {
211+
color: transparent;
212+
position: relative;
213+
overflow: hidden;
214+
}
215+
216+
.da-title-actions.save-only .da-title-action.is-sending::after {
217+
content: '';
218+
position: absolute;
219+
width: 22px;
220+
height: 22px;
221+
background: url('/blocks/edit/img/Smock_Send_18_N.svg') center/22px no-repeat;
222+
filter: brightness(0) invert(1);
223+
animation: animated-background 1s linear infinite;
224+
}
225+
226+
.da-title-save-disabled-msg {
227+
margin: 8px auto;
228+
max-width: 900px;
229+
color: var(--s2-gray-700);
230+
font-size: 12px;
231+
font-style: italic;
195232
}
196233

197234
.da-title-action-send {

blocks/edit/da-title/da-title.js

Lines changed: 202 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export default class DaTitle extends LitElement {
3737
collabUsers: { attribute: false },
3838
previewPrefix: { attribute: false },
3939
livePrefix: { attribute: false },
40+
hasChanges: { attribute: false },
41+
_savingDisabled: { state: true },
4042
_actionsVis: { state: true },
4143
_status: { state: true },
4244
_fixedActions: { state: true },
@@ -46,8 +48,14 @@ export default class DaTitle extends LitElement {
4648
connectedCallback() {
4749
super.connectedCallback();
4850
this.shadowRoot.adoptedStyleSheets = [sheet];
49-
this._actionsVis = [];
51+
this._actionsVis = this.getInitialActions();
52+
this.hasChanges = false;
53+
this._savingDisabled = false;
54+
this._isStaleIgnored = false;
55+
this._pollSession = 0;
5056
inlinesvg({ parent: this.shadowRoot, paths: ICONS });
57+
this.syncConfigPolling();
58+
5159
if (this.details.view === 'sheet') {
5260
this.collabStatus = window.navigator.onLine
5361
? 'connected'
@@ -58,6 +66,21 @@ export default class DaTitle extends LitElement {
5866
}
5967
}
6068

69+
disconnectedCallback() {
70+
this.clearConfigPolling();
71+
super.disconnectedCallback();
72+
}
73+
74+
updated(changedProperties) {
75+
super.updated(changedProperties);
76+
if (changedProperties.has('details')) {
77+
this._actionsVis = this.getInitialActions();
78+
this._savingDisabled = false;
79+
this._isStaleIgnored = false;
80+
this.syncConfigPolling();
81+
}
82+
}
83+
6184
firstUpdated() {
6285
const observer = new IntersectionObserver((entries) => {
6386
this._fixedActions = !entries[0].isIntersecting;
@@ -68,6 +91,7 @@ export default class DaTitle extends LitElement {
6891
}
6992

7093
handleError(json, action, icon) {
94+
// eslint-disable-next-line no-console
7195
console.log('handleError', json, action, icon);
7296
this._status = { ...json.error, action };
7397
icon.classList.remove('is-sending');
@@ -93,24 +117,47 @@ export default class DaTitle extends LitElement {
93117
async handleAction(action) {
94118
this.toggleActions();
95119
this._status = null;
96-
const sendBtn = this.shadowRoot.querySelector('.da-title-action-send-icon');
97-
sendBtn.classList.add('is-sending');
120+
121+
const sendBtn = this.shadowRoot.querySelector(
122+
this._isSaveOnlyView ? '.da-title-action' : '.da-title-action-send-icon',
123+
);
124+
125+
if (sendBtn) {
126+
sendBtn.classList.add('is-sending');
127+
}
98128

99129
const { hash } = window.location;
100130
const pathname = hash.replace('#', '');
101131

102-
// Only save to DA if it is a sheet or config
103-
if (this.details.view === 'sheet') {
132+
if (this.details.view === 'sheet' && action === 'save') {
104133
const dasSave = await saveToDa(pathname, this.sheet);
134+
if (sendBtn) sendBtn.classList.remove('is-sending');
105135
if (!dasSave.ok) return;
136+
this.hasChanges = false;
137+
return;
106138
}
107-
if (this.details.view === 'config') {
139+
140+
if (this._isConfigView) {
141+
if (this._savingDisabled) {
142+
if (sendBtn) {
143+
sendBtn.classList.remove('is-sending');
144+
}
145+
return;
146+
}
108147
const daConfigResp = await saveDaConfig(pathname, this.sheet);
148+
149+
if (sendBtn) {
150+
sendBtn.classList.remove('is-sending');
151+
}
152+
109153
if (!daConfigResp.ok) {
110154
// eslint-disable-next-line no-console
111155
console.log('Saving configuration failed because:', daConfigResp.status, await daConfigResp.text());
112-
return;
156+
} else {
157+
this.hasChanges = false;
158+
await this.cacheConfigData();
113159
}
160+
return;
114161
}
115162
if (action === 'preview' || action === 'publish') {
116163
const cdn = await getCdnConfig(pathname);
@@ -141,7 +188,7 @@ export default class DaTitle extends LitElement {
141188
window.open(`${toOpenInAem}?nocache=${Date.now()}`, toOpenInAem);
142189
}
143190
if (this.details.view === 'edit' && action === 'publish') saveDaVersion(pathname);
144-
sendBtn.classList.remove('is-sending');
191+
if (sendBtn) sendBtn.classList.remove('is-sending');
145192
}
146193

147194
async handleRoleRequest() {
@@ -189,15 +236,13 @@ export default class DaTitle extends LitElement {
189236
}
190237

191238
async toggleActions() {
192-
// toggle off if already on
193-
if (this._actionsVis.length > 0) {
194-
this._actionsVis = [];
239+
if (this._isSaveOnlyView || this.isDotDADoc) {
195240
return;
196241
}
197242

198-
// toggle on for config
199-
if (this.details.view === 'config') {
200-
this._actionsVis = ['save'];
243+
// toggle off if already on
244+
if (this._actionsVis.length > 0) {
245+
this._actionsVis = [];
201246
return;
202247
}
203248

@@ -217,12 +262,122 @@ export default class DaTitle extends LitElement {
217262
return !this.permissions.some((permission) => permission === 'write');
218263
}
219264

265+
get _isConfigView() {
266+
return this.details.view === 'config';
267+
}
268+
269+
get _isSaveOnlyView() {
270+
return this._isConfigView || this.details.view === 'sheet';
271+
}
272+
273+
get isDotDADoc() {
274+
return this.details.view === 'edit' && this.details.fullpath.includes('/.da/');
275+
}
276+
277+
getInitialActions() {
278+
if (this.isDotDADoc) {
279+
return [];
280+
}
281+
if (this._isSaveOnlyView) {
282+
return ['save'];
283+
}
284+
return [];
285+
}
286+
287+
clearConfigPolling() {
288+
if (this._pollInterval) {
289+
clearInterval(this._pollInterval);
290+
this._pollInterval = null;
291+
}
292+
}
293+
294+
syncConfigPolling() {
295+
this._pollSession = (this._pollSession || 0) + 1;
296+
this.clearConfigPolling();
297+
if (!this._isConfigView || this._isStaleIgnored) {
298+
this._cachedConfigData = null;
299+
return;
300+
}
301+
this.startConfigPolling(this._pollSession);
302+
}
303+
304+
async cacheConfigData() {
305+
const resp = await daFetch(this.details.sourceUrl);
306+
if (!resp.ok) return;
307+
this._cachedConfigData = JSON.stringify(await resp.json());
308+
}
309+
310+
async startConfigPolling(pollSession) {
311+
await this.cacheConfigData();
312+
if (pollSession !== this._pollSession || this._isStaleIgnored) return;
313+
this.clearConfigPolling();
314+
this._pollInterval = setInterval(() => this.checkConfigChanges(), 30000);
315+
}
316+
317+
async checkConfigChanges() {
318+
if (this._isStaleIgnored) return;
319+
const resp = await daFetch(this.details.sourceUrl);
320+
if (!resp.ok) return;
321+
const latestConfigData = JSON.stringify(await resp.json());
322+
if (!this._cachedConfigData) {
323+
this._cachedConfigData = latestConfigData;
324+
return;
325+
}
326+
if (latestConfigData !== this._cachedConfigData) {
327+
this.clearConfigPolling();
328+
this.showConfigStaleDialog();
329+
}
330+
}
331+
332+
async showConfigStaleDialog() {
333+
await import('../../shared/da-dialog/da-dialog.js');
334+
this._dialog = {
335+
title: 'Config Updated',
336+
content: html`
337+
<p>The config has been updated. Please refresh to get the latest changes, or ignore to keep your existing edits.</p>
338+
`,
339+
action: {
340+
style: 'accent',
341+
label: 'Refresh',
342+
click: async () => this.handleConfigRefresh(),
343+
},
344+
ignoreAction: {
345+
style: 'primary outline',
346+
label: 'Ignore',
347+
click: () => this.handleConfigIgnore(),
348+
},
349+
close: () => this.handleConfigIgnore(),
350+
};
351+
}
352+
353+
handleConfigIgnore() {
354+
this._dialog = undefined;
355+
this._savingDisabled = true;
356+
this._isStaleIgnored = true;
357+
this.clearConfigPolling();
358+
}
359+
360+
async handleConfigRefresh() {
361+
this._dialog = undefined;
362+
this._savingDisabled = false;
363+
this._isStaleIgnored = false;
364+
this.hasChanges = false;
365+
const daSheet = document.querySelector('.da-sheet');
366+
if (!daSheet) return;
367+
const { default: initSheet, getData } = await import('../../sheet/utils/index.js');
368+
const freshData = await getData(this.details.sourceUrl);
369+
this.sheet = await initSheet(daSheet, freshData);
370+
this.syncConfigPolling();
371+
}
372+
220373
renderActions() {
374+
const saveDisabled = this._isSaveOnlyView && (!this.hasChanges || this._savingDisabled);
221375
return html`${this._actionsVis.map((action) => html`
222376
<button
223377
@click=${() => this.handleAction(action)}
224-
class="con-button blue da-title-action"
225-
aria-label="Send">
378+
class="con-button da-title-action ${saveDisabled ? '' : 'blue'}"
379+
aria-label="${action}"
380+
?disabled=${saveDisabled}>
226381
${action.charAt(0).toUpperCase() + action.slice(1)}
227382
</button>
228383
`)}`;
@@ -248,6 +403,21 @@ export default class DaTitle extends LitElement {
248403
.action=${this._dialog.action}
249404
@close=${this._dialog.close}>
250405
${this._dialog.content}
406+
${this._dialog.ignoreAction ? html`
407+
<sl-button
408+
slot="footer-right"
409+
class=${this._dialog.ignoreAction.style}
410+
@click=${this._dialog.ignoreAction.click}>
411+
${this._dialog.ignoreAction.label}
412+
</sl-button>
413+
<sl-button
414+
slot="footer-right"
415+
class=${this._dialog.action.style}
416+
@click=${this._dialog.action.click}
417+
?disabled=${this._dialog.action.disabled}>
418+
${this._dialog.action.label}
419+
</sl-button>
420+
` : nothing}
251421
</da-dialog>
252422
`;
253423
}
@@ -290,17 +460,24 @@ export default class DaTitle extends LitElement {
290460
<div class="da-title-collab-actions-wrapper">
291461
${this.collabStatus ? this.renderCollab() : nothing}
292462
${this._status ? this.renderError() : nothing}
293-
<div class="da-title-actions ${this._fixedActions ? 'is-fixed' : ''} ${this._actionsVis.length > 0 ? 'is-open' : ''}">
294-
${this.renderActions()}
295-
<button
296-
@click=${this.toggleActions}
297-
class="con-button blue da-title-action-send"
298-
aria-label="Send">
299-
<span class="da-title-action-send-icon"></span>
300-
</button>
301-
</div>
463+
${this.isDotDADoc ? nothing : html`
464+
<div class="da-title-actions ${this._fixedActions ? 'is-fixed' : ''} ${!this._isSaveOnlyView && this._actionsVis.length > 0 ? 'is-open' : ''} ${this._isSaveOnlyView ? 'save-only' : ''}">
465+
${this.renderActions()}
466+
${this._isSaveOnlyView ? nothing : html`
467+
<button
468+
@click=${this.toggleActions}
469+
class="con-button blue da-title-action-send"
470+
aria-label="Send">
471+
<span class="da-title-action-send-icon"></span>
472+
</button>
473+
`}
474+
</div>
475+
`}
302476
</div>
303477
</div>
478+
${this._isConfigView && this._savingDisabled
479+
? html`<p class="da-title-save-disabled-msg">Saving is disabled until the config has been refreshed. If you have unsaved changes that you want to preserve, you can copy them and merge them after refreshing the config.</p>`
480+
: nothing}
304481
${this._dialog ? this.renderDialog() : nothing}
305482
`;
306483
}

blocks/shared/da-dialog/da-dialog.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ svg.icon {
6363
justify-content: space-between;
6464
align-items: center;
6565
gap: 12px;
66+
67+
slot[name='footer-right'] {
68+
display: flex;
69+
align-items: center;
70+
justify-content: flex-end;
71+
gap: 8px;
72+
}
6673

6774
.da-dialog-footer-message {
6875
font-style: italic;

0 commit comments

Comments
 (0)