374 lines
15 KiB
Swift
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
|