Skip to content

Commit dd51c08

Browse files
authored
feat: clean up server power action buttons (#5836)
1 parent 5244060 commit dd51c08

4 files changed

Lines changed: 229 additions & 80 deletions

File tree

packages/ui/src/components/base/JoinedButtons.vue

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
<template>
22
<div class="joined-buttons">
3-
<ButtonStyled :color="color">
4-
<button :disabled="disabled" @click="handlePrimaryAction">
3+
<ButtonStyled
4+
:color="color"
5+
:size="size"
6+
:class="{ 'joined-buttons__primary--muted': primaryMuted }"
7+
>
8+
<button :disabled="primaryDisabledResolved" @click="handlePrimaryAction">
59
<component :is="primaryAction.icon" v-if="primaryAction.icon" aria-hidden="true" />
610
{{ primaryAction.label }}
711
</button>
812
</ButtonStyled>
9-
<ButtonStyled v-if="dropdownActions.length > 0" :color="color">
10-
<OverflowMenu class="btn-dropdown-animation" :options="dropdownOptions" :disabled="disabled">
13+
<ButtonStyled
14+
v-if="dropdownActions.length > 0"
15+
:color="color"
16+
:size="size"
17+
class="joined-buttons__dropdown"
18+
>
19+
<OverflowMenu
20+
class="btn-dropdown-animation !w-10"
21+
:options="dropdownOptions"
22+
:disabled="dropdownDisabledResolved"
23+
>
1124
<DropdownIcon />
1225
<template v-for="action in dropdownActions" :key="action.id" #[action.id]>
1326
<component :is="action.icon" v-if="action.icon" aria-hidden="true" />
@@ -40,14 +53,25 @@ export interface JoinedButtonAction {
4053
interface Props {
4154
actions: JoinedButtonAction[]
4255
color?: Colors
56+
size?: 'standard' | 'large' | 'small'
4357
disabled?: boolean
58+
primaryDisabled?: boolean
59+
dropdownDisabled?: boolean
60+
primaryMuted?: boolean
4461
}
4562
4663
const props = withDefaults(defineProps<Props>(), {
4764
color: 'standard',
65+
size: 'standard',
4866
disabled: false,
67+
primaryDisabled: undefined,
68+
dropdownDisabled: undefined,
69+
primaryMuted: false,
4970
})
5071
72+
const primaryDisabledResolved = computed(() => props.primaryDisabled ?? props.disabled)
73+
const dropdownDisabledResolved = computed(() => props.dropdownDisabled ?? props.disabled)
74+
5175
const primaryAction = computed(() => props.actions[0])
5276
5377
const dropdownActions = computed(() => props.actions.slice(1))
@@ -84,7 +108,7 @@ const dropdownOptions = computed(() =>
84108
)
85109
86110
function handlePrimaryAction() {
87-
if (primaryAction.value && !props.disabled) {
111+
if (primaryAction.value && !primaryDisabledResolved.value) {
88112
primaryAction.value.action()
89113
}
90114
}
@@ -118,4 +142,8 @@ function handlePrimaryAction() {
118142
.btn-dropdown-animation {
119143
padding: 0.5rem !important;
120144
}
145+
146+
.joined-buttons__primary--muted {
147+
opacity: 0.6;
148+
}
121149
</style>

packages/ui/src/components/servers/server-header/PanelServerActionButton.vue

Lines changed: 54 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,46 @@
77
</button>
88
</ButtonStyled>
99

10-
<template v-else>
11-
<ButtonStyled v-if="showStopButton" type="standard" color="red" size="large">
12-
<button
13-
v-tooltip="busyTooltip"
14-
:disabled="!canTakeAction"
15-
@click="initiateAction('Stop')"
16-
>
17-
<div class="flex gap-1">
18-
<StopCircleIcon class="h-5 w-5" />
19-
<span>Stop</span>
20-
</div>
10+
<template v-else-if="showRestartButton">
11+
<ButtonStyled type="standard" color="orange" size="large">
12+
<button v-tooltip="busyTooltip" :disabled="!canTakeAction" @click="handlePrimaryAction">
13+
<UpdatedIcon />
14+
<span>{{ primaryActionText }}</span>
2115
</button>
2216
</ButtonStyled>
2317

24-
<div v-if="showRestartDropdown" class="joined-buttons">
25-
<ButtonStyled type="standard" color="orange" size="large">
26-
<button v-tooltip="busyTooltip" :disabled="!canTakeAction" @click="handlePrimaryAction">
27-
<UpdatedIcon />
28-
<span>{{ primaryActionText }}</span>
29-
</button>
30-
</ButtonStyled>
31-
<ButtonStyled type="standard" color="orange" size="large">
32-
<OverflowMenu
33-
v-tooltip="busyTooltip"
34-
:disabled="!canTakeAction"
35-
:options="[
36-
{
37-
id: 'kill_server',
38-
action: () => initiateAction('Kill'),
39-
},
40-
]"
41-
>
42-
<div class="w-0 text-xl relative top-0.5 right-2.5">
43-
<DropdownIcon />
44-
</div>
18+
<JoinedButtons
19+
color="red"
20+
size="large"
21+
:actions="stopSplitActions"
22+
:primary-disabled="!canTakeAction"
23+
:dropdown-disabled="!canKill"
24+
>
25+
<template #kill_server>
26+
<SlashIcon class="h-5 w-5" />
27+
Kill server
28+
</template>
29+
</JoinedButtons>
30+
</template>
31+
32+
<template v-else-if="isStopping">
33+
<JoinedButtons
34+
color="red"
35+
size="large"
36+
:actions="stopSplitActions"
37+
:primary-disabled="true"
38+
:dropdown-disabled="!canKill"
39+
:primary-muted="true"
40+
>
41+
<template #kill_server>
42+
<SlashIcon class="h-5 w-5" />
43+
Kill server
44+
</template>
45+
</JoinedButtons>
46+
</template>
4547

46-
<template #kill_server>
47-
<SlashIcon class="h-5 w-5" />
48-
Kill server
49-
</template>
50-
</OverflowMenu>
51-
</ButtonStyled>
52-
</div>
53-
<ButtonStyled v-else type="standard" color="brand" size="large">
48+
<template v-else>
49+
<ButtonStyled type="standard" color="brand" size="large">
5450
<button v-tooltip="busyTooltip" :disabled="!canTakeAction" @click="handlePrimaryAction">
5551
<PlayIcon />
5652
<span>{{ primaryActionText }}</span>
@@ -63,7 +59,6 @@
6359

6460
<script setup lang="ts">
6561
import {
66-
DropdownIcon,
6762
LoaderCircleIcon,
6863
PlayIcon,
6964
SlashIcon,
@@ -72,7 +67,7 @@ import {
7267
} from '@modrinth/assets'
7368
import { computed } from 'vue'
7469
75-
import { ButtonStyled, OverflowMenu } from '#ui/components'
70+
import { ButtonStyled, type JoinedButtonAction, JoinedButtons } from '#ui/components'
7671
7772
import { useServerPowerAction } from './use-server-power-action'
7873
@@ -87,41 +82,30 @@ const props = withDefaults(
8782
8883
const {
8984
isInstalling,
90-
showStopButton,
85+
isStopping,
86+
showRestartButton,
9187
busyTooltip,
9288
canTakeAction,
89+
canKill,
9390
primaryActionText,
9491
initiateAction,
9592
handlePrimaryAction,
9693
} = useServerPowerAction({
9794
disabled: computed(() => props.disabled),
9895
})
9996
100-
const showRestartDropdown = computed(() => primaryActionText.value === 'Restart')
97+
const stopSplitActions = computed<JoinedButtonAction[]>(() => [
98+
{
99+
id: 'stop',
100+
label: isStopping.value ? 'Stopping' : 'Stop',
101+
icon: StopCircleIcon,
102+
action: () => initiateAction('Stop'),
103+
},
104+
{
105+
id: 'kill_server',
106+
label: 'Kill server',
107+
icon: SlashIcon,
108+
action: () => initiateAction('Kill'),
109+
},
110+
])
101111
</script>
102-
103-
<style scoped>
104-
.joined-buttons {
105-
display: flex;
106-
align-items: center;
107-
}
108-
109-
.joined-buttons > :deep(.btn) {
110-
border-radius: 0;
111-
}
112-
113-
.joined-buttons > :deep(.btn:first-child) {
114-
border-top-left-radius: var(--radius-md);
115-
border-bottom-left-radius: var(--radius-md);
116-
}
117-
118-
.joined-buttons > :deep(.btn:last-child) {
119-
border-top-right-radius: var(--radius-md);
120-
border-bottom-right-radius: var(--radius-md);
121-
margin-left: -1px;
122-
}
123-
124-
.joined-buttons > :deep(.btn:not(:last-child)) {
125-
border-right: none;
126-
}
127-
</style>

packages/ui/src/components/servers/server-header/use-server-power-action.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
2020
const isStopping = computed(() => powerState.value === 'stopping')
2121
const isStarting = computed(() => powerState.value === 'starting')
2222
const isTransitioning = computed(() => isStarting.value || isStopping.value)
23-
const showStopButton = computed(() => isRunning.value || isStarting.value)
23+
24+
const showStopSplit = computed(() => isRunning.value || isStarting.value || isStopping.value)
25+
const showRestartButton = computed(() => isRunning.value || isStarting.value)
26+
27+
const isBlockedByPropsOrBusy = computed(
28+
() => Boolean(options?.disabled?.value) || busyReasons.value.length > 0,
29+
)
2430

2531
const busyTooltip = computed(() => {
2632
if (isStarting.value) return 'Your server is starting'
2733
return busyReasons.value.length > 0 ? formatMessage(busyReasons.value[0].reason) : undefined
2834
})
2935

30-
const canTakeAction = computed(
31-
() => !isTransitioning.value && !options?.disabled?.value && busyReasons.value.length === 0,
36+
const canTakeAction = computed(() => !isTransitioning.value && !isBlockedByPropsOrBusy.value)
37+
38+
const canKill = computed(
39+
() =>
40+
!isBlockedByPropsOrBusy.value && (isStopping.value || isRunning.value || isStarting.value),
3241
)
3342

3443
const primaryActionText = computed(() => {
@@ -57,7 +66,11 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
5766
}
5867

5968
function initiateAction(action: PowerAction) {
60-
if (!canTakeAction.value) return
69+
if (action === 'Kill') {
70+
if (!canKill.value) return
71+
} else {
72+
if (!canTakeAction.value) return
73+
}
6174
void sendPowerAction(action)
6275
}
6376

@@ -70,9 +83,11 @@ export function useServerPowerAction(options?: { disabled?: Ref<boolean> }) {
7083
isRunning,
7184
isStopping,
7285
isTransitioning,
73-
showStopButton,
86+
showStopSplit,
87+
showRestartButton,
7488
busyTooltip,
7589
canTakeAction,
90+
canKill,
7691
primaryActionText,
7792
sendPowerAction,
7893
initiateAction,

0 commit comments

Comments
 (0)