Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions assets/css/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,37 @@ mark {
transparent 4px
);
}
/* Scan sweep across the top of the queue / match-confirm panels. The band is a
repeating gradient whose period is one track width, painted on a double-width
element, so shifting exactly one period (-50%) loops with no seam: the next
band enters the left edge as the previous one leaves the right. Colour is
currentColor, so callers pick it with text-*. */
@keyframes tac-scan-sweep {
from {
transform: translateX(-50%);
}
to {
transform: translateX(0);
}
}
.tac-scan-sweep {
width: 200%;
background-image: repeating-linear-gradient(
90deg,
transparent 0,
transparent 12.5%,
currentColor 25%,
transparent 37.5%,
transparent 50%
);
animation: tac-scan-sweep 2s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.tac-scan-sweep {
animation: none;
}
}

/* One-shot pulse for clip share buttons — toast is far from the click
target, so the button itself flashes amber + scales briefly. Used
by MatchHighlightsReel queue/featured share, HighlightCard, and
Expand Down
91 changes: 90 additions & 1 deletion components/MatchOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,64 @@ import SettingHeader from "~/components/match/SettingHeader.vue";
</FormItem>
</FormField>

<FormField
v-if="canSetVetoPickTimeout"
v-slot="{ value }"
name="veto_pick_timeout"
>
<FormItem>
<div
class="flex flex-row items-center justify-between cursor-pointer"
@click="toggleVetoTimer(!value)"
>
<div class="space-y-0.5">
<SettingHeader>{{
$t("match.options.advanced.veto_pick_timeout.label")
}}</SettingHeader>
<FormDescription>{{
$t(
"match.options.advanced.veto_pick_timeout.description",
)
}}</FormDescription>
</div>
<FormControl>
<Switch
class="pointer-events-none"
:model-value="value > 0"
/>
</FormControl>
</div>

<div v-if="value > 0" class="mt-4 pl-4 border-l-2">
<NumberField
class="gap-2"
:min="1"
:max="600"
:model-value="value"
@update:model-value="
(timeout) => {
form.setFieldValue('veto_pick_timeout', timeout);
}
"
>
<NumberFieldContent>
<NumberFieldDecrement />
<FormControl>
<NumberFieldInput />
</FormControl>
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
<FormDescription class="mt-2">
{{
$t("match.options.advanced.veto_pick_timeout.range")
}}
</FormDescription>
</div>
<FormMessage />
</FormItem>
</FormField>

<FormField v-slot="{ value }" name="round_restart_delay">
<FormItem>
<SettingHeader>{{
Expand All @@ -832,7 +890,9 @@ import SettingHeader from "~/components/match/SettingHeader.vue";
</NumberFieldContent>
</NumberField>
<FormDescription>
{{ $t("match.options.advanced.round_restart_delay.range") }}
{{
$t("match.options.advanced.round_restart_delay.range")
}}
</FormDescription>
<FormMessage />
</FormItem>
Expand Down Expand Up @@ -1175,6 +1235,7 @@ import {
import { mapFields } from "~/graphql/mapGraphql";
import { useApplicationSettingsStore } from "~/stores/ApplicationSettings";
import { useAuthStore } from "~/stores/AuthStore";
import { canSetVetoPickTimeout } from "~/utilities/setupOptions";

interface Map {
id: string;
Expand Down Expand Up @@ -1294,6 +1355,7 @@ export default {
select_single_region: null as string | null,
showAdvancedSettings: false,
advHeight: "0px",
lastVetoPickTimeout: 0,
};
},
watch: {
Expand Down Expand Up @@ -1634,6 +1696,9 @@ export default {
canSetcheckInSettings() {
return useAuthStore().isRoleAbove(e_player_roles_enum.match_organizer);
},
canSetVetoPickTimeout() {
return canSetVetoPickTimeout();
},
canSetMatchCancellation() {
return useAuthStore().isRoleAbove(
e_player_roles_enum.tournament_organizer,
Expand All @@ -1651,8 +1716,32 @@ export default {
)?.value;
return val || "180";
},
vetoPickTimeoutDefault(): number {
const val = Number.parseInt(
useApplicationSettingsStore().settings.find(
(s) => s.name === "public.veto_pick_timeout",
)?.value ?? "",
10,
);
return Number.isNaN(val) || val <= 0 ? 60 : val;
},
},
methods: {
toggleVetoTimer(enabled: boolean) {
if (!enabled) {
const current = this.form.values.veto_pick_timeout;
if (current > 0) {
this.lastVetoPickTimeout = current;
}
this.form.setFieldValue("veto_pick_timeout", 0);
return;
}

this.form.setFieldValue(
"veto_pick_timeout",
this.lastVetoPickTimeout || this.vetoPickTimeoutDefault,
);
},
animateAdvanced(open: boolean) {
const wrap = this.$refs.advWrapRef as HTMLElement | undefined;
if (!wrap) {
Expand Down
62 changes: 19 additions & 43 deletions components/draft-games/DraftClock.vue
Original file line number Diff line number Diff line change
@@ -1,64 +1,32 @@
<script setup lang="ts">
import { ref, watch, computed, onUnmounted } from "vue";
import { computed, toRef } from "vue";
import { useCountdown } from "~/composables/useCountdown";

const props = withDefaults(
defineProps<{
deadline?: string;
total?: number;
accent?: string;
pulse?: boolean;
compact?: boolean;
}>(),
{
total: 30,
accent: "var(--tac-amber)",
pulse: false,
compact: false,
},
);

const remaining = ref<number>(0);
let raf: number | null = null;

const RADIUS = 52;
const CIRC = 2 * Math.PI * RADIUS;

const fraction = computed(() => {
if (!props.deadline || props.total <= 0) {
return 1;
}
return Math.max(0, Math.min(1, remaining.value / props.total));
});
const { remaining, fraction, urgent } = useCountdown(
toRef(props, "deadline"),
toRef(props, "total"),
);

const dashOffset = computed(() => CIRC * (1 - fraction.value));

// Drive the ring off requestAnimationFrame so the stroke interpolates every
// frame — a setInterval tick makes it visibly step.
const tick = () => {
if (!props.deadline) {
remaining.value = 0;
raf = null;
return;
}
const ms = new Date(props.deadline).getTime() - Date.now();
remaining.value = Math.max(0, ms / 1000);
raf = remaining.value > 0 ? requestAnimationFrame(tick) : null;
};

const start = () => {
if (raf) {
cancelAnimationFrame(raf);
}
tick();
};

watch(() => props.deadline, start, { immediate: true });

onUnmounted(() => {
if (raf) {
cancelAnimationFrame(raf);
}
});

const urgent = computed(() => remaining.value <= 6 && remaining.value > 0);
</script>

<template>
Expand All @@ -67,7 +35,11 @@ const urgent = computed(() => remaining.value <= 6 && remaining.value > 0);
:class="{ pulse: pulse }"
:style="{ '--accent': accent }"
>
<svg viewBox="0 0 120 120" class="h-32 w-32 -rotate-90">
<svg
viewBox="0 0 120 120"
class="-rotate-90"
:class="compact ? 'h-16 w-16' : 'h-32 w-32'"
>
<circle
cx="60"
cy="60"
Expand Down Expand Up @@ -95,13 +67,17 @@ const urgent = computed(() => remaining.value <= 6 && remaining.value > 0);
<div class="absolute inset-0 grid place-items-center text-center">
<div>
<div
class="font-mono text-3xl font-bold tabular-nums leading-none"
:class="urgent ? 'text-destructive' : 'text-foreground'"
class="font-mono font-bold tabular-nums leading-none"
:class="[
urgent ? 'text-destructive' : 'text-foreground',
compact ? 'text-base' : 'text-3xl',
]"
>
<template v-if="deadline">{{ Math.ceil(remaining) }}</template>
<template v-else>—</template>
</div>
<div
v-if="!compact"
class="mt-1 font-mono text-[0.55rem] uppercase tracking-[0.28em] text-muted-foreground"
>
<slot>SEC</slot>
Expand Down
Loading