world-manager/World Manager for Minecraft/UI/Preview/PreviewFixtures.swift
2026-06-01 20:50:52 -05:00

374 lines
15 KiB
Swift

// SPDX-FileCopyrightText: 2026 John Burwell and contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
import Foundation
import SwiftUI
#if DEBUG
enum PreviewFixtures {
static let baseDate = Date(timeIntervalSinceReferenceDate: 770_000_000)
static let sourceOneURL = URL(fileURLWithPath: "/tmp/preview-library-1")
static let sourceTwoURL = URL(fileURLWithPath: "/tmp/preview-library-2")
static let worldCollectionURL = sourceOneURL.appendingPathComponent(MinecraftContentType.world.collectionFolderName)
static let behaviorCollectionURL = sourceOneURL.appendingPathComponent(MinecraftContentType.behaviorPack.collectionFolderName)
static let resourceCollectionURL = sourceOneURL.appendingPathComponent(MinecraftContentType.resourcePack.collectionFolderName)
static let behaviorPackIdentity = PackIdentity(
type: .behaviorPack,
uuid: "874117c4-3f9c-432e-bd64-da7336313337",
version: "3.0.0",
fallbackName: "Wither Storm Behavior",
fallbackLocationHint: behaviorCollectionURL.lastPathComponent
)
static let resourcePackIdentity = PackIdentity(
type: .resourcePack,
uuid: "823cbe13-eac7-49a4-b9cd-1dd25ab1048a",
version: "3.0.0",
fallbackName: "Wither Storm Resources",
fallbackLocationHint: resourceCollectionURL.lastPathComponent
)
static let behaviorPackReference = ContentPackReference(
name: "§5Cracker's Wither Storm Mod",
type: .behaviorPack,
uuid: behaviorPackIdentity.uuid,
version: behaviorPackIdentity.version,
source: .referencedByWorld
)
static let resourcePackReference = ContentPackReference(
name: "§5Cracker's Wither Storm Mod",
type: .resourcePack,
uuid: resourcePackIdentity.uuid,
version: resourcePackIdentity.version,
source: .referencedByWorld
)
static let featuredWorld = MinecraftContentItem(
folderURL: worldCollectionURL.appendingPathComponent("world-alpha"),
folderName: "world-alpha",
contentType: .world,
collectionRootURL: worldCollectionURL,
displayName: "THE ABSOLUTELY ENORMOUS KID CHAOS LAB WITH 9999 TNT AND A SECRET LLAMA BUNKER v2.0",
lastPlayedDate: baseDate.addingTimeInterval(-86_400 * 3),
modifiedDate: baseDate.addingTimeInterval(-86_400 * 2),
sizeBytes: 35_500_000,
packReferences: [behaviorPackReference, resourcePackReference],
metadataLoaded: true,
sizeLoaded: true
)
static let siblingWorld = MinecraftContentItem(
folderURL: worldCollectionURL.appendingPathComponent("world-beta"),
folderName: "world-beta",
contentType: .world,
collectionRootURL: worldCollectionURL,
displayName: "Sky Battle Arena",
modifiedDate: baseDate.addingTimeInterval(-86_400 * 14),
sizeBytes: 14_200_000,
packReferences: [resourcePackReference],
metadataLoaded: true,
sizeLoaded: true
)
static let archiveWorld = MinecraftContentItem(
folderURL: worldCollectionURL.appendingPathComponent("world-gamma"),
folderName: "world-gamma",
contentType: .world,
collectionRootURL: worldCollectionURL,
displayName: "Grandma's Survival Backup But Everyone Has Netherite",
modifiedDate: baseDate.addingTimeInterval(-86_400 * 60),
sizeBytes: 227_500_000,
metadataLoaded: true,
sizeLoaded: true
)
static let behaviorPackItem = MinecraftContentItem(
folderURL: behaviorCollectionURL.appendingPathComponent("wither-storm-bp"),
folderName: "wither-storm-bp",
contentType: .behaviorPack,
collectionRootURL: behaviorCollectionURL,
displayName: "§5Cracker's Wither Storm Mod",
modifiedDate: baseDate.addingTimeInterval(-86_400 * 6),
sizeBytes: 5_300_000,
packUUID: behaviorPackIdentity.uuid,
packVersion: behaviorPackIdentity.version,
metadataLoaded: true,
sizeLoaded: true
)
static let resourcePackItem = MinecraftContentItem(
folderURL: resourceCollectionURL.appendingPathComponent("wither-storm-rp"),
folderName: "wither-storm-rp",
contentType: .resourcePack,
collectionRootURL: resourceCollectionURL,
displayName: "§5Cracker's Wither Storm Mod",
modifiedDate: baseDate.addingTimeInterval(-86_400 * 6),
sizeBytes: 8_900_000,
packUUID: resourcePackIdentity.uuid,
packVersion: resourcePackIdentity.version,
metadataLoaded: true,
sizeLoaded: true
)
static let secondLibraryPack = MinecraftContentItem(
folderURL: sourceTwoURL
.appendingPathComponent(MinecraftContentType.resourcePack.collectionFolderName)
.appendingPathComponent("cartoon-rp"),
folderName: "cartoon-rp",
contentType: .resourcePack,
collectionRootURL: sourceTwoURL.appendingPathComponent(MinecraftContentType.resourcePack.collectionFolderName),
displayName: "Cartoon Blocks Remix",
modifiedDate: baseDate.addingTimeInterval(-86_400 * 11),
sizeBytes: 12_700_000,
packUUID: "246df391-7dd1-4df6-b7aa-308a94a85d81",
packVersion: "1.4.2",
metadataLoaded: true,
sizeLoaded: true
)
static let primarySource: MinecraftSource = {
var source = MinecraftSource(folderURL: sourceOneURL)
source.displayName = "Kid iPad Imports"
source.displayItems = [
featuredWorld,
siblingWorld,
archiveWorld,
behaviorPackItem,
resourcePackItem
]
source.displayItemCountsByType = source.displayItems.reduce(into: [MinecraftContentType: Int]()) { counts, item in
counts[item.contentType, default: 0] += 1
}
source.displayItemCountsByKind = source.displayItems.reduce(into: [MinecraftContentKind: Int]()) { counts, item in
counts[item.contentKind, default: 0] += 1
}
source.rawItems = source.displayItems
source.logicalPacks = [
LogicalPack(
id: behaviorPackIdentity,
contentType: .behaviorPack,
displayName: behaviorPackItem.displayName,
uuid: behaviorPackItem.packUUID,
version: behaviorPackItem.packVersion,
representativeItemID: behaviorPackItem.id,
instanceItemIDs: [behaviorPackItem.id],
isSuspicious: false
),
LogicalPack(
id: resourcePackIdentity,
contentType: .resourcePack,
displayName: resourcePackItem.displayName,
uuid: resourcePackItem.packUUID,
version: resourcePackItem.packVersion,
representativeItemID: resourcePackItem.id,
instanceItemIDs: [resourcePackItem.id],
isSuspicious: false
)
]
source.logicalWorlds = [
LogicalWorld(
id: featuredWorld.id,
itemID: featuredWorld.id,
usedPackIDs: [behaviorPackIdentity, resourcePackIdentity],
unresolvedReferences: []
),
LogicalWorld(
id: siblingWorld.id,
itemID: siblingWorld.id,
usedPackIDs: [resourcePackIdentity],
unresolvedReferences: []
)
]
source.packInstances = [
PackInstance(
id: behaviorPackItem.id,
itemID: behaviorPackItem.id,
sourceID: source.id,
logicalPackID: behaviorPackIdentity,
origin: .foundInCollection,
hostWorldItemID: nil
),
PackInstance(
id: resourcePackItem.id,
itemID: resourcePackItem.id,
sourceID: source.id,
logicalPackID: resourcePackIdentity,
origin: .foundInCollection,
hostWorldItemID: nil
)
]
source.worldPackRelationships = [
WorldPackRelationship(
worldItemID: featuredWorld.id,
logicalPackID: behaviorPackIdentity,
reference: behaviorPackReference
),
WorldPackRelationship(
worldItemID: featuredWorld.id,
logicalPackID: resourcePackIdentity,
reference: resourcePackReference
),
WorldPackRelationship(
worldItemID: siblingWorld.id,
logicalPackID: resourcePackIdentity,
reference: resourcePackReference
)
]
source.indexedItemCount = source.displayItems.count
source.indexedDetailCount = source.displayItems.count
source.lastScanDate = baseDate
return source
}()
static let secondarySource: MinecraftSource = {
var source = MinecraftSource(folderURL: sourceTwoURL)
source.displayName = "Downloads"
source.displayItems = [secondLibraryPack]
source.displayItemCountsByType = source.displayItems.reduce(into: [MinecraftContentType: Int]()) { counts, item in
counts[item.contentType, default: 0] += 1
}
source.displayItemCountsByKind = source.displayItems.reduce(into: [MinecraftContentKind: Int]()) { counts, item in
counts[item.contentKind, default: 0] += 1
}
source.rawItems = source.displayItems
source.indexedItemCount = source.displayItems.count
source.indexedDetailCount = source.displayItems.count
source.lastScanDate = baseDate.addingTimeInterval(-3_600)
return source
}()
static let allSources = [primarySource, secondarySource]
static let directoryEntries = [
DirectoryEntry(name: "db", isDirectory: true),
DirectoryEntry(name: "level.dat", isDirectory: false),
DirectoryEntry(name: "levelname.txt", isDirectory: false),
DirectoryEntry(name: "world_icon.jpeg", isDirectory: false),
DirectoryEntry(name: "resource_packs", isDirectory: true)
]
}
@MainActor
struct SidebarColumnPreviewContainer: View {
@State private var selection: SidebarSelection? = .allContent(sourceID: PreviewFixtures.primarySource.id)
var body: some View {
NavigationStack {
SourcesSidebarView(
sources: PreviewFixtures.allSources,
connectedDevices: [],
selection: $selection,
addSourceAction: {},
addDeviceSourceAction: {},
addConnectedDeviceAction: { _ in },
rescanSourceAction: { _ in },
removeSourceAction: { _ in },
filters: { source in
let allFilter = SidebarFilter(
title: "All Content",
iconName: "square.stack.3d.up",
count: source.displayItems.count,
selection: .allContent(sourceID: source.id)
)
let groupedFilters = MinecraftContentType.allCases.compactMap { contentType -> SidebarFilter? in
let count = source.displayItems.filter { $0.contentType == contentType }.count
guard count > 0 else {
return nil
}
let iconName: String
switch contentType {
case .world:
iconName = "globe.europe.africa"
case .behaviorPack:
iconName = "shippingbox"
case .resourcePack:
iconName = "paintpalette"
case .skinPack:
iconName = "person.crop.square"
case .worldTemplate:
iconName = "doc.on.doc"
}
return SidebarFilter(
title: contentType.rawValue,
iconName: iconName,
count: count,
selection: .contentType(sourceID: source.id, contentType: contentType)
)
}
return [allFilter] + groupedFilters
}
)
}
}
}
struct ItemListColumnPreviewContainer: View {
@State private var isDropTargeted = false
@State private var selectedItemID: MinecraftContentItem.ID? = PreviewFixtures.featuredWorld.id
@State private var searchText = ""
@State private var sortMode: ItemSortMode = .modifiedDate
var body: some View {
NavigationStack {
ItemListColumnView(
isEmpty: false,
isDropTargeted: $isDropTargeted,
selectedItemID: $selectedItemID,
searchText: $searchText,
sortMode: $sortMode,
showsHeader: true,
sourceName: PreviewFixtures.primarySource.displayName,
showsSourceName: false,
title: "All Items",
subtitle: "5 items in Kid iPad Imports",
showsSubtitle: false,
isRefreshing: false,
showsProjectionLoadingState: false,
items: PreviewFixtures.primarySource.displayItems,
searchPrompt: "Search Worlds",
chooseFolderAction: {},
dropAction: { _ in false },
itemContextMenu: { item in
Button("Reveal \(item.displayName)") {}
}
)
}
}
}
struct ItemDetailColumnPreviewContainer: View {
var body: some View {
NavigationStack {
ItemDetailColumnView(
item: PreviewFixtures.featuredWorld,
source: PreviewFixtures.primarySource,
showsSourceDetails: false,
behaviorPacks: PreviewFixtures.primarySource.resolvedPackReferences(for: PreviewFixtures.featuredWorld.id, type: .behaviorPack),
resourcePacks: PreviewFixtures.primarySource.resolvedPackReferences(for: PreviewFixtures.featuredWorld.id, type: .resourcePack),
worldsUsingPack: [],
backingPackInstances: [],
isSuspiciousPack: false,
contents: PreviewFixtures.directoryEntries,
directoryPreviewLimit: 12,
isEmpty: false,
isPerformingItemAction: false,
areFileActionsEnabled: true,
exportTitle: PreviewFixtures.featuredWorld.contentType.exportTitle,
exportAction: {},
revealAction: {},
shareAction: { _ in }
)
}
}
}
#endif