feat: modrinth hosting - files tab refactor (#4912)

* feat: api-client module for content v0

* feat: delete unused components + modules + setting

* feat: xhr uploading

* feat: fs module -> api-client

* feat: migrate files.vue to use tanstack

* fix: mem leak + other issues

* fix: build

* feat: switch to monaco

* fix: go back to using ace, but improve preloading + theme

* fix: styling + dead attrs

* feat: match figma

* fix: padding

* feat: files-new for ui page structure

* feat: finalize files.vue

* fix: lint

* fix: qa

* fix: dep

* fix: lint

* fix: lockfile merge

* feat: icons on navtab

* fix: surface alternating on table

* fix: hover surface color

---------

Signed-off-by: Calum H. <contact@cal.engineer>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-01-06 00:35:51 +00:00
committed by GitHub
parent 61d4a34f0f
commit 099011a177
89 changed files with 5863 additions and 2091 deletions

View File

@@ -2,15 +2,17 @@ import type { InferredClientModules } from '../modules'
import { buildModuleStructure } from '../modules'
import type { ClientConfig } from '../types/client'
import type { RequestContext, RequestOptions } from '../types/request'
import type { UploadMetadata, UploadProgress, UploadRequestOptions } from '../types/upload'
import type { AbstractFeature } from './abstract-feature'
import type { AbstractModule } from './abstract-module'
import { AbstractUploadClient } from './abstract-upload-client'
import type { AbstractWebSocketClient } from './abstract-websocket'
import { ModrinthApiError, ModrinthServerError } from './errors'
/**
* Abstract base client for Modrinth APIs
*/
export abstract class AbstractModrinthClient {
export abstract class AbstractModrinthClient extends AbstractUploadClient {
protected config: ClientConfig
protected features: AbstractFeature[]
@@ -30,6 +32,7 @@ export abstract class AbstractModrinthClient {
public readonly iso3166!: InferredClientModules['iso3166']
constructor(config: ClientConfig) {
super()
this.config = {
timeout: 10000,
labrinthBaseUrl: 'https://api.modrinth.com',
@@ -176,6 +179,35 @@ export abstract class AbstractModrinthClient {
return next()
}
/**
* Execute the feature chain for an upload
*
* Similar to executeFeatureChain but calls executeXHRUpload at the end.
* This allows features (auth, retry, etc.) to wrap the upload execution.
*/
protected async executeUploadFeatureChain<T>(
context: RequestContext,
progressCallbacks: Array<(p: UploadProgress) => void>,
abortController: AbortController,
): Promise<T> {
const applicableFeatures = this.features.filter((feature) => feature.shouldApply(context))
let index = applicableFeatures.length
const next = async (): Promise<T> => {
index--
if (index >= 0) {
return applicableFeatures[index].execute(next, context)
} else {
await this.config.hooks?.onRequest?.(context)
return this.executeXHRUpload<T>(context, progressCallbacks, abortController)
}
}
return next()
}
/**
* Build the full URL for a request
*/
@@ -212,6 +244,36 @@ export abstract class AbstractModrinthClient {
}
}
/**
* Build context for an upload request
*
* Sets metadata.isUpload = true so features can detect uploads.
*/
protected buildUploadContext(
url: string,
path: string,
options: UploadRequestOptions,
): RequestContext {
const metadata: UploadMetadata = {
isUpload: true,
file: options.file,
onProgress: options.onProgress,
}
return {
url,
path,
options: {
...options,
method: 'POST',
body: options.file,
},
attempt: 1,
startTime: Date.now(),
metadata,
}
}
/**
* Build default headers for all requests
*
@@ -243,6 +305,23 @@ export abstract class AbstractModrinthClient {
*/
protected abstract executeRequest<T>(url: string, options: RequestOptions): Promise<T>
/**
* Execute the actual XHR upload
*
* This must be implemented by platform clients that support uploads.
* Called at the end of the upload feature chain.
*
* @param context - Request context with upload metadata
* @param progressCallbacks - Callbacks to invoke on progress events
* @param abortController - Controller for cancellation
* @returns Promise resolving to the response data
*/
protected abstract executeXHRUpload<T>(
context: RequestContext,
progressCallbacks: Array<(p: UploadProgress) => void>,
abortController: AbortController,
): Promise<T>
/**
* Normalize an error into a ModrinthApiError
*

View File

@@ -0,0 +1,21 @@
import type { UploadHandle, UploadRequestOptions } from '../types/upload'
/**
* Abstract base class defining upload capability
*
* All clients that support file uploads must extend this class.
* Platform-specific implementations should provide the actual upload mechanism
* (e.g., XHR for browser environments).
*
* Upload goes through the feature chain (auth, retry, circuit-breaker, etc.)
* just like regular requests.
*/
export abstract class AbstractUploadClient {
/**
* Upload a file with progress tracking
* @param path - API path (e.g., '/fs/create')
* @param options - Upload options including file, api, version
* @returns UploadHandle with promise, onProgress chain, and cancel method
*/
abstract upload<T = void>(path: string, options: UploadRequestOptions): UploadHandle<T>
}