Add spinners to a lot of actions (also fixes #93)

pull/147/head
Candid Dauth 2021-04-14 03:45:05 +02:00
rodzic 7cf01a9abe
commit f02a99bcc5
17 zmienionych plików z 106 dodań i 41 usunięć

Wyświetl plik

@ -27,6 +27,7 @@ export default class ClickMarker extends Vue {
results: SearchResult[] = [];
layers!: SearchResultsLayer[]; // Don't make layer objects reactive
isAdding = false;
mounted(): void {
this.layers = [];
@ -110,6 +111,7 @@ export default class ClickMarker extends Vue {
async addToMap(result: SearchResult, type: Type): Promise<void> {
this.$bvToast.hide("fm-click-marker-add-error");
this.isAdding = true;
try {
const obj: Partial<MarkerCreate<StringMap> & LineCreate<StringMap>> = {
@ -142,6 +144,8 @@ export default class ClickMarker extends Vue {
this.close(result);
} catch (err) {
showErrorToast(this, "fm-click-marker-add-error", "Error adding to map", err);
} finally {
this.isAdding = false;
}
}

Wyświetl plik

@ -8,6 +8,7 @@
</template>
<SearchResultInfo
:result="result"
:is-adding="isAdding"
@add-to-map="addToMap(result, $event)"
@use-as-from="useAsFrom(result)"
@use-as-via="useAsVia(result)"

Wyświetl plik

@ -31,6 +31,9 @@ export default class FileResults extends Vue {
/** When clicking or selecting a search result, zoom to it. */
@Prop({ type: Boolean, default: false }) autoZoom!: boolean;
isAddingView: Array<ViewImport> = [];
isAddingType: Array<TypeImport> = [];
get hasViews(): boolean {
return this.file.views.length > 0;
}
@ -49,11 +52,14 @@ export default class FileResults extends Vue {
async addView(view: ViewImport): Promise<void> {
this.$bvModal.hide("fm-file-result-import-error");
this.isAddingView.push(view);
try {
await this.client.addView(view);
} catch (err) {
showErrorToast(this, "fm-file-result-import-error", "Error importing view", err);
} finally {
this.isAddingView = this.isAddingView.filter((v) => v !== view);
}
};
@ -63,11 +69,14 @@ export default class FileResults extends Vue {
async addType(type: TypeImport): Promise<void> {
this.$bvModal.hide("fm-file-result-import-error");
this.isAddingType.push(type);
try {
this.client.addType(type);
await this.client.addType(type);
} catch (err) {
showErrorToast(this, "fm-file-result-import-error", "Error importing type", err);
} finally {
this.isAddingType = this.isAddingType.filter((t) => t !== type);
}
};

Wyświetl plik

@ -16,7 +16,8 @@
{{" "}}
<span class="result-type">(View)</span>
</span>
<a href="javascript:" v-if="client.padData && client.writable == 2 && !viewExists(view)" @click="addView(view)" v-b-tooltip.hover.right="'Add this view to the map'"><Icon icon="plus" alt="Add"></Icon></a>
<b-spinner small v-if="isAddingView.includes(view)"></b-spinner>
<a href="javascript:" v-else-if="client.padData && client.writable == 2 && !viewExists(view)" @click="addView(view)" v-b-tooltip.hover.right="'Add this view to the map'"><Icon icon="plus" alt="Add"></Icon></a>
</b-list-group-item>
</b-list-group>
</template>
@ -33,7 +34,8 @@
{{" "}}
<span class="result-type">(Type)</span>
</span>
<a href="javascript:" v-if="client.padData && client.writable == 2 && !typeExists(type)" @click="addType(type)" v-b-tooltip.hover.right="'Add this type to the map'"><Icon icon="plus" alt="Add"></Icon></a>
<b-spinner small v-if="isAddingType.includes(type)"></b-spinner>
<a href="javascript:" v-else-if="client.padData && client.writable == 2 && !typeExists(type)" @click="addType(type)" v-b-tooltip.hover.right="'Add this type to the map'"><Icon icon="plus" alt="Add"></Icon></a>
</b-list-group-item>
</b-list-group>
</template>

Wyświetl plik

@ -24,14 +24,22 @@ export default class History extends Vue {
@Prop({ type: String, required: true }) id!: string;
popover: { entry: HistoryEntryWithLabels; target: HTMLElement } | null = null;
isLoading = true;
reverting: HistoryEntryWithLabels | null = null;
async handleShow(): Promise<void> {
handleShow(): void {
this.isLoading = true;
}
async handleShown(): Promise<void> {
this.$bvToast.hide("fm-history-error");
try {
await this.client.listenToHistory();
} catch (err) {
showErrorToast(this, "fm-history-error", "Error loading history", err);
} finally {
this.isLoading = false;
}
}
@ -49,10 +57,14 @@ export default class History extends Vue {
if (!await this.$bvModal.msgBoxConfirm(entry.labels.confirm))
return;
this.reverting = entry;
try {
this.client.revertHistoryEntry({ id: entry.id });
await this.client.revertHistoryEntry({ id: entry.id });
} catch (err) {
showErrorToast(this, "fm-history-error", "Error loading history", err);
} finally {
this.reverting = null;
}
}

Wyświetl plik

@ -1,6 +1,21 @@
<b-modal :id="id" title="History" ok-only ok-title="Close" size="xl" dialog-class="fm-history" @show="handleShow" @hidden="handleHidden" scrollable>
<b-modal
:id="id"
title="History"
ok-only
ok-title="Close"
size="xl"
dialog-class="fm-history"
@show="handleShow"
@shown="handleShown"
@hidden="handleHidden"
:is-busy="!!reverting"
scrollable
>
<p><em>Here you can inspect and revert the last 50 changes to the map.</em></p>
<b-table-simple striped hover>
<div v-if="isLoading" class="d-flex justify-content-center">
<b-spinner></b-spinner>
</div>
<b-table-simple v-else striped hover>
<b-thead>
<b-tr>
<b-th style="min-width: 12rem">Date</b-th>
@ -19,7 +34,10 @@
<b-button v-if="entry.labels.diff" @click="handleInfoClick($event.target, entry)" @blur="handleInfoBlur()"><Icon icon="info-sign"></Icon></b-button>
</b-td>
<b-td class="td-buttons">
<b-button v-if="entry.labels.button" block :disabled="!!client.loading" @click="revert(entry)">{{entry.labels.button}}</b-button>
<b-button v-if="entry.labels.button" block :disabled="!!reverting" @click="revert(entry)">
<b-spinner small v-if="reverting === entry"></b-spinner>
{{entry.labels.button}}
</b-button>
</b-td>
</b-tr>
</b-tbody>

Wyświetl plik

@ -31,6 +31,7 @@ export default class LineInfo extends Vue {
@Prop({ type: Boolean, default: false }) showBackButton!: boolean;
isDeleting = false;
isExporting = false;
showElevationPlot = false;
isMoving = false;
@ -45,6 +46,7 @@ export default class LineInfo extends Vue {
return;
this.isDeleting = true;
try {
await this.client.deleteLine({ id: this.lineId });
} catch (err) {
@ -64,12 +66,15 @@ export default class LineInfo extends Vue {
return;
this.$bvToast.hide("fm-line-info-export-error");
this.isExporting = true;
try {
const exported = await this.client.exportLine({ id: this.line.id, format });
saveAs(new Blob([exported], { type: "application/gpx+xml" }), `${this.line.name}.gpx`);
} catch(err) {
showErrorToast(this, "fm-line-info-export-error", "Error exporting line", err);
} finally {
this.isExporting = false;
}
}

Wyświetl plik

@ -36,7 +36,12 @@
<b-button-toolbar v-if="!isMoving">
<b-button v-b-tooltip.hover="'Zoom to line'" @click="zoomToLine()" size="sm"><Icon icon="zoom-in" alt="Zoom to line"></Icon></b-button>
<b-dropdown text="Export" size="sm">
<b-dropdown size="sm" :disabled="isExporting">
<template #button-content>
<b-spinner small v-if="isExporting"></b-spinner>
Export
</template>
<b-dropdown-item
href="javascript:"
@click="exportRoute('gpx-trk')"

Wyświetl plik

@ -41,13 +41,14 @@ export default class ManageViews extends Vue {
};
async deleteView(view: View): Promise<void> {
Vue.set(this.isDeleting, view.id, true);
this.$bvToast.hide(`fm-save-view-error-${view.id}`);
try {
if (!await this.$bvModal.msgBoxConfirm(`Do you really want to delete the view “${view.name}”?`))
return;
Vue.set(this.isDeleting, view.id, true);
await this.client.deleteView({ id: view.id });
} catch (err) {
showErrorToast(this, `fm-save-view-error-${view.id}`, `Error deleting view “${view.name}`, err);

Wyświetl plik

@ -48,6 +48,7 @@ export default class MarkerInfo extends Vue {
return;
this.isDeleting = true;
try {
await this.client.deleteMarker({ id: this.markerId });
} catch (err) {

Wyświetl plik

@ -75,6 +75,7 @@ export default class MultipleInfo extends Vue {
return;
this.isDeleting = true;
try {
for (const object of this.objects) {
if (isMarker(object))

Wyświetl plik

@ -43,6 +43,7 @@ export default class PadSettings extends Vue {
@Prop({ type: Boolean }) readonly isCreate?: boolean;
isSaving = false;
isDeleting = false;
deleteConfirmation = "";
padData: PadDataCreate | PadDataUpdate = null as any;
@ -88,23 +89,6 @@ export default class PadSettings extends Vue {
this.padDataValidationProvider?.validate({ ...padData });
}
/*
$scope.copyPadId = fmUtils.generateRandomPadId();
$scope.copyPad = function() {
socket.copyPad({ toId: $scope.copyPadId }, function(err) {
if(err) {
$scope.dialogError = err;
return;
}
$scope.closeDialog();
var url = $scope.urlPrefix + $scope.copyPadId;
$scope.showMessage("success", "The pad has been copied to", [ { label: url, url: url } ]);
$scope.copyPadId = fmUtils.generateRandomPadId();
});
};
*/
async save(): Promise<void> {
this.isSaving = true;
this.$bvToast.hide("fm-pad-settings-error");
@ -112,7 +96,6 @@ export default class PadSettings extends Vue {
try {
if(this.isCreate)
await this.client.createPad(this.padData as PadDataCreate);
// this.client.updateBbox(leafletToFmBbox(map.map.getBounds(), map.map.getZoom()));
else
await this.client.editPad(this.padData);
@ -129,19 +112,20 @@ export default class PadSettings extends Vue {
}
async deletePad(): Promise<void> {
this.isSaving = true;
this.$bvToast.hide("fm-pad-settings-error");
try {
if (!await this.$bvModal.msgBoxConfirm(`Are you sure you want to delete the map “${this.padData.name}”? Deleted maps cannot be restored!`))
if (!await this.$bvModal.msgBoxConfirm(`Are you sure you want to delete the map “${this.padData.name}”? Deleted maps cannot be restored!`))
return;
this.isDeleting = true;
try {
await this.client.deletePad();
this.$bvModal.hide(this.id);
} catch (err) {
showErrorToast(this, "fm-pad-settings-error", "Error deleting map", err);
} finally {
this.isSaving = false;
this.isDeleting = false;
}
};
}

Wyświetl plik

@ -4,6 +4,7 @@
dialog-class="fm-pad-settings"
:no-cancel="noCancel"
:is-saving="isSaving"
:is-busy="isDeleting"
:is-create="isCreate"
:is-modified="isModified"
@submit="save"
@ -105,7 +106,10 @@
<b-input-group>
<b-form-input id="delete-input" v-model="deleteConfirmation" autocomplete="off"></b-form-input>
<b-input-group-append>
<b-button type="submit" variant="danger" :disabled="deleteConfirmation != 'DELETE'">Delete map</b-button>
<b-button type="submit" variant="danger" :disabled="isDeleting || isSaving || deleteConfirmation != 'DELETE'">
<b-spinner small v-if="isDeleting"></b-spinner>
Delete map
</b-button>
</b-input-group-append>
</b-input-group>
<template #description>

Wyświetl plik

@ -100,6 +100,8 @@ export default class RouteForm extends Vue {
routeError: string | null = null;
hoverDestinationIdx: number | null = null;
hoverInsertIdx: number | null = null;
isAdding = false;
isExporting = false;
// Do not make reactive
suggestionMarker: MarkerLayer | undefined;
@ -467,6 +469,7 @@ export default class RouteForm extends Vue {
async addToMap(type: Type): Promise<void> {
this.$bvToast.hide("fm-route-form-add-error");
this.isAdding = true;
try {
const line = await this.client.addLine({ typeId: type.id, routePoints: this.routeObj!.routePoints, mode: this.routeObj!.mode });
@ -474,17 +477,22 @@ export default class RouteForm extends Vue {
this.mapComponents.selectionHandler.setSelectedItems([{ type: "line", id: line.id }], true);
} catch (err) {
showErrorToast(this, "fm-route-form-add-error", "Error adding line", err);
} finally {
this.isAdding = false;
}
}
async exportRoute(format: ExportFormat): Promise<void> {
this.$bvToast.hide("fm-route-form-export-error");
this.isExporting = true;
try {
const exported = await this.client.exportRoute({ format });
saveAs(new Blob([exported], { type: "application/gpx+xml" }), "FacilMap route.gpx");
} catch(err) {
showErrorToast(this, "fm-route-form-export-error", "Error exporting route", err);
} finally {
this.isExporting = false;
}
}

Wyświetl plik

@ -90,10 +90,20 @@
<b-button-toolbar v-if="showToolbar && !client.readonly">
<b-button v-b-tooltip.hover="'Zoom to route'" @click="zoomToRoute()" size="sm"><Icon icon="zoom-in" alt="Zoom to route"></Icon></b-button>
<b-dropdown v-if="lineTypes.length > 0" text="Add to map" size="sm">
<b-dropdown v-if="lineTypes.length > 0" size="sm" :disabled="isAdding">
<template #button-content>
<b-spinner small v-if="isAdding"></b-spinner>
Add to map
</template>
<b-dropdown-item v-for="type in lineTypes" href="javascript:" @click="addToMap(type)">{{type.name}}</b-dropdown-item>
</b-dropdown>
<b-dropdown text="Export" size="sm">
<b-dropdown size="sm" :disabled="isExporting">
<template #button-content>
<b-spinner small v-if="isExporting"></b-spinner>
Export
</template>
<b-dropdown-item
href="javascript:"
@click="exportRoute('gpx-trk')"

Wyświetl plik

@ -17,6 +17,7 @@ export default class FormModal extends Vue {
@Prop({ type: String }) readonly dialogClass?: string;
@Prop({ type: Boolean }) readonly noCancel?: boolean;
@Prop({ type: Boolean }) readonly isSaving?: boolean;
@Prop({ type: Boolean }) readonly isBusy?: boolean;
@Prop({ type: Boolean }) readonly isCreate?: boolean;
@Prop({ type: Boolean, default: true }) readonly isModified?: boolean;
@Prop({ type: String }) readonly size?: string;

Wyświetl plik

@ -4,9 +4,8 @@
:title="title"
:size="size || 'lg'"
:dialog-class="dialogClass"
:no-close-on-esc="noCancel" :no-close-on-backdrop="noCancel" :hide-header-close="noCancel" :ok-only="noCancel"
:busy="isSaving"
:ok-disabled="!isCreate && !isModified"
:no-close-on-esc="isSaving || isBusy || noCancel" :no-close-on-backdrop="isSaving || isBusy || noCancel || isModified" :hide-header-close="noCancel"
@close="(isSaving || isBusy) && $event.preventDefault()"
@ok.prevent="handleSubmit(observer)"
@show="$emit('show')"
@hidden="$emit('hidden')"
@ -24,10 +23,10 @@
<template #modal-footer="{ ok, cancel }">
<slot name="footer-left"></slot>
<div style="flex-grow: 1"></div>
<b-button v-if="!noCancel" :variant="noCancel || isModified || isCreate ? 'secondary' : 'primary'" @click="cancel" :disabled="isSaving">
<b-button v-if="!noCancel" :variant="noCancel || isModified || isCreate ? 'secondary' : 'primary'" @click="cancel" :disabled="isSaving || isBusy">
{{isModified ? "Cancel" : "Close"}}
</b-button>
<b-button v-if="noCancel || isModified || isCreate" variant="primary" @click="ok" :disabled="isSaving">
<b-button v-if="noCancel || isModified || isCreate" variant="primary" @click="ok" :disabled="isSaving || isBusy">
<b-spinner small v-if="isSaving"></b-spinner>
{{okTitle || (isCreate ? 'Create' : 'Save')}}
</b-button>