Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89c39af14e | ||
|
|
f7953308b2 | ||
|
|
6100b9ebac | ||
|
|
ce7bdfde2e | ||
|
|
ee68ee5fcf | ||
|
|
d12292e6af | ||
|
|
33c9937547 | ||
|
|
ae6197d855 | ||
|
|
21614116fc | ||
|
|
85259446b1 | ||
|
|
2e4dfa48cd | ||
|
|
844c4d7587 | ||
|
|
1311f3127e | ||
|
|
a53bb8870b | ||
|
|
38a202109d | ||
|
|
9c1bfe2024 |
No files matched your search
+8
@@ -10,5 +10,13 @@
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>2C96045430463C2E0003A2A8</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// AppRouter.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class AppRouter {
|
||||
var selectedTab: AppTab = .home
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum AppTab: Hashable {
|
||||
case home
|
||||
case worldsList
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// CreateWorldButton.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 02/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CreateWorldButton : View {
|
||||
var body: some View {
|
||||
NavigationLink{
|
||||
CreateWorldView()
|
||||
} label : {
|
||||
Label("Créer un monde", systemImage: "plus")
|
||||
.font(.headline)
|
||||
.padding(.vertical, 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.frame(maxWidth: 300)
|
||||
.buttonStyle(.glassProminent)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// MainTabView.swift
|
||||
//
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct MainTabView: View {
|
||||
var body : some View {
|
||||
TabView {
|
||||
NavigationStack {
|
||||
HomeView()
|
||||
}.tabItem {
|
||||
Label("Accueil", systemImage: "house")
|
||||
}
|
||||
|
||||
NavigationStack {
|
||||
WorldListView()
|
||||
}.tabItem {
|
||||
Label("Mes mondes", systemImage: "globe.europe.africa")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// WorldCardView.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 03/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WorldCard : View {
|
||||
var world : World
|
||||
var isSelected = false
|
||||
|
||||
var body : some View {
|
||||
ZStack(alignment: .bottom) {
|
||||
LinearGradient(
|
||||
colors: [.clear, .black.opacity(0.5)],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
|
||||
HStack(alignment: .bottom) {
|
||||
Text(world.name)
|
||||
.foregroundStyle(.white)
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 28))
|
||||
.foregroundStyle(.white, .indigo)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
.padding(.horizontal, 6)
|
||||
|
||||
}
|
||||
.frame(height: 250)
|
||||
.background {
|
||||
if let dataImage = world.illustrationData,
|
||||
let uiImage = UIImage(data: dataImage) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
}else {
|
||||
Image(systemName: "globe.europe.africa")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
}
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 20))
|
||||
.contentShape(RoundedRectangle(cornerRadius: 20))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// MainTabView.swift
|
||||
//
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct MainTabView: View {
|
||||
@Environment(AppRouter.self) private var router
|
||||
|
||||
var body : some View {
|
||||
@Bindable var router = router
|
||||
|
||||
TabView(selection: $router.selectedTab) {
|
||||
NavigationStack {
|
||||
HomeView()
|
||||
}
|
||||
.tabItem {Label("Accueil", systemImage: "house")}
|
||||
.tag(AppTab.home)
|
||||
|
||||
NavigationStack {
|
||||
WorldListView()
|
||||
}
|
||||
.tabItem {Label("Mes mondes", systemImage: "globe.europe.africa")}
|
||||
.tag(AppTab.worldsList)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// AttributeDefinition.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 06/09/2026.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
class AttributeDefinition {
|
||||
var id: UUID
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
|
||||
var name : String
|
||||
var type : AttributeType
|
||||
|
||||
init(name: String, type: AttributeType){
|
||||
self.id = UUID()
|
||||
self.createdAt = Date()
|
||||
self.updatedAt = Date()
|
||||
self.name = name
|
||||
self.type = type
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// AttributeType.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 06/09/2026.
|
||||
//
|
||||
|
||||
enum AttributeType : nonisolated Codable {
|
||||
case string
|
||||
case int
|
||||
case double
|
||||
case bool
|
||||
case date
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Entity.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 05/09/2026.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
class Entity {
|
||||
var id : UUID
|
||||
var createdAt : Date
|
||||
var updatedAt : Date
|
||||
|
||||
var name : String
|
||||
var world : World
|
||||
var worldEntityType : WorldEntityType
|
||||
@Relationship var enabledLocalAttributeDefinitions = [AttributeDefinition]()
|
||||
@Relationship var personalAttributeDefinitions = [AttributeDefinition]()
|
||||
|
||||
|
||||
|
||||
init(name: String, world: World, worldEntityType: WorldEntityType){
|
||||
self.id = UUID()
|
||||
self.createdAt = Date()
|
||||
self.updatedAt = Date()
|
||||
self.name = name
|
||||
self.world = world
|
||||
self.worldEntityType = worldEntityType
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// EntityType.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 05/09/2026.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
class EntityType {
|
||||
var id : UUID
|
||||
var createdAt : Date
|
||||
var updatedAt : Date
|
||||
|
||||
var name : String
|
||||
@Relationship var globalAttributeDefinitions = [AttributeDefinition]()
|
||||
|
||||
|
||||
init(name: String) {
|
||||
self.id = UUID()
|
||||
self.createdAt = Date()
|
||||
self.updatedAt = Date()
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Relation.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 06/09/2026.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
class Relation{
|
||||
var id : UUID
|
||||
var createdAt : Date
|
||||
var updatedAt : Date
|
||||
|
||||
var label : String
|
||||
var source : RelationEndpoint
|
||||
var target : RelationEndpoint
|
||||
var world : World
|
||||
|
||||
init(label: String, source : RelationEndpoint, target : RelationEndpoint, world : World){
|
||||
self.id = UUID()
|
||||
self.createdAt = Date()
|
||||
self.updatedAt = Date()
|
||||
self.label = label
|
||||
self.source = source
|
||||
self.target = target
|
||||
self.world = world
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// RelationEndpoint.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 06/09/2026.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum RelationEndpoint : nonisolated Codable, Equatable {
|
||||
case entityType(id: UUID)
|
||||
case worldEntityType(id: UUID)
|
||||
case entity(id: UUID)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
struct DeleteModel {
|
||||
let modelContext: ModelContext
|
||||
|
||||
// MARK: - Entity
|
||||
|
||||
func delete(_ entity: Entity) {
|
||||
deleteRelations(.entity(id: entity.id))
|
||||
modelContext.delete(entity)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - WorldEntityType
|
||||
|
||||
func delete(_ worldEntityType: WorldEntityType) {
|
||||
deleteRelations(.worldEntityType(id: worldEntityType.id))
|
||||
|
||||
let entities = fetchEntities(for: worldEntityType)
|
||||
|
||||
for entity in entities {
|
||||
delete(entity)
|
||||
}
|
||||
|
||||
modelContext.delete(worldEntityType)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - EntityType
|
||||
|
||||
func delete(_ entityType: EntityType) {
|
||||
deleteRelations(.entityType(id: entityType.id))
|
||||
|
||||
let worldEntityTypes = fetchWorldEntityTypes(using: entityType)
|
||||
|
||||
for worldEntityType in worldEntityTypes {
|
||||
delete(worldEntityType)
|
||||
}
|
||||
|
||||
modelContext.delete(entityType)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - World
|
||||
|
||||
func delete(_ world: World) {
|
||||
for relation in world.relations {
|
||||
modelContext.delete(relation)
|
||||
}
|
||||
|
||||
let worldEntityTypes = fetchWorldEntityTypes(in: world)
|
||||
|
||||
for worldEntityType in worldEntityTypes {
|
||||
delete(worldEntityType)
|
||||
}
|
||||
|
||||
modelContext.delete(world)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Relations
|
||||
|
||||
private func deleteRelations(_ endpoint: RelationEndpoint) {
|
||||
let relations = fetchAllRelations()
|
||||
|
||||
for relation in relations {
|
||||
if relation.source == endpoint || relation.target == endpoint {
|
||||
modelContext.delete(relation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Fetch
|
||||
|
||||
private func fetchAllRelations() -> [Relation] {
|
||||
let descriptor = FetchDescriptor<Relation>()
|
||||
|
||||
return (try? modelContext.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
|
||||
private func fetchEntities(for worldEntityType: WorldEntityType) -> [Entity] {
|
||||
let worldEntityTypeID = worldEntityType.id
|
||||
|
||||
let descriptor = FetchDescriptor<Entity>(
|
||||
predicate: #Predicate {
|
||||
$0.worldEntityType.id == worldEntityTypeID
|
||||
}
|
||||
)
|
||||
|
||||
return (try? modelContext.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
|
||||
private func fetchWorldEntityTypes(in world: World) -> [WorldEntityType] {
|
||||
let worldID = world.id
|
||||
|
||||
let descriptor = FetchDescriptor<WorldEntityType>(
|
||||
predicate: #Predicate {
|
||||
$0.world.id == worldID
|
||||
}
|
||||
)
|
||||
|
||||
return (try? modelContext.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
|
||||
private func fetchWorldEntityTypes(using entityType: EntityType) -> [WorldEntityType] {
|
||||
let entityTypeID = entityType.id
|
||||
|
||||
let descriptor = FetchDescriptor<WorldEntityType>(
|
||||
predicate: #Predicate {
|
||||
$0.entityType.id == entityTypeID
|
||||
}
|
||||
)
|
||||
|
||||
return (try? modelContext.fetch(descriptor)) ?? []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// World.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
class World {
|
||||
var id : UUID
|
||||
var createdAt : Date
|
||||
var updatedAt : Date
|
||||
|
||||
var name : String
|
||||
var overview : String
|
||||
@Attribute(.externalStorage) var illustrationData : Data?
|
||||
@Relationship var relations = [Relation]()
|
||||
|
||||
|
||||
init(name: String, overview: String, illustrationData: Data?) {
|
||||
self.id = UUID()
|
||||
self.name = name
|
||||
self.overview = overview
|
||||
self.createdAt = Date()
|
||||
self.updatedAt = Date()
|
||||
self.illustrationData = illustrationData
|
||||
}
|
||||
|
||||
func duplicate() -> World {
|
||||
let newWorld = World(
|
||||
name: self.name, overview: self.overview, illustrationData: self.illustrationData
|
||||
)
|
||||
|
||||
return newWorld
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// WorldEntityType.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 06/09/2026.
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
@Model
|
||||
class WorldEntityType {
|
||||
var id : UUID
|
||||
var createdAt : Date
|
||||
var updatedAt : Date
|
||||
|
||||
var world : World
|
||||
var entityType : EntityType
|
||||
@Relationship var enabledGlobalAttributeDefinitions = [AttributeDefinition]()
|
||||
@Relationship var localAttributeDefinitions = [AttributeDefinition]()
|
||||
|
||||
init(world: World, entityType: EntityType){
|
||||
self.id = UUID()
|
||||
self.createdAt = Date()
|
||||
self.updatedAt = Date()
|
||||
self.world = world
|
||||
self.entityType = entityType
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// IllustrationSection.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 05/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
|
||||
struct IllustrationSection : View {
|
||||
@Binding var selectedItem: PhotosPickerItem?
|
||||
@Binding var selectedImageData: Data?
|
||||
|
||||
var body: some View {
|
||||
Section("Illustration") {
|
||||
if let selectedImageData, let uiImage = UIImage(data: selectedImageData) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(height: 150)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.listRowInsets(EdgeInsets())
|
||||
}
|
||||
|
||||
PhotosPicker(
|
||||
selection: $selectedItem,
|
||||
matching: .any(of: [.images, .not(.screenshots)])
|
||||
) {
|
||||
Label(
|
||||
selectedImageData == nil ? "Sélectionner une photo" : "Modifier la photo",
|
||||
systemImage: "photo"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// InformationsSection.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 05/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InformationsSection : View {
|
||||
@Binding var worldName : String
|
||||
@Binding var worldOverview : String
|
||||
|
||||
var body: some View {
|
||||
Section("Informations") {
|
||||
TextField("Nom du monde", text: $worldName)
|
||||
|
||||
TextField(
|
||||
"Description",
|
||||
text: $worldOverview,
|
||||
axis: .vertical
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// CreateWorldView.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
|
||||
//
|
||||
// CreateWorldView.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import PhotosUI
|
||||
|
||||
struct CreateWorldView: View {
|
||||
@State private var worldName = ""
|
||||
@State private var worldOverview = ""
|
||||
|
||||
@Environment(\.modelContext) private var context
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(AppRouter.self) private var router
|
||||
|
||||
@State private var showSaveError = false
|
||||
@State private var saveErrorMessage = ""
|
||||
|
||||
@State private var selectedItem: PhotosPickerItem?
|
||||
@State private var selectedImageData: Data?
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
InformationsSection(
|
||||
worldName: $worldName,
|
||||
worldOverview: $worldOverview
|
||||
)
|
||||
|
||||
IllustrationSection(
|
||||
selectedItem: $selectedItem,
|
||||
selectedImageData: $selectedImageData
|
||||
)
|
||||
|
||||
Section {
|
||||
Button("Créer le monde") {
|
||||
let world = World(
|
||||
name: worldName,
|
||||
overview: worldOverview,
|
||||
illustrationData: selectedImageData
|
||||
)
|
||||
|
||||
context.insert(world)
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
router.selectedTab = .worldsList
|
||||
dismiss()
|
||||
} catch {
|
||||
saveErrorMessage =
|
||||
"Impossible de sauvegarder le monde : \(error.localizedDescription)"
|
||||
showSaveError = true
|
||||
}
|
||||
}
|
||||
.disabled(worldName.isEmpty)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.blue)
|
||||
.foregroundStyle(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
.navigationTitle("Créer un monde")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.alert("Erreur", isPresented: $showSaveError) {
|
||||
Button("OK") { }
|
||||
} message: {
|
||||
Text(saveErrorMessage)
|
||||
}
|
||||
.onChange(of: selectedItem) { _, newItem in
|
||||
Task {
|
||||
if let data = try? await newItem?.loadTransferable(type: Data.self) {
|
||||
selectedImageData = data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ import SwiftUI
|
||||
|
||||
struct HomeView : View {
|
||||
var body: some View {
|
||||
Text("Home")
|
||||
CreateWorldButton()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// EmptyWorldView.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 02/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EmptyWorld : View {
|
||||
var body : some View {
|
||||
VStack(spacing: 20) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(.indigo.opacity(0.12))
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
Image(systemName: "globe.europe.africa")
|
||||
.font(.system(size: 42))
|
||||
.foregroundStyle(.indigo)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Text("Ton univers commence ici.")
|
||||
.font(.title3.bold())
|
||||
Text("Crée ton premier monde et commence à construire ton univers.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: 300)
|
||||
|
||||
|
||||
CreateWorldButton()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// WorldListToolbarView.swift
|
||||
// WorldBuilder
|
||||
//
|
||||
// Created by Giovanni Josserand on 03/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct WorldListToolbar : ViewModifier {
|
||||
@Binding var isSelecting : Bool
|
||||
@Binding var selectedWorlds : Set<World.ID>
|
||||
|
||||
@Environment(\.modelContext) private var context
|
||||
|
||||
let worldsIsEmpty : Bool
|
||||
let worlds : [World]
|
||||
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.toolbar{
|
||||
if !worldsIsEmpty {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
if isSelecting {
|
||||
Button("Annuler") {
|
||||
selectedWorlds.removeAll()
|
||||
isSelecting = false
|
||||
}
|
||||
} else {
|
||||
Menu {
|
||||
//Faire le tri
|
||||
}label: {
|
||||
Button {
|
||||
//
|
||||
}label: {
|
||||
Image(systemName: "line.3.horizontal.decrease")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !isSelecting {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
NavigationLink {
|
||||
CreateWorldView()
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isSelecting && !selectedWorlds.isEmpty {
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
Button {
|
||||
let worldsToAction = worlds.filter { selectedWorlds.contains($0.id) }
|
||||
for world in worldsToAction {
|
||||
context.insert(world.duplicate())
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "plus.square.on.square")
|
||||
}
|
||||
.disabled(selectedWorlds.isEmpty)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
Button(role: .destructive) {
|
||||
let worldsToAction = worlds.filter { selectedWorlds.contains($0.id) }
|
||||
let shouldExitSelection = worldsToAction.count == worlds.count
|
||||
|
||||
for world in worldsToAction {
|
||||
DeleteModel(modelContext: context).delete(world)
|
||||
}
|
||||
selectedWorlds.removeAll()
|
||||
|
||||
if shouldExitSelection {
|
||||
isSelecting = false
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.disabled(selectedWorlds.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,91 @@
|
||||
//
|
||||
// WorldListView.swift
|
||||
//
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
//
|
||||
// WorldListView.swift
|
||||
//
|
||||
//
|
||||
// Created by Giovanni Josserand on 01/09/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct WorldListView: View {
|
||||
@Environment(\.modelContext) private var context
|
||||
@Query(sort: \World.createdAt, order: .reverse)
|
||||
private var worlds: [World]
|
||||
let columns = [GridItem(.flexible()), GridItem(.flexible())]
|
||||
|
||||
@State private var isSelecting = false
|
||||
@State private var selectedWorlds = Set<World.ID>()
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WorldListView: View {
|
||||
var body: some View {
|
||||
Text("My worlds")
|
||||
VStack(alignment: .leading, spacing: 24){
|
||||
|
||||
if worlds.isEmpty {
|
||||
EmptyWorld()
|
||||
}else{
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns){
|
||||
ForEach(worlds) {world in
|
||||
Group {
|
||||
if isSelecting {
|
||||
WorldCard(
|
||||
world: world,
|
||||
isSelected: selectedWorlds.contains(world.id)
|
||||
)
|
||||
.onTapGesture {
|
||||
if selectedWorlds.contains(world.id) {
|
||||
selectedWorlds.remove(world.id)
|
||||
} else {
|
||||
selectedWorlds.insert(world.id)
|
||||
}
|
||||
}
|
||||
}else {
|
||||
NavigationLink{
|
||||
WorldOverviewView()
|
||||
} label: {
|
||||
WorldCard(world: world)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.contextMenu{
|
||||
Button {
|
||||
isSelecting = true
|
||||
selectedWorlds.insert(world.id)
|
||||
} label: {
|
||||
Label("Sélectionner", systemImage: "checkmark.circle")
|
||||
}
|
||||
Button {
|
||||
context.insert(world.duplicate())
|
||||
} label: {
|
||||
Label("Dupliquer", systemImage: "plus.square.on.square")
|
||||
}
|
||||
|
||||
Button(role: .destructive){
|
||||
DeleteModel(modelContext: context).delete(world)
|
||||
}label : {
|
||||
Label("Supprimer", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Mes mondes")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.padding(.horizontal, 8)
|
||||
.frame(maxHeight: .infinity, alignment: .topLeading)
|
||||
.modifier(
|
||||
WorldListToolbar(
|
||||
isSelecting: $isSelecting,
|
||||
selectedWorlds: $selectedWorlds,
|
||||
worldsIsEmpty: worlds.isEmpty,
|
||||
worlds: worlds
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,17 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
@main
|
||||
struct WorldBuilderApp: App {
|
||||
@State private var router = AppRouter()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(router)
|
||||
}
|
||||
.modelContainer(for: World.self)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user