@@ -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 }
0 commit comments