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>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>2C96045430463C2E0003A2A8</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</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 {
|
struct HomeView : View {
|
||||||
var body: some 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,86 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
struct WorldListView: View {
|
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>()
|
||||||
|
|
||||||
|
|
||||||
var body: some 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 SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct WorldBuilderApp: App {
|
struct WorldBuilderApp: App {
|
||||||
|
@State private var router = AppRouter()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
.environment(router)
|
||||||
}
|
}
|
||||||
|
.modelContainer(for: World.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user