diff --git a/components/events/EventSearch.vue b/components/events/EventSearch.vue new file mode 100644 index 00000000..113b67e2 --- /dev/null +++ b/components/events/EventSearch.vue @@ -0,0 +1,153 @@ + + + diff --git a/components/servers/ServerAccess.vue b/components/servers/ServerAccess.vue new file mode 100644 index 00000000..496ecc31 --- /dev/null +++ b/components/servers/ServerAccess.vue @@ -0,0 +1,449 @@ + + + diff --git a/components/servers/ServerMapRotation.vue b/components/servers/ServerMapRotation.vue index e7661b18..32189328 100644 --- a/components/servers/ServerMapRotation.vue +++ b/components/servers/ServerMapRotation.vue @@ -6,17 +6,14 @@ import gql from "graphql-tag"; import { ChevronDown, ChevronUp, - Download, GripVertical, - ListOrdered, Map as MapIcon, - Plus, - Shuffle, - TriangleAlert, X, } from "lucide-vue-next"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; +import { Switch } from "~/components/ui/switch"; import { Select, SelectContent, @@ -26,8 +23,7 @@ import { SelectTrigger, SelectValue, } from "~/components/ui/select"; -import AnimatedFilters from "~/components/common/AnimatedFilters.vue"; -import SettingsSaveBar from "~/components/settings/SettingsSaveBar.vue"; +import SettingsSection from "~/components/settings/SettingsSection.vue"; import { toast } from "@/components/ui/toast"; type RotationMap = { @@ -38,7 +34,7 @@ type RotationMap = { workshop_map_id: string | null; }; -const props = defineProps<{ serverId: string; enabled?: boolean }>(); +const props = defineProps<{ serverId: string }>(); const { t } = useI18n(); const nuxtApp = useNuxtApp(); @@ -93,22 +89,6 @@ const QUERY = gql` } `; -const SAVE = gql` - mutation SetServerMapRotation( - $serverId: uuid! - $mapIds: [uuid!]! - $shuffle: Boolean! - ) { - setServerMapRotation( - server_id: $serverId - map_ids: $mapIds - shuffle: $shuffle - ) { - success - } - } -`; - const IMPORT = gql` mutation ImportWorkshopCollection($collection: String!) { importWorkshopCollection(collection: $collection) { @@ -132,9 +112,10 @@ const { result, refetch } = useQuery( const entries = ref>([]); const shuffle = ref(true); -const dirty = ref(false); -const revision = ref(0); -const submitting = ref(false); +const baseline = ref<{ ids: Array; shuffle: boolean }>({ + ids: [], + shuffle: true, +}); const importing = ref(false); const selectedMapId = ref(); const collection = ref(""); @@ -143,6 +124,46 @@ const brokenPosters = ref(new Set()); const mapKey = (map: RotationMap) => map.workshop_map_id || map.name; const mapLabel = (map: RotationMap) => map.label || map.name; +const changes = computed>(() => { + const before = baseline.value.ids; + const after = entries.value.map((map) => map.id); + const added = after.filter((id) => !before.includes(id)).length; + const removed = before.filter((id) => !after.includes(id)).length; + const list: Array = []; + + if (added) { + list.push( + t("pages.dedicated_servers.detail.settings.changes.maps_added", { + count: added, + }), + ); + } + + if (removed) { + list.push( + t("pages.dedicated_servers.detail.settings.changes.maps_removed", { + count: removed, + }), + ); + } + + if (!added && !removed && after.join() !== before.join()) { + list.push(t("pages.dedicated_servers.detail.settings.changes.map_order")); + } + + if (shuffle.value !== baseline.value.shuffle) { + list.push( + t( + shuffle.value + ? "pages.dedicated_servers.detail.settings.changes.shuffle_on" + : "pages.dedicated_servers.detail.settings.changes.shuffle_off", + ), + ); + } + + return list.map((text) => ({ text, restart: true })); +}); + function reset() { const server = (result.value as any)?.servers_by_pk; @@ -150,13 +171,16 @@ function reset() { (row: { map: RotationMap }) => row.map, ); shuffle.value = server?.map_rotation_shuffle ?? true; - dirty.value = false; + baseline.value = { + ids: entries.value.map((map) => map.id), + shuffle: shuffle.value, + }; } watch( result, () => { - if (!dirty.value) { + if (!changes.value.length) { reset(); } }, @@ -212,43 +236,12 @@ const warnWorkshopOrder = computed( entries.value.some((map) => map.workshop_map_id), ); -const orderModel = computed({ - get: () => (shuffle.value ? "shuffle" : "ordered"), - set: (value: string) => { - shuffle.value = value === "shuffle"; - touch(); - }, -}); - -const orderOptions = computed(() => [ - { - key: "shuffle", - label: t("pages.dedicated_servers.detail.map_rotation.shuffle"), - icon: Shuffle, - title: t("pages.dedicated_servers.detail.map_rotation.shuffle"), - desc: t("pages.dedicated_servers.detail.map_rotation.shuffle_hint"), - }, - { - key: "ordered", - label: t("pages.dedicated_servers.detail.map_rotation.in_order"), - icon: ListOrdered, - title: t("pages.dedicated_servers.detail.map_rotation.in_order"), - desc: t("pages.dedicated_servers.detail.map_rotation.in_order_hint"), - }, -]); - -function touch() { - dirty.value = true; - revision.value++; -} - function add(map: RotationMap) { if (entries.value.some((entry) => mapKey(entry) === mapKey(map))) { return; } - entries.value.push(map); - touch(); + entries.value = [...entries.value, map]; } function addSelected() { @@ -268,14 +261,14 @@ function moveTo(from: number, to: number) { return; } - const [map] = entries.value.splice(from, 1); - entries.value.splice(to, 0, map); - touch(); + const next = [...entries.value]; + const [map] = next.splice(from, 1); + next.splice(to, 0, map); + entries.value = next; } function remove(index: number) { - entries.value.splice(index, 1); - touch(); + entries.value = entries.value.filter((_, position) => position !== index); } const dragIndex = ref(null); @@ -365,289 +358,179 @@ async function importCollection() { await refetch(); } -async function save() { - if (submitting.value) { - return; - } - - submitting.value = true; - const startedAt = revision.value; - - try { - await nuxtApp.$apollo.defaultClient.mutate({ - mutation: SAVE, - variables: { - serverId: props.serverId, - mapIds: entries.value.map((map) => map.id), - shuffle: shuffle.value, - }, - }); - - toast({ - title: t( - props.enabled === false - ? "pages.dedicated_servers.detail.map_rotation.saved_disabled" - : "pages.dedicated_servers.detail.map_rotation.saved", - ), - }); - - // Changes made while the server was restarting stay pending. - if (revision.value === startedAt) { - dirty.value = false; - await refetch(); - } - } finally { - submitting.value = false; - } +function payload() { + return { + map_ids: entries.value.map((map) => map.id), + shuffle: shuffle.value, + }; } -const cardClasses = - "relative isolate overflow-hidden rounded-lg border border-border [background:linear-gradient(180deg,hsl(var(--card)/0.2)_0%,hsl(var(--card)/0.04)_100%)]"; +async function saved() { + baseline.value = { + ids: entries.value.map((map) => map.id), + shuffle: shuffle.value, + }; -const microLabelClasses = - "font-mono text-[0.6rem] font-bold uppercase tracking-[0.18em] text-muted-foreground"; + await refetch(); +} -const chipClasses = - "inline-flex items-center rounded border px-1.5 py-0.5 font-mono text-[0.58rem] font-bold uppercase leading-none tracking-[0.12em]"; +defineExpose({ changes, payload, reset, saved });