Files
Modrinth-plus/packages/ui/.storybook/preview.ts
Calum H. 7d92e4ec7f feat: content tab rewrite for worlds (#5136)
* feat: base content card component

* fix: tooltips + colors

* feat: fix orgs

* feat: base content tab internals rewrite

* feat: fix invalidmodal

* feat: add ContentModpackCard

* fix: extract types

* draft: layout

* feat: unlink modal

* feat: impl content tab

* fix: lint

* fix: toggling

* temp: disable updating stuff

* feat: selection v-model

* feat: bulk selection

* feat: mods tab rough draft

* feat: use fuse.js

* feat: add project combobox

* clean up project combobox

* feat: start install to play modal

* fix: events

* feat: use v-on

* feat: bulk actions + fix floating action bar width

* feat: figma alignments

* feat: migrate toggle to tailwind

* fix: row borders

* feat: disabled state

* feat: virtual list impl for card table based on window scroll

* fix: lint

* feat: virtualization + smaller contentcard items

* feat: use ContentCardTable + ContentCardItems

* feat: fix gap + border issues on last elm

* feat: cleanup + use proper searching

* fix: use TeleportOverflowMenu

* fix: fallback to svg if src is invalid on avatar component

* fix: storybook

* feat: start on updater modal

* feat: finish content updater modal

* feat: i18n pass

* feat: impl modal

* feat(app): backend changes for content tab refactor (#5237)

* feat: include_changelog=false for updater modal

* fix: hash overrides

* feat: update checking for modpack

* feat: qa

* feat: modpack content modal

* fix: padding in table to match modals + tightness

* fix: lint

* feat: delete modal

* feat: fix toggle bugs

* fix: prepr

* fix: duplicate messages

* qa: full width search

* qa: use bg-surface-1.5

* qa: animation for filter pills

* qa: standardize hover colors

* fix: border-[1px] is border

* qa: mass de-select actually mass selecting

* qa: match figma designs for floating action bar

* qa: modal fixes

* q: modal fixes x2

* fix: table border

* qa: confirm modals

* qa: modal alignment

* qa: re-add stuck heading + dedupe logic

* qa: dedupe virtual scrolling + remove dead components

* qa: responsiveness for content table + link fixes

* qa: version column link, tooltips + lint fixes

* qa: instance busy protections

* fix: installation freeze bug

* chore: remove old mods page

* refactor: deduplicate layout

* chore: delete old content page(s)

* qa

* qa

* qa

* feat: sort btn - to iterate

* fix: ml

* feat: date added

* fix: lint

* fix: formatting.ts removal

* feat: get_dependencies_as_content_items

* qa: final QA changes

* refactor: deduplicate + polish content.rs

* feat: hook up content.vue with v1

* feat: hide v1 content api behind frontend feature flag

* fix: query keys + copy on empty state

* chore: i18n pass

* feat: reimpl unlink + upload endpoint

* feat: use bulk endpoints v1

* fix: lint

* fix: flags

* fix: responsiveness via container queries

* fix: lint

* qa: 1

* qa: fixes

* qa: fix ssr issues with browse content

* qa: header page divider

* qa: modals

* fix: prepr

* fix: issues

* fix: lint

* fix: toggle v1 ff

* qa: 5

* qa: delete modal copy

* feat: creation flow modals (#5383)

* refactor: delete content v0 usages + impl

* feat: qa + fixes

* feat: installing banner using state event

* feat: fix modpack card bugs + filtering issues

* refactor: delete backups v0 api module

* feat: v1 servers GET endpoint

* fix: backups

* feat: swap to kyros upload v1 addon

* fix: use tanstack for loader.vue

* feat: finish install from discovery modal

* qa: bug fixes

* feat: set up installation settings

* fix: lint

* fix: typos

* fix: bugs

* fix: disable inline content

* feat: content tab improvements — upload UX, installation settings, and client-only indicators

   Upload cancellation and navigation guard:
   - Add ConfirmLeaveModal that prompts when navigating away during upload
   - Cancel in-flight XHR uploads when user confirms leaving the page
   - Add beforeunload handler to warn on browser/tab close during upload
   - Track uploadedBytes/totalBytes in UploadState for progress display
   - Replace Collapsible with Transition for upload progress admonition
   - Show byte progress and percentage in upload banner
   - Clamp upload progress to prevent exceeding 100%

   Installation settings (server.properties):
   - Add KnownPropertiesFields and PropertiesFields types to Archon types
   - Add buildProperties() to creation flow context to collect gamemode,
     difficulty, seed, world type, structures, and generator settings
   - Pass properties through installContent on onboarding, discovery, and
     ServerSetupModal flows

   Server setup and discovery flow improvements:
   - Migrate ServerSetupModal from servers_v0.reinstall to content_v1.installContent
   - Replace loaderApiNames lookup with toApiLoader() helper
   - Remove eraseDataOnInstall toggle — always use soft_override: false
   - Simplify modpack install on discovery page to use first available version
     and route through creation flow modal for both onboarding and non-onboarding
   - Differentiate post-install navigation: content page for onboarding,
     loader options for existing servers

   Modpack update flow:
   - Replace updateModpack() call with installContent() using soft_override: true
     to support version selection in the content updater modal

   Client-only mod indicators:
   - Add environment field to AddonVersion (reuses Labrinth.Projects.v3.Environment)
   - Add environment to ContentItem and isClientOnly to ContentCardTableItem
   - Show orange TriangleAlertIcon with tooltip on client-only mods in content table
   - Add "Client-only" filter pill to content filtering (controlled via
     showClientOnlyFilter on ContentManagerContext)
   - Apply client-only indicators in both ContentPageLayout and ModpackContentModal

   Misc:
   - Add CLAUDE.md note about using prepr commands for lint checks
   - Export ConfirmLeaveModal from instances barrel

* fix: piping

* fix: switch content disable for linked server instances

* feat: client only filter

* fix: prepr

* feat: hasUpdate shape update

* feat: bulk update endpoint impl for content in panel

* feat: websocket state impl again with new phases

* fix: ws

* fix: use timeout fn for sync admon + fix content card layout scroll for browsers with overflow anchor bug

* fix: qa bugs

* fix: lint, a11y and i18n

* refactor: set up layouts folder properly

* fix: linked data cache stuff + lint

* feat: move installationsettings to shared layout

* fix: lint

* fix: issues

* feat: temp fuck staging up

* fix: lockfile

* fix: data sync issues on loader.vue

* fix: lint

* Hide shader configuration files from content list (#5499)

* feat: workaround search problem + split out reset

* fix: qa

* fix: changelog not showing on first open

* fix: qa + optimistic updating improvements

* fix: prepr+lint

* fix: qa

* feat: qa

* fix: lint

* fix: lint

* fix: build

* fix: build

* fix: type errors

* fix: fade and JAVA_HOME passthrough

* feat: qa

* feat: impl diff shit

* fix: qa

* fix: app qa

* feat: update diff modal

* fix: endpoint

* fix: qa

* fix: qa

* fix: use bulk in modpack modal

* feat: abort signal impl + fix issues

* fix: diff modal trunc

* feat: qa

* fix: qa

* feat: tooltip content tab

* fix: prepr

* fix: dismiss on settings btn

* feat: qa

* feat: dont clear handlers on disconnect

* fix: lint

* fix: wrangler + introduce staging-archon env file

---------

Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Artyom Ezri <61311568+Artezon@users.noreply.github.com>
2026-03-12 13:24:32 -07:00

211 lines
5.5 KiB
TypeScript

import '@modrinth/assets/omorphia.scss'
import 'floating-vue/dist/style.css'
import '../src/styles/tailwind.css'
import type { Labrinth } from '@modrinth/api-client'
import { GenericModrinthClient } from '@modrinth/api-client'
import { withThemeByClassName } from '@storybook/addon-themes'
import type { Preview } from '@storybook/vue3-vite'
import { setup } from '@storybook/vue3-vite'
import FloatingVue from 'floating-vue'
import { defineComponent, ref } from 'vue'
import { createI18n } from 'vue-i18n'
import NotificationPanel from '../src/components/nav/NotificationPanel.vue'
import PopupNotificationPanel from '../src/components/nav/PopupNotificationPanel.vue'
import {
buildLocaleMessages,
createMessageCompiler,
type CrowdinMessages,
} from '../src/composables/i18n'
import {
AbstractPopupNotificationManager,
AbstractWebNotificationManager,
I18N_INJECTION_KEY,
type I18nContext,
type NotificationPanelLocation,
type PopupNotification,
provideFilePicker,
provideModrinthClient,
provideNotificationManager,
providePopupNotificationManager,
provideTags,
type WebNotification,
} from '../src/providers'
// Load locale messages from the UI package's locales
// @ts-ignore
const localeModules = import.meta.glob('../src/locales/*/index.json', {
eager: true,
}) as Record<string, { default: CrowdinMessages }>
// Set up vue-i18n for Storybook - provides useVIntl() context for components
const i18n = createI18n({
legacy: false,
locale: 'en-US',
fallbackLocale: 'en-US',
messageCompiler: createMessageCompiler(),
missingWarn: false,
fallbackWarn: false,
messages: buildLocaleMessages(localeModules),
})
class StorybookNotificationManager extends AbstractWebNotificationManager {
private readonly state = ref<WebNotification[]>([])
private readonly locationState = ref<NotificationPanelLocation>('right')
public getNotificationLocation(): NotificationPanelLocation {
return this.locationState.value
}
public setNotificationLocation(location: NotificationPanelLocation): void {
this.locationState.value = location
}
public getNotifications(): WebNotification[] {
return this.state.value
}
protected addNotificationToStorage(notification: WebNotification): void {
this.state.value.push(notification)
}
protected removeNotificationFromStorage(id: string | number): void {
const index = this.state.value.findIndex((n) => n.id === id)
if (index > -1) {
this.state.value.splice(index, 1)
}
}
protected removeNotificationFromStorageByIndex(index: number): void {
this.state.value.splice(index, 1)
}
protected clearAllNotificationsFromStorage(): void {
this.state.value.splice(0)
}
}
class StorybookPopupNotificationManager extends AbstractPopupNotificationManager {
private readonly state = ref<PopupNotification[]>([])
public getNotifications(): PopupNotification[] {
return this.state.value
}
protected addNotificationToStorage(notification: PopupNotification): void {
this.state.value.push(notification)
}
protected removeNotificationFromStorage(id: string | number): void {
const index = this.state.value.findIndex((n) => n.id === id)
if (index > -1) {
this.state.value.splice(index, 1)
}
}
protected clearAllNotificationsFromStorage(): void {
this.state.value.splice(0)
}
}
setup((app) => {
app.use(i18n)
// Provide the custom I18nContext for components using injectI18n()
const i18nContext: I18nContext = {
locale: i18n.global.locale,
t: (key, values) => i18n.global.t(key, values ?? {}) as string,
setLocale: (newLocale) => {
i18n.global.locale.value = newLocale
},
}
app.provide(I18N_INJECTION_KEY, i18nContext)
app.use(FloatingVue, {
themes: {
'ribbit-popout': {
$extend: 'dropdown',
placement: 'bottom-end',
instantMove: true,
distance: 8,
},
'dismissable-prompt': {
$extend: 'dropdown',
placement: 'bottom-start',
},
},
})
// Create teleport target for components that use <Teleport to="#teleports">
if (typeof document !== 'undefined' && !document.getElementById('teleports')) {
const teleportTarget = document.createElement('div')
teleportTarget.id = 'teleports'
document.body.appendChild(teleportTarget)
}
})
const StorybookProvider = defineComponent({
setup(_, { slots }) {
provideNotificationManager(new StorybookNotificationManager())
providePopupNotificationManager(new StorybookPopupNotificationManager())
const modrinthClient = new GenericModrinthClient({
userAgent: 'modrinth-storybook/1.0.0',
})
provideModrinthClient(modrinthClient)
const gameVersions = ref<Labrinth.Tags.v2.GameVersion[]>([])
const loaders = ref<Labrinth.Tags.v2.Loader[]>([])
modrinthClient.labrinth.state.build().then((state) => {
gameVersions.value = state.gameVersions
loaders.value = state.loaders
})
provideTags({ gameVersions, loaders })
provideFilePicker({
async pickImage() {
return null
},
async pickModpackFile() {
return null
},
})
return () => slots.default?.()
},
})
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
decorators: [
withThemeByClassName({
themes: {
light: 'light-mode',
dark: 'dark-mode',
oled: 'oled-mode',
},
defaultTheme: 'dark',
}),
(story) => ({
components: { story, StorybookProvider, NotificationPanel, PopupNotificationPanel },
template: /*html*/ `
<StorybookProvider>
<NotificationPanel />
<PopupNotificationPanel />
<story />
</StorybookProvider>
`,
}),
],
}
export default preview