Skip to content
Open
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
16 changes: 6 additions & 10 deletions apps/app-frontend/src/pages/Browse.vue
Original file line number Diff line number Diff line change
Expand Up @@ -909,17 +909,18 @@ function getCardActions(
const isQueued = queuedServerInstallProjectIds.value.has(projectResult.project_id)
const isQueuedRoot = queuedServerInstallRootProjectIds.value.has(projectResult.project_id)
const isInstallingSelection = isInstallingQueuedServerInstalls.value
const showAsInstalling = isInstalling || (isInstallingSelection && isQueuedRoot)
const validatingInstall =
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
const installLabel = showAsInstalled
? commonMessages.installedLabel
: isQueued
? isInstalling || isInstallingSelection
? showAsInstalling
? validatingInstall
? commonMessages.validatingLabel
: messages.installingToServer
: commonMessages.selectedLabel
: isInstalling || isInstallingSelection
: showAsInstalling
? validatingInstall
? commonMessages.validatingLabel
: messages.installingToServer
Expand All @@ -928,16 +929,11 @@ function getCardActions(
{
key: 'install',
label: formatMessage(installLabel),
icon:
isInstalling || isInstallingSelection
? SpinnerIcon
: isQueued || showAsInstalled
? CheckIcon
: PlusIcon,
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
icon: showAsInstalling ? SpinnerIcon : isQueued || showAsInstalled ? CheckIcon : PlusIcon,
iconClass: showAsInstalling ? 'animate-spin' : undefined,
disabled:
showAsInstalled || isInstalling || isInstallingSelection || (isQueued && !isQueuedRoot),
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
color: isQueued && !showAsInstalling ? 'green' : 'brand',
type: 'outlined',
onClick: async () => {
if (isQueuedRoot) {
Expand Down
16 changes: 6 additions & 10 deletions apps/frontend/src/pages/discover/[type]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,18 @@ function getCardActions(
serverData.value.upstream?.project_id === result.project_id
const isInstalling = installingProjectIds.value.has(result.project_id)
const isInstallingSelection = isInstallingQueuedServerInstalls.value
const showAsInstalling = isInstalling || (isInstallingSelection && isQueuedRoot)
const validatingInstall =
isInstalling && currentProjectType !== 'modpack' && !isInstallingSelection
const installLabel = isInstalled
? formatMessage(commonMessages.installedLabel)
: isQueued
? isInstalling || isInstallingSelection
? showAsInstalling
? validatingInstall
? formatMessage(commonMessages.validatingLabel)
: formatMessage(commonMessages.installingLabel)
: formatMessage(commonMessages.selectedLabel)
: isInstalling || isInstallingSelection
: showAsInstalling
? validatingInstall
? formatMessage(commonMessages.validatingLabel)
: formatMessage(commonMessages.installingLabel)
Expand All @@ -381,16 +382,11 @@ function getCardActions(
{
key: 'install',
label: installLabel,
icon:
isInstalling || isInstallingSelection
? SpinnerIcon
: isQueued || isInstalled
? CheckIcon
: DownloadIcon,
iconClass: isInstalling || isInstallingSelection ? 'animate-spin' : undefined,
icon: showAsInstalling ? SpinnerIcon : isQueued || isInstalled ? CheckIcon : DownloadIcon,
iconClass: showAsInstalling ? 'animate-spin' : undefined,
disabled:
!!isInstalled || isInstalling || isInstallingSelection || (isQueued && !isQueuedRoot),
color: isQueued && !isInstalling && !isInstallingSelection ? 'green' : 'brand',
color: isQueued && !showAsInstalling ? 'green' : 'brand',
type: 'outlined',
onClick: () => serverInstall(projectResult),
},
Expand Down
51 changes: 51 additions & 0 deletions packages/api-client/src/modules/archon/content/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,57 @@ export class ArchonContentV1Module extends AbstractModule {
})
}

/** POST /v1/:server_id/worlds/:world_id/addons/set-enabled-server */
public async setAddonEnabledServer(
serverId: string,
worldId: string,
request: Archon.Content.v1.SetAddonEnabledRequest,
): Promise<void> {
await this.client.request<void>(
`/servers/${serverId}/worlds/${worldId}/addons/set-enabled-server`,
{
api: 'archon',
version: 1,
method: 'POST',
body: request,
},
)
}

/** POST /v1/:server_id/worlds/:world_id/addons/set-enabled-player */
public async setAddonEnabledPlayer(
serverId: string,
worldId: string,
request: Archon.Content.v1.SetAddonEnabledRequest,
): Promise<void> {
await this.client.request<void>(
`/servers/${serverId}/worlds/${worldId}/addons/set-enabled-player`,
{
api: 'archon',
version: 1,
method: 'POST',
body: request,
},
)
}

/** POST /v1/:server_id/worlds/:world_id/addons/set-locked-side-toggle */
public async setAddonSideToggleLocked(
serverId: string,
worldId: string,
request: Archon.Content.v1.SetAddonSideToggleLockedRequest,
): Promise<void> {
await this.client.request<void>(
`/servers/${serverId}/worlds/${worldId}/addons/set-locked-side-toggle`,
{
api: 'archon',
version: 1,
method: 'POST',
body: request,
},
)
}

/** POST /v1/:server_id/worlds/:world_id/addons/delete-many */
public async deleteAddons(
serverId: string,
Expand Down
32 changes: 32 additions & 0 deletions packages/api-client/src/modules/archon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,25 @@ export namespace Archon {
environment?: Labrinth.Projects.v3.Environment | null
}

export type AddonManifestEnvironment =
| 'client_and_server'
| 'client_only'
| 'dedicated_server_only'

export type AddonManifestWarnings = {
multiple_mod_entries: number | null
malformed: boolean
}

export type AddonManifest = {
platform: Modloader
name: string | null
version: string | null
environment: AddonManifestEnvironment | null
icon_embedded: boolean
warnings: AddonManifestWarnings
}

export type AddonStatus =
| 'pending'
| 'installed'
Expand All @@ -316,6 +335,10 @@ export namespace Archon {
filesize: number
btime?: string
disabled: boolean
disabled_server: boolean
disabled_player: boolean
side_toggle_unlocked: boolean
manifest: AddonManifest | null
kind: AddonKind
from_modpack: boolean
status: AddonStatus
Expand Down Expand Up @@ -354,6 +377,14 @@ export namespace Archon {
filename: string
}

export type SetAddonEnabledRequest = RemoveAddonRequest & {
enabled: boolean
}

export type SetAddonSideToggleLockedRequest = RemoveAddonRequest & {
locked: boolean
}

export type UpdateAddonRequest = {
filename: string
version_id?: string | null
Expand Down Expand Up @@ -1063,6 +1094,7 @@ export namespace Archon {
project_id: string | null
pack_client_retained: boolean
pack_client_depends: boolean
manifest?: Archon.Content.v1.AddonManifest | null
status: Archon.Content.v1.AddonStatus
filesize: number | null
name: string | null
Expand Down
18 changes: 18 additions & 0 deletions packages/api-client/src/modules/kyros/content/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ export class KyrosContentV1Module extends AbstractModule {
return 'kyros_content_v1'
}

/** GET /v1/worlds/:world_id/content/embedded-icon */
public async getEmbeddedAddonIcon(
worldId: string,
parentDirectory: 'mods' | 'plugins',
filename: string,
): Promise<Blob> {
return this.client.request<Blob>(`/worlds/${worldId}/content/embedded-icon`, {
api: '',
version: 'v1',
method: 'GET',
params: {
parent_directory: parentDirectory,
filename,
},
useNodeAuth: true,
})
}

/**
* Upload addon files to a world via multipart form data
*
Expand Down
28 changes: 18 additions & 10 deletions packages/ui/src/components/base/Toggle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,37 @@
:id="id"
type="button"
role="switch"
:aria-checked="modelValue"
:aria-checked="indeterminate ? 'mixed' : modelValue"
:disabled="disabled"
class="group inline-flex shrink-0 touch-manipulation items-center rounded-full m-0 p-1 transition-all duration-200 cursor-pointer border border-solid border-surface-5"
:class="[
small ? 'h-5 !w-[40px]' : 'h-6 !w-[48px]',
modelValue ? 'bg-brand' : 'bg-button-bg',
indeterminate || modelValue ? 'bg-brand' : 'bg-button-bg',
disabled ? 'opacity-50 cursor-not-allowed' : '',
]"
@click="toggle"
>
<span
class="rounded-full transition-all duration-200"
:class="[
small ? 'w-3 h-3' : 'w-4 h-4',
modelValue
indeterminate
? small
? 'translate-x-[20px] bg-black/90'
: 'translate-x-[24px] bg-black/90'
: 'bg-secondary',
? 'h-0.5 w-2.5 translate-x-[11px] bg-black/90'
: 'h-0.5 w-3 translate-x-[14px] bg-black/90'
: modelValue
? small
? 'h-3 w-3 translate-x-[20px] bg-black/90'
: 'h-4 w-4 translate-x-[24px] bg-black/90'
: small
? 'h-3 w-3 bg-gray'
: 'h-4 w-4 bg-gray',
disabled
? ''
: small
? 'group-hover:w-[14px] group-hover:h-[14px] group-hover:m-[-1px] group-active:w-[10px] group-active:h-[10px] group-active:m-[1px]'
: 'group-hover:w-[18px] group-hover:h-[18px] group-hover:m-[-1px] group-active:w-[14px] group-active:h-[14px] group-active:m-[1px]',
: indeterminate
? 'group-hover:scale-x-110 group-active:scale-x-75'
: small
? 'group-hover:w-[14px] group-hover:h-[14px] group-hover:m-[-1px] group-active:w-[10px] group-active:h-[10px] group-active:m-[1px]'
: 'group-hover:w-[18px] group-hover:h-[18px] group-hover:m-[-1px] group-active:w-[14px] group-active:h-[14px] group-active:m-[1px]',
]"
/>
</button>
Expand All @@ -37,6 +44,7 @@ const props = defineProps<{
id?: string
disabled?: boolean
small?: boolean
indeterminate?: boolean
}>()

const modelValue = defineModel<boolean>()
Expand Down
51 changes: 43 additions & 8 deletions packages/ui/src/composables/server-panel-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,17 +233,48 @@ export function useServerPanelSync(options: UseServerPanelSyncOptions) {
}

const content = worldContentUpdateToAddons(event)
queryClient.setQueryData<Archon.Content.v1.Addons>(contentListKey(serverId), {
queryClient.setQueryData<Archon.Content.v1.Addons>(contentListKey(serverId), (current) => ({
...content,
addons: content.addons?.filter((addon) => !addon.from_modpack) ?? null,
})
queryClient.setQueryData<Archon.Content.v1.Addons>(modpackContentListKey(serverId), {
...content,
addons: content.addons?.filter((addon) => addon.from_modpack) ?? null,
})
addons: mergeWorldContentSideState(
current?.addons ?? [],
content.addons?.filter((addon) => !addon.from_modpack) ?? [],
),
}))
queryClient.setQueryData<Archon.Content.v1.Addons>(
modpackContentListKey(serverId),
(current) => ({
...content,
addons: mergeWorldContentSideState(
current?.addons ?? [],
content.addons?.filter((addon) => addon.from_modpack) ?? [],
),
}),
)
void queryClient.invalidateQueries({ queryKey: contentListKey(serverId) })
void queryClient.invalidateQueries({ queryKey: modpackContentListKey(serverId) })
void queryClient.invalidateQueries({ queryKey: serverV1DetailKey(serverId) })
}

function mergeWorldContentSideState(
currentAddons: Archon.Content.v1.Addon[],
incomingAddons: Archon.Content.v1.Addon[],
) {
const currentByFilename = new Map(
currentAddons.map((addon) => [normalizeAddonFilename(addon.filename), addon] as const),
)
return incomingAddons.map((incoming) => {
const current = currentByFilename.get(normalizeAddonFilename(incoming.filename))
return current
? {
...incoming,
disabled_server: current.disabled_server,
disabled_player: current.disabled_player,
side_toggle_unlocked: current.side_toggle_unlocked,
}
: incoming
})
}

function worldContentUpdateToAddons(
event: Archon.Sync.v1.WorldContentUpdateEvent,
): Archon.Content.v1.Addons {
Expand Down Expand Up @@ -297,7 +328,11 @@ export function useServerPanelSync(options: UseServerPanelSyncOptions) {
filename: item.filename,
filesize: item.filesize ?? 0,
btime: item.btime,
disabled: item.filename.endsWith('.disabled'),
disabled: false,
disabled_server: false,
disabled_player: false,
side_toggle_unlocked: false,
manifest: item.manifest ?? null,
kind: parentDirectoryToAddonKind(item.parent_directory),
from_modpack: item.from_modpack,
status: item.status,
Expand Down
13 changes: 8 additions & 5 deletions packages/ui/src/composables/virtual-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface ScrollViewportOptions {
}

export interface VirtualScrollOptions {
itemHeight: number
itemHeight: number | Ref<number>
bufferSize?: number
initialItemCount?: number
enabled?: Ref<boolean>
Expand Down Expand Up @@ -140,6 +140,9 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
onNearEnd,
nearEndThreshold = 0.2,
} = options
const resolvedItemHeight = computed(() =>
typeof itemHeight === 'number' ? itemHeight : itemHeight.value,
)

const {
listContainer,
Expand All @@ -152,7 +155,7 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
onScroll: checkNearEnd,
})

const totalHeight = computed(() => items.value.length * itemHeight)
const totalHeight = computed(() => items.value.length * resolvedItemHeight.value)

const visibleRange = computed(() => {
if (enabled && !enabled.value) {
Expand All @@ -163,8 +166,8 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
return { start: 0, end: Math.min(items.value.length, initialItemCount) }
}

const start = Math.floor(relativeScrollTop.value / itemHeight)
const visibleCount = Math.ceil(viewportHeight.value / itemHeight)
const start = Math.floor(relativeScrollTop.value / resolvedItemHeight.value)
const visibleCount = Math.ceil(viewportHeight.value / resolvedItemHeight.value)
const rangeSize = visibleCount + bufferSize * 2

const rangeStart = Math.min(
Expand All @@ -180,7 +183,7 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
})

const visibleTop = computed(() =>
enabled && !enabled.value ? 0 : visibleRange.value.start * itemHeight,
enabled && !enabled.value ? 0 : visibleRange.value.start * resolvedItemHeight.value,
)

const visibleItems = computed(() =>
Expand Down
Loading
Loading