Skip to content

Commit 1fd0bde

Browse files
committed
refactor: yeet modal, start work on subpage for editing
1 parent cb96a9d commit 1fd0bde

File tree

6 files changed

+204
-611
lines changed

6 files changed

+204
-611
lines changed

apps/frontend/src/composables/servers/modules/scheduling.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,40 @@ export class SchedulingModule extends ServerModule {
66
tasks: ScheduledTask[] = [];
77

88
async fetch(): Promise<void> {
9-
this.tasks = await useServersFetch<ScheduledTask[]>(
10-
`servers/${this.serverId}/options/schedules`,
11-
{ version: 1 },
12-
);
9+
// this.tasks = await useServersFetch<ScheduledTask[]>(
10+
// `servers/${this.serverId}/options/schedules`,
11+
// { version: 1 },
12+
// );
1313
}
1414

1515
async deleteTask(task: ScheduledTask): Promise<void> {
16-
await useServersFetch(`servers/${this.serverId}/options/schedules`, {
17-
method: "DELETE",
18-
body: { title: task.title },
19-
version: 1,
20-
});
21-
this.tasks = this.tasks.filter((t) => t.title !== task.title);
16+
// await useServersFetch(`servers/${this.serverId}/options/schedules`, {
17+
// method: "DELETE",
18+
// body: { title: task.title },
19+
// version: 1,
20+
// });
21+
// this.tasks = this.tasks.filter((t) => t.title !== task.title);
2222
}
2323

2424
async createTask(task: ScheduledTask): Promise<number> {
25-
await useServersFetch(`servers/${this.serverId}/options/schedules`, {
26-
method: "POST",
27-
body: task,
28-
version: 1,
29-
});
30-
this.tasks.push(task);
31-
return this.tasks.length;
25+
// await useServersFetch(`servers/${this.serverId}/options/schedules`, {
26+
// method: "POST",
27+
// body: task,
28+
// version: 1,
29+
// });
30+
// this.tasks.push(task);
31+
// return this.tasks.length;
3232
}
3333

3434
async editTask(taskTitle: string, updatedTask: Partial<ScheduledTask>): Promise<void> {
35-
await useServersFetch(`servers/${this.serverId}/options/schedules`, {
36-
method: "PATCH",
37-
body: { title: taskTitle, ...updatedTask },
38-
version: 1,
39-
});
40-
const index = this.tasks.findIndex((t) => t.title === taskTitle);
41-
if (index !== -1) {
42-
this.tasks[index] = { ...this.tasks[index], ...updatedTask };
43-
}
35+
// await useServersFetch(`servers/${this.serverId}/options/schedules`, {
36+
// method: "PATCH",
37+
// body: { title: taskTitle, ...updatedTask },
38+
// version: 1,
39+
// });
40+
// const index = this.tasks.findIndex((t) => t.title === taskTitle);
41+
// if (index !== -1) {
42+
// this.tasks[index] = { ...this.tasks[index], ...updatedTask };
43+
// }
4444
}
4545
}

apps/frontend/src/pages/servers/manage/[id]/options/scheduling.vue renamed to apps/frontend/src/pages/servers/manage/[id]/options/scheduling/index.vue

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
<div class="header__row">
77
<h2 class="header__title text-2xl">Task Scheduling</h2>
88
<div class="input-group">
9-
<button class="iconified-button brand-button" @click="handleCreateTask">
9+
<NuxtLink
10+
:to="`/servers/manage/${route.params.id}/options/scheduling/new`"
11+
class="iconified-button brand-button"
12+
>
1013
<PlusIcon />
1114
Create Task
12-
</button>
15+
</NuxtLink>
1316
</div>
1417
</div>
1518

@@ -129,9 +132,12 @@
129132
<div>
130133
<div class="flex gap-1">
131134
<ButtonStyled icon-only circular>
132-
<button v-tooltip="'Edit task'" @click="handleTaskEdit(task)">
135+
<NuxtLink
136+
v-tooltip="'Edit task'"
137+
:to="`/servers/manage/${route.params.id}/options/scheduling/${encodeURIComponent(task.title)}`"
138+
>
133139
<EditIcon />
134-
</button>
140+
</NuxtLink>
135141
</ButtonStyled>
136142
<ButtonStyled icon-only circular color="red">
137143
<button
@@ -154,8 +160,6 @@
154160
</section>
155161
</div>
156162
</div>
157-
158-
<ScheduleTaskModal ref="scheduleModal" @save="handleTaskSave" />
159163
</div>
160164
</template>
161165

@@ -172,7 +176,7 @@ import {
172176
SortAscendingIcon as AscendingIcon,
173177
SortDescendingIcon as DescendingIcon,
174178
} from "@modrinth/assets";
175-
import { Toggle, Checkbox, RaisedBadge, ButtonStyled, ScheduleTaskModal } from "@modrinth/ui";
179+
import { Toggle, Checkbox, RaisedBadge, ButtonStyled } from "@modrinth/ui";
176180
import cronstrue from "cronstrue";
177181
import type { ScheduledTask } from "@modrinth/utils";
178182
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
@@ -181,14 +185,15 @@ const props = defineProps<{
181185
server: ModrinthServer;
182186
}>();
183187
188+
const route = useRoute();
189+
184190
onBeforeMount(async () => {
185191
await props.server.scheduling.fetch();
186192
});
187193
188194
const selectedTasks = ref<ScheduledTask[]>([]);
189195
const sortBy = ref("Name");
190196
const descending = ref(false);
191-
const scheduleModal = ref();
192197
193198
const tasks = computed(() => props.server.scheduling.tasks);
194199
@@ -238,10 +243,6 @@ async function handleTaskToggle(task: ScheduledTask, enabled: boolean): Promise<
238243
}
239244
}
240245
241-
function handleTaskEdit(task: ScheduledTask): void {
242-
scheduleModal.value?.show(task);
243-
}
244-
245246
async function handleTaskDelete(task: ScheduledTask): Promise<void> {
246247
if (confirm(`Are you sure you want to delete "${task.title}"?`)) {
247248
try {
@@ -254,10 +255,6 @@ async function handleTaskDelete(task: ScheduledTask): Promise<void> {
254255
}
255256
}
256257
257-
function handleCreateTask(): void {
258-
scheduleModal.value?.showNew();
259-
}
260-
261258
async function handleBulkToggle(): Promise<void> {
262259
const enabledCount = selectedTasks.value.filter((t) => t.enabled).length;
263260
const shouldEnable = enabledCount < selectedTasks.value.length / 2;
@@ -288,23 +285,6 @@ async function handleBulkDelete(): Promise<void> {
288285
}
289286
}
290287
291-
async function handleTaskSave(data: ScheduledTask): Promise<void> {
292-
try {
293-
if (scheduleModal.value?.mode === "edit") {
294-
await props.server.scheduling.editTask(
295-
scheduleModal.value.originalData?.title || data.title,
296-
data,
297-
);
298-
console.log("Updated task:", data.title);
299-
} else {
300-
await props.server.scheduling.createTask(data);
301-
console.log("Created task:", data.title);
302-
}
303-
} catch (error) {
304-
console.error("Failed to save task:", error);
305-
}
306-
}
307-
308288
function updateSort(): void {
309289
// Trigger reactivity for sortedTasks
310290
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<template>
2+
<div>
3+
<div
4+
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-button-bg p-1 text-sm font-bold"
5+
>
6+
<button
7+
v-for="(tab, index) in tabs"
8+
:key="tab"
9+
ref="tabElements"
10+
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full bg-transparent"
11+
:class="{
12+
'text-button-textSelected': activeTabIndex === index,
13+
'text-contrast': activeTabIndex !== index,
14+
}"
15+
@click="setActiveTab(index)"
16+
>
17+
<span class="text-nowrap font-bold text-center mx-auto">{{ getTabLabel(tab) }}</span>
18+
</button>
19+
20+
<div
21+
:class="`tabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 bg-button-bgSelected`"
22+
:style="{
23+
left: sliderLeftPx,
24+
top: sliderTopPx,
25+
right: sliderRightPx,
26+
bottom: sliderBottomPx,
27+
opacity:
28+
sliderLeft === 4 && sliderLeft === sliderRight ? 0 : activeTabIndex === -1 ? 0 : 1,
29+
}"
30+
aria-hidden="true"
31+
/>
32+
</div>
33+
34+
<!-- Tab Content -->
35+
<div class="tab-content mt-4">
36+
<template v-for="(tab, index) in tabs" :key="tab">
37+
<div v-show="activeTabIndex === index" class="tab-panel">
38+
<slot :name="tab" />
39+
</div>
40+
</template>
41+
</div>
42+
</div>
43+
</template>
44+
45+
<script setup lang="ts">
46+
import { ref, computed, watch, onMounted, nextTick } from 'vue'
47+
48+
interface Props {
49+
tabs: string[]
50+
formatFunction?: (tab: string) => string
51+
defaultTab?: string
52+
}
53+
54+
const props = withDefaults(defineProps<Props>(), {
55+
formatFunction: undefined,
56+
defaultTab: undefined,
57+
})
58+
59+
const activeTabIndex = ref(0)
60+
const tabElements = ref<HTMLElement[]>([])
61+
62+
const sliderLeft = ref(4)
63+
const sliderTop = ref(4)
64+
const sliderRight = ref(4)
65+
const sliderBottom = ref(4)
66+
67+
const sliderLeftPx = computed(() => `${sliderLeft.value}px`)
68+
const sliderTopPx = computed(() => `${sliderTop.value}px`)
69+
const sliderRightPx = computed(() => `${sliderRight.value}px`)
70+
const sliderBottomPx = computed(() => `${sliderBottom.value}px`)
71+
72+
function getTabLabel(tab: string): string {
73+
return props.formatFunction ? props.formatFunction(tab) : tab
74+
}
75+
76+
function setActiveTab(index: number) {
77+
activeTabIndex.value = index
78+
updateSliderPosition()
79+
}
80+
81+
function updateSliderPosition() {
82+
nextTick(() => {
83+
const el = tabElements.value[activeTabIndex.value]
84+
85+
if (!el || !el.offsetParent) return
86+
87+
const parent = el.offsetParent as HTMLElement
88+
89+
const newValues = {
90+
left: el.offsetLeft,
91+
top: el.offsetTop,
92+
right: parent.offsetWidth - el.offsetLeft - el.offsetWidth,
93+
bottom: parent.offsetHeight - el.offsetTop - el.offsetHeight,
94+
}
95+
96+
if (sliderLeft.value === 4 && sliderRight.value === 4) {
97+
// Initial position
98+
sliderLeft.value = newValues.left
99+
sliderRight.value = newValues.right
100+
sliderTop.value = newValues.top
101+
sliderBottom.value = newValues.bottom
102+
} else {
103+
const delay = 200
104+
105+
if (newValues.left < sliderLeft.value) {
106+
sliderLeft.value = newValues.left
107+
setTimeout(() => {
108+
sliderRight.value = newValues.right
109+
}, delay)
110+
} else {
111+
sliderRight.value = newValues.right
112+
setTimeout(() => {
113+
sliderLeft.value = newValues.left
114+
}, delay)
115+
}
116+
117+
if (newValues.top < sliderTop.value) {
118+
sliderTop.value = newValues.top
119+
setTimeout(() => {
120+
sliderBottom.value = newValues.bottom
121+
}, delay)
122+
} else {
123+
sliderBottom.value = newValues.bottom
124+
setTimeout(() => {
125+
sliderTop.value = newValues.top
126+
}, delay)
127+
}
128+
}
129+
})
130+
}
131+
132+
onMounted(() => {
133+
if (props.defaultTab) {
134+
const defaultIndex = props.tabs.indexOf(props.defaultTab)
135+
if (defaultIndex !== -1) {
136+
activeTabIndex.value = defaultIndex
137+
}
138+
}
139+
updateSliderPosition()
140+
})
141+
142+
watch(activeTabIndex, () => {
143+
updateSliderPosition()
144+
})
145+
</script>
146+
147+
<style scoped>
148+
.tabs-transition {
149+
transition:
150+
all 150ms cubic-bezier(0.4, 0, 0.2, 1),
151+
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
152+
}
153+
154+
.card-shadow {
155+
box-shadow: var(--shadow-card);
156+
}
157+
158+
.tab-content {
159+
min-height: 200px;
160+
}
161+
</style>

packages/ui/src/components/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export { default as SimpleBadge } from './base/SimpleBadge.vue'
4040
export { default as Slider } from './base/Slider.vue'
4141
export { default as SmartClickable } from './base/SmartClickable.vue'
4242
export { default as StatItem } from './base/StatItem.vue'
43+
export { default as TabbedContent } from "./base/TabbedContent.vue"
4344
export { default as TagItem } from './base/TagItem.vue'
4445
export { default as TeleportDropdownMenu } from './base/TeleportDropdownMenu.vue'
4546
export { default as Timeline } from './base/Timeline.vue'
@@ -111,5 +112,4 @@ export { default as ThemeSelector } from './settings/ThemeSelector.vue'
111112

112113
// Servers
113114
export { default as BackupWarning } from './servers/backups/BackupWarning.vue'
114-
export { default as ServersSpecs } from './billing/ServersSpecs.vue'
115-
export { default as ScheduleTaskModal } from "./servers/scheduling/ScheduleTaskModal.vue"
115+
export { default as ServersSpecs } from './billing/ServersSpecs.vue'

0 commit comments

Comments
 (0)