2024-08-26 19:09:43 +00:00
|
|
|
<!-- eslint-disable vue/multi-word-component-names -->
|
2023-09-09 22:18:16 +00:00
|
|
|
<template>
|
2023-09-10 03:38:24 +00:00
|
|
|
<div class="card">
|
2023-10-27 15:09:44 +00:00
|
|
|
<header class="card-header is-unselectable" style="cursor: pointer">
|
2023-09-12 15:39:54 +00:00
|
|
|
<p class="card-header-title" @click="toggle">{{ header }}</p>
|
2023-09-13 15:58:15 +00:00
|
|
|
|
2025-12-28 01:10:28 +00:00
|
|
|
<p v-if="refreshable && is_open" class="card-header-icon px-0">
|
|
|
|
|
<BulmaButton class="is-small is-primary" @click="load">
|
2024-08-26 19:09:43 +00:00
|
|
|
<FontAwesomeIcon
|
|
|
|
|
:icon="['fas', 'arrows-rotate']"
|
2025-12-28 01:10:28 +00:00
|
|
|
:spin="state === 'loading'"
|
2023-09-12 20:39:31 +00:00
|
|
|
/>
|
2023-09-14 13:54:23 +00:00
|
|
|
</BulmaButton>
|
2023-09-12 15:46:45 +00:00
|
|
|
</p>
|
2023-09-13 15:58:15 +00:00
|
|
|
|
2023-09-12 15:39:54 +00:00
|
|
|
<button class="card-header-icon" @click="toggle">
|
2023-09-09 23:19:46 +00:00
|
|
|
<span class="icon">
|
2024-08-26 19:09:43 +00:00
|
|
|
<FontAwesomeIcon
|
|
|
|
|
:icon="['fas', is_open ? 'angle-down' : 'angle-right']"
|
2023-09-09 22:18:16 +00:00
|
|
|
/>
|
|
|
|
|
</span>
|
2023-09-10 03:38:24 +00:00
|
|
|
</button>
|
|
|
|
|
</header>
|
|
|
|
|
|
2025-12-28 16:44:18 +00:00
|
|
|
<slot v-if="state === 'loading'" name="loading">
|
|
|
|
|
<div class="card-content">
|
2024-08-26 19:09:43 +00:00
|
|
|
<progress class="progress is-primary" />
|
2023-09-14 03:59:33 +00:00
|
|
|
</div>
|
2025-12-28 16:44:18 +00:00
|
|
|
</slot>
|
|
|
|
|
|
|
|
|
|
<slot v-else-if="state === 'err'" name="error">
|
|
|
|
|
<div class="card-content has-text-danger has-text-centered">
|
2023-09-13 15:58:15 +00:00
|
|
|
<span class="icon is-large">
|
2024-08-26 19:09:43 +00:00
|
|
|
<FontAwesomeIcon :icon="['fas', 'ban']" size="3x" />
|
2023-09-13 15:58:15 +00:00
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-12-28 16:44:18 +00:00
|
|
|
</slot>
|
|
|
|
|
|
|
|
|
|
<slot v-else-if="state === 'ok'" name="default" />
|
2023-09-09 22:18:16 +00:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
2024-08-26 19:09:43 +00:00
|
|
|
<script setup lang="ts">
|
2025-12-28 01:10:28 +00:00
|
|
|
import { computed, ref } from "vue";
|
2024-08-27 00:25:42 +00:00
|
|
|
|
2023-09-14 13:54:23 +00:00
|
|
|
import BulmaButton from "./Button.vue";
|
|
|
|
|
|
2025-12-28 01:10:28 +00:00
|
|
|
const props = withDefaults(
|
2024-08-26 19:09:43 +00:00
|
|
|
defineProps<{
|
|
|
|
|
header: string;
|
2025-12-28 01:10:28 +00:00
|
|
|
opening?: () => Promise<void>;
|
2024-08-26 19:09:43 +00:00
|
|
|
refreshable?: boolean;
|
|
|
|
|
}>(),
|
2025-12-28 01:10:28 +00:00
|
|
|
{ opening: async () => {}, refreshable: false },
|
2024-08-26 19:09:43 +00:00
|
|
|
);
|
2023-09-14 03:59:33 +00:00
|
|
|
|
2025-12-28 01:10:28 +00:00
|
|
|
const state = ref<"closed" | "loading" | "ok" | "err">("closed");
|
|
|
|
|
const is_open = computed(() => state.value !== "closed");
|
2023-09-12 15:39:54 +00:00
|
|
|
|
2025-12-28 16:35:10 +00:00
|
|
|
async function toggle(): Promise<void> {
|
2024-08-26 19:09:43 +00:00
|
|
|
if (is_open.value) {
|
2025-12-28 01:10:28 +00:00
|
|
|
state.value = "closed";
|
|
|
|
|
} else {
|
|
|
|
|
await load();
|
2023-09-14 03:59:33 +00:00
|
|
|
}
|
2024-08-26 19:09:43 +00:00
|
|
|
}
|
2023-09-14 03:59:33 +00:00
|
|
|
|
2025-12-28 16:35:10 +00:00
|
|
|
async function load(): Promise<void> {
|
2025-12-28 01:10:28 +00:00
|
|
|
state.value = "loading";
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await props.opening();
|
|
|
|
|
state.value = "ok";
|
|
|
|
|
} catch {
|
|
|
|
|
state.value = "err";
|
|
|
|
|
}
|
2023-09-09 22:18:16 +00:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
2023-10-27 15:09:44 +00:00
|
|
|
<style scoped>
|
|
|
|
|
div.card:not(:last-child) {
|
|
|
|
|
margin-bottom: 1.5rem;
|
2023-09-09 22:18:16 +00:00
|
|
|
}
|
|
|
|
|
</style>
|