kopia lustrzana https://github.com/FacilMap/facilmap
Add spinners to a lot of actions (also fixes #93)
rodzic
7cf01a9abe
commit
f02a99bcc5
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -48,6 +48,7 @@ export default class MarkerInfo extends Vue {
|
|||
return;
|
||||
|
||||
this.isDeleting = true;
|
||||
|
||||
try {
|
||||
await this.client.deleteMarker({ id: this.markerId });
|
||||
} catch (err) {
|
||||
|
|
|
@ -75,6 +75,7 @@ export default class MultipleInfo extends Vue {
|
|||
return;
|
||||
|
||||
this.isDeleting = true;
|
||||
|
||||
try {
|
||||
for (const object of this.objects) {
|
||||
if (isMarker(object))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
Ładowanie…
Reference in New Issue