Skip to content

Commit

Permalink
feat: File refresh icon for reloading the file (and partials). (#236)
Browse files Browse the repository at this point in the history
Instead of needing to reload the entire page to check for changes it now reloads just the individual file and clears the partials.

Also updates the states for amagaki and grow to have the `partialsOrGetPartials` style of accessing the partials to prevent the need for the UI to keep track of the initial load.

part of #133
  • Loading branch information
Zoramite committed Aug 13, 2021
1 parent 76799c8 commit b59521a
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 53 deletions.
7 changes: 7 additions & 0 deletions src/ts/editor/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ export const EVENT_NOTIFICATION_SHOW = 'live.notification.show';
*/
export const EVENT_ONBOARDING_UPDATE = 'live.onboarding.update';

/**
* Custom event name for when the editor wants to refresh the file.
*
* Can be used to clear specific caches when a refresh occurs.
*/
export const EVENT_REFRESH_FILE = 'live.refresh.file';

/**
* Custom event name for triggering a render.
*/
Expand Down
9 changes: 9 additions & 0 deletions src/ts/editor/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
EVENT_FILE_LOAD_COMPLETE,
EVENT_FILE_SAVE_COMPLETE,
EVENT_ONBOARDING_UPDATE,
EVENT_REFRESH_FILE,
EVENT_RENDER,
} from './events';
import {announceNotification, readNotification} from './ui/parts/notifications';
Expand Down Expand Up @@ -221,6 +222,14 @@ export class EditorState extends ListenersMixin(Base) {
document.addEventListener(EVENT_ONBOARDING_UPDATE, (evt: Event) => {
this.checkOnboarding();
});

// Listen for file refreshing.
document.addEventListener(EVENT_REFRESH_FILE, () => {
// Reload the current file.
if (this.file) {
this.getFile(this.file.file);
}
});
}

/**
Expand Down
26 changes: 24 additions & 2 deletions src/ts/editor/ui/parts/content/toolbar.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {BasePart, UiPartComponent, UiPartConfig} from '..';
import {TemplateResult, classMap, html, repeat} from '@blinkk/selective-edit';
import {UrlConfig, UrlLevel} from '../../../api';

import {DataStorage} from '../../../../utility/dataStorage';
import {EVENT_REFRESH_FILE} from '../../../events';
import {EditorState} from '../../../state';
import {findPreviewValue} from '@blinkk/selective-edit/dist/utility/preview';

Expand Down Expand Up @@ -60,6 +62,7 @@ export class ContentToolbarPart extends BasePart implements UiPartComponent {
)}
</div>
<div class="le__part__content__toolbar__icons">
${this.templateIconRefresh()}
${repeat(
this.config.state?.file?.urls || [],
url => {
Expand All @@ -75,12 +78,12 @@ export class ContentToolbarPart extends BasePart implements UiPartComponent {
>`;
}
)}
${this.templateExpanded()}
${this.templateIconExpanded()}
</div>
</div>`;
}

templateExpanded(): TemplateResult {
templateIconExpanded(): TemplateResult {
return html`<div
class="le__clickable le__tooltip--top"
data-tip=${this.isExpanded ? 'Content and preview' : 'Content only'}
Expand All @@ -98,4 +101,23 @@ export class ContentToolbarPart extends BasePart implements UiPartComponent {
>
</div>`;
}

templateIconRefresh(): TemplateResult {
if (!this.config.state.file) {
return html``;
}

return html`<div
class="le__clickable le__tooltip--top"
data-tip="Refresh file"
@click=${() => {
// Notify that the file refresh is happening.
document.dispatchEvent(new CustomEvent(EVENT_REFRESH_FILE));
this.render();
}}
>
<span class="material-icons">refresh</span>
</div>`;
}
}
47 changes: 42 additions & 5 deletions src/ts/projectType/amagaki/amagakiState.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import {ApiError, PartialData, catchError} from '../../editor/api';

import {BaseProjectTypeState} from '../state';
import {EVENT_REFRESH_FILE} from '../../editor/events';
import {EditorState} from '../../editor/state';

export class AmagakiState extends BaseProjectTypeState {
partials?: Record<string, PartialData>;
partials?: Record<string, PartialData> | null;

constructor(editorState: EditorState) {
super(editorState);

document.addEventListener(EVENT_REFRESH_FILE, () => {
// Reset when the file refreshes.
this.partials = undefined;
});
}

get api() {
return this.editorState.api;
Expand All @@ -11,10 +23,10 @@ export class AmagakiState extends BaseProjectTypeState {
getPartials(
callback?: (devices: Record<string, PartialData>) => void,
callbackError?: (error: ApiError) => void
): Record<string, PartialData> | undefined {
const promiseKey = 'getPartials';
): Record<string, PartialData> | undefined | null {
const promiseKey = AmagakiStatePromiseKeys.GetPartials;
if (this.promises[promiseKey]) {
return;
return this.partials;
}
this.promises[promiseKey] = this.api.projectTypes.amagaki
.getPartials()
Expand All @@ -28,7 +40,32 @@ export class AmagakiState extends BaseProjectTypeState {
this.triggerListener(promiseKey, this.partials);
this.render();
})
.catch(error => catchError(error, callbackError));
.catch(error => {
this.partials = null;
catchError(error, callbackError);
});
return this.partials;
}

/**
* Lazy load of partials data.
*
* Understands the null state when there is an error requesting.
*/
partialsOrGetPartials(): Record<string, PartialData> | undefined | null {
if (
this.partials === undefined &&
!this.inProgress(AmagakiStatePromiseKeys.GetPartials)
) {
this.getPartials();
}
return this.partials;
}
}

/**
* Promise keys used for tracking in operation promises for the state.
*/
export enum AmagakiStatePromiseKeys {
GetPartials = 'GetPartials',
}
18 changes: 3 additions & 15 deletions src/ts/projectType/amagaki/field/partials.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import {GenericPartialsField} from '../../generic/field/partials';
import {PartialData} from '../../../editor/api';

export class AmagakiPartialsField extends GenericPartialsField {
initPartials() {
this.partials = this.globalConfig.state.projectTypes.amagaki.partials;
this.globalConfig.state.projectTypes.amagaki.addListener(
'getPartials',
partials => {
this.partials = partials;
this.items = null;
this.render();
}
);

// Load the partials if not loaded.
if (this.partials === undefined) {
this.globalConfig.state.projectTypes.amagaki.getPartials();
}
get partials(): Record<string, PartialData> | undefined | null {
return this.globalConfig.state.projectTypes.amagaki.partialsOrGetPartials();
}
}
16 changes: 10 additions & 6 deletions src/ts/projectType/generic/field/partials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface GenericPartialsFieldConfig extends FieldConfig {
}

export interface GenericPartialsFieldComponent {
partials?: Record<string, PartialData>;
partials?: Record<string, PartialData> | undefined | null;
}

export class GenericPartialsField
Expand All @@ -64,7 +64,6 @@ export class GenericPartialsField
{
config: GenericPartialsFieldConfig;
globalConfig: LiveEditorGlobalConfig;
partials?: Record<string, PartialData>;
selective?: SelectiveEditor;

constructor(
Expand All @@ -77,22 +76,27 @@ export class GenericPartialsField
this.config = config;
this.globalConfig = globalConfig;
this.ListItemCls = GenericPartialListFieldItem;
this.initPartials();
}

/**
* Does the list allow for showing simple fields?
* Partial list does not allow showing simple fields inline.
*/
get allowSimple(): boolean {
return false;
}

protected ensureItems(editor: SelectiveEditor): Array<ListItemComponent> {
// If there are no items there is a chance this is from the partials not
// being loaded yet. If there are partials, reset the value.
if (this.items && this.items.length === 0 && this.partials) {
this.items = null;
}

if (this.items === null) {
this.items = [];

// While waiting for the partials to load, there are no items.
if (this.partials === undefined) {
if (!this.partials) {
return this.items;
}

Expand Down Expand Up @@ -310,7 +314,7 @@ export class GenericPartialsField
modal.show();
}

initPartials() {
get partials(): Record<string, PartialData> | undefined | null {
throw new Error('Not implemented.');
}

Expand Down
18 changes: 3 additions & 15 deletions src/ts/projectType/grow/field/partials.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import {GenericPartialsField} from '../../generic/field/partials';
import {PartialData} from '../../../editor/api';

export class GrowPartialsField extends GenericPartialsField {
initPartials() {
this.partials = this.globalConfig.state.projectTypes.grow.partials;
this.globalConfig.state.projectTypes.grow.addListener(
'getPartials',
partials => {
this.partials = partials;
this.items = null;
this.render();
}
);

// Load the partials if not loaded.
if (this.partials === undefined) {
this.globalConfig.state.projectTypes.grow.getPartials();
}
get partials(): Record<string, PartialData> | undefined | null {
return this.globalConfig.state.projectTypes.grow.partialsOrGetPartials();
}
}
77 changes: 67 additions & 10 deletions src/ts/projectType/grow/growState.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import {ApiError, PartialData, catchError} from '../../editor/api';

import {BaseProjectTypeState} from '../state';
import {EVENT_REFRESH_FILE} from '../../editor/events';
import {EditorState} from '../../editor/state';

export class GrowState extends BaseProjectTypeState {
partials?: Record<string, PartialData>;
strings?: Record<string, any>;
partials?: Record<string, PartialData> | null;
strings?: Record<string, any> | null;

constructor(editorState: EditorState) {
super(editorState);

document.addEventListener(EVENT_REFRESH_FILE, () => {
// Reset when the file refreshes.
this.partials = undefined;
this.strings = undefined;
});
}

get api() {
return this.editorState.api;
Expand All @@ -12,10 +25,10 @@ export class GrowState extends BaseProjectTypeState {
getPartials(
callback?: (partials: Record<string, PartialData>) => void,
callbackError?: (error: ApiError) => void
): Record<string, PartialData> | undefined {
const promiseKey = 'getPartials';
): Record<string, PartialData> | undefined | null {
const promiseKey = GrowStatePromiseKeys.GetPartials;
if (this.promises[promiseKey]) {
return;
return this.partials;
}
this.promises[promiseKey] = this.api.projectTypes.grow
.getPartials()
Expand All @@ -29,17 +42,20 @@ export class GrowState extends BaseProjectTypeState {
this.triggerListener(promiseKey, this.partials);
this.render();
})
.catch(error => catchError(error, callbackError));
.catch(error => {
this.partials = null;
catchError(error, callbackError);
});
return this.partials;
}

getStrings(
callback?: (strings: Record<string, any>) => void,
callbackError?: (error: ApiError) => void
): Record<string, any> | undefined {
const promiseKey = 'getStrings';
): Record<string, any> | undefined | null {
const promiseKey = GrowStatePromiseKeys.GetStrings;
if (this.promises[promiseKey]) {
return;
return this.strings;
}
this.promises[promiseKey] = this.api.projectTypes.grow
.getStrings()
Expand All @@ -53,7 +69,48 @@ export class GrowState extends BaseProjectTypeState {
this.triggerListener(promiseKey, this.strings);
this.render();
})
.catch(error => catchError(error, callbackError));
.catch(error => {
this.strings = null;
catchError(error, callbackError);
});
return this.strings;
}

/**
* Lazy load of partials data.
*
* Understands the null state when there is an error requesting.
*/
partialsOrGetPartials(): Record<string, PartialData> | undefined | null {
if (
this.partials === undefined &&
!this.inProgress(GrowStatePromiseKeys.GetPartials)
) {
this.getPartials();
}
return this.partials;
}

/**
* Lazy load of strings data.
*
* Understands the null state when there is an error requesting.
*/
stringsOrGetStrings(): Record<string, any> | undefined | null {
if (
this.strings === undefined &&
!this.inProgress(GrowStatePromiseKeys.GetStrings)
) {
this.getStrings();
}
return this.strings;
}
}

/**
* Promise keys used for tracking in operation promises for the state.
*/
export enum GrowStatePromiseKeys {
GetPartials = 'GetPartials',
GetStrings = 'GetStrings',
}
9 changes: 9 additions & 0 deletions src/ts/projectType/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export class BaseProjectTypeState
this.editorState = editorState;
}

/**
* Determines if there is an existing promise for a given key.
*
* @param key Key identifying the promise or loading status.
*/
inProgress(key: string): boolean {
return key in this.promises;
}

/**
* Signal for the editor to re-render.
*/
Expand Down

0 comments on commit b59521a

Please sign in to comment.