Fix id of Items, saving

This commit is contained in:
Christoph Hagen
2025-06-11 08:19:44 +02:00
parent 5970ce2e9f
commit 1d0eba9d78
64 changed files with 233 additions and 217 deletions

View File

@ -78,6 +78,10 @@ struct AddFileView: View {
}
private func importSelectedFiles() {
guard !filesToAdd.isEmpty else {
dismiss()
return
}
for file in filesToAdd {
guard file.isSelected else {
print("Skipping unselected file \(file.uniqueId)")

View File

@ -41,7 +41,7 @@ struct FileContentView: View {
.foregroundStyle(.secondary)
case .text, .code:
TextFileContentView(file: file)
.id(file.id + file.modifiedDate.description)
.id(file.identifier + file.modifiedDate.description)
case .video:
VStack {
if let image = file.imageToDisplay {

View File

@ -72,11 +72,12 @@ struct FileDetailView: View {
}
IdPropertyView(
id: $file.id,
id: $file.identifier,
title: "Name",
footer: "The unique name of the file, which is also used to reference it in posts and pages.",
validation: file.isValid,
update: { file.update(id: $0) })
.id(file.id)
switch language {
case .english:
@ -154,7 +155,7 @@ struct FileDetailView: View {
}
private func showFileInFinder() {
content.storage.openFinderWindow(withSelectedFile: file.id)
content.storage.openFinderWindow(withSelectedFile: file.identifier)
}
private func markFileAsChanged() {
@ -169,11 +170,11 @@ struct FileDetailView: View {
private func replaceFile() {
guard let url = openFilePanel() else {
print("File '\(file.id)': No file selected as replacement")
print("File '\(file.identifier)': No file selected as replacement")
return
}
guard content.storage.importExternalFile(at: url, fileId: file.id) else {
print("File '\(file.id)': Failed to replace file")
guard content.storage.importExternalFile(at: url, fileId: file.identifier) else {
print("File '\(file.identifier)': Failed to replace file")
return
}
@ -197,7 +198,7 @@ struct FileDetailView: View {
let response = panel.runModal()
guard response == .OK else {
print("File '\(file.id)': Failed to select file to replace")
print("File '\(file.identifier)': Failed to select file to replace")
return nil
}
@ -209,8 +210,8 @@ struct FileDetailView: View {
return
}
guard content.storage.removeFileContent(file: file.id) else {
print("File '\(file.id)': Failed to delete file to make it external")
guard content.storage.removeFileContent(file: file.identifier) else {
print("File '\(file.identifier)': Failed to delete file to make it external")
return
}
DispatchQueue.main.async {
@ -220,8 +221,8 @@ struct FileDetailView: View {
}
private func deleteFile() {
guard content.storage.delete(file: file.id) else {
print("File '\(file.id)': Failed to delete file in content folder")
guard content.storage.delete(file: file.identifier) else {
print("File '\(file.identifier)': Failed to delete file in content folder")
return
}
content.remove(file)

View File

@ -32,7 +32,7 @@ struct FileListView: View {
guard !searchString.isEmpty else {
return filesBySelectedType
}
return filesBySelectedType.filter { $0.id.contains(searchString) }
return filesBySelectedType.filter { $0.identifier.contains(searchString) }
}
var body: some View {
@ -55,10 +55,10 @@ struct FileListView: View {
LazyVStack(spacing: 0) {
ForEach(filteredFiles) { file in
SelectableListItem(selected: selectedFile == file) {
Text(file.id)
Text(file.identifier)
.lineLimit(1)
}
.id(file.id)
.id(file.identifier)
.onTapGesture {
selectedFile = file
}

View File

@ -30,7 +30,7 @@ final class FileToAdd: ObservableObject {
}
var idAlreadyExists: Bool {
content.files.contains { $0.id == uniqueId }
content.files.contains { $0.identifier == uniqueId }
}
}

View File

@ -43,7 +43,7 @@ struct MultiFileSelectionView: View {
guard !searchString.isEmpty else {
return filesBySelectedType
}
return filesBySelectedType.filter { $0.id.contains(searchString) }
return filesBySelectedType.filter { $0.identifier.contains(searchString) }
}
var body: some View {
@ -59,7 +59,7 @@ struct MultiFileSelectionView: View {
.foregroundStyle(.red)
.contentShape(Rectangle())
.onTapGesture { deselect(file: file) }
Text(file.id)
Text(file.identifier)
Spacer()
}
}
@ -99,7 +99,7 @@ struct MultiFileSelectionView: View {
Image(systemSymbol: .plusCircleFill)
.foregroundStyle(.green)
}
Text(file.id)
Text(file.identifier)
Spacer()
}
.contentShape(Rectangle())

View File

@ -49,9 +49,9 @@ struct TextFileContentView: View {
private func reload() {
fileContent = file.textContent()
loadedFile = file.id
loadedFile = file.identifier
loadedFileDate = file.modifiedDate
print("Loaded content of file \(file.id)")
print("Loaded content of file \(file.identifier)")
}
private func save() {
@ -59,25 +59,25 @@ struct TextFileContentView: View {
print("[ERROR] Text File View: No file loaded to save")
return
}
guard loadedFile == file.id else {
guard loadedFile == file.identifier else {
print("[ERROR] Text File View: Not saving since file changed")
reload()
return
}
guard loadedFileDate == file.modifiedDate else {
print("Text File View: Not saving changed file \(file.id)")
print("Text File View: Not saving changed file \(file.identifier)")
reload()
return
}
guard fileContent != "" else {
print("Text File View: Not saving empty file \(file.id)")
print("Text File View: Not saving empty file \(file.identifier)")
return
}
guard file.save(textContent: fileContent) else {
print("[ERROR] Text File View: Failed to save file \(file.id)")
print("[ERROR] Text File View: Failed to save file \(file.identifier)")
return
}
loadedFileDate = file.modifiedDate
print("Text File View: Saved file \(file.id)")
print("Text File View: Saved file \(file.identifier)")
}
}

View File

@ -72,11 +72,11 @@ struct GenerationContentView: View {
GenerationStringIssuesView(
text: "required files",
statusWhenNonEmpty: .nominal,
items: content.results.requiredFiles) { $0.id }
items: content.results.requiredFiles) { $0.identifier }
GenerationStringIssuesView(
text: "external files",
statusWhenNonEmpty: .nominal,
items: content.results.externalFiles) { $0.id }
items: content.results.externalFiles) { $0.identifier }
GenerationIssuesView(
text: "empty pages",
statusWhenNonEmpty: .warning,
@ -96,14 +96,14 @@ struct GenerationContentView: View {
statusWhenNonEmpty: .warning,
items: draftPages,
buttonText: "Show",
itemText: { $0.id },
itemText: { $0.identifier },
action: { show($0) })
GenerationIssuesActionView(
title: "draft posts",
statusWhenNonEmpty: .warning,
items: draftPosts,
buttonText: "Show",
itemText: { $0.id },
itemText: { $0.identifier },
action: { show($0) })
GenerationIssuesView(
text: "additional output files",
@ -117,10 +117,10 @@ struct GenerationContentView: View {
}
GenerationStringIssuesView(
text: "inaccessible files",
items: content.results.inaccessibleFiles) { $0.id }
items: content.results.inaccessibleFiles) { $0.identifier }
GenerationStringIssuesView(
text: "unparsable files",
items: content.results.unparsableFiles) { $0.id }
items: content.results.unparsableFiles) { $0.identifier }
GenerationStringIssuesView(
text: "unsaved output files",
items: content.results.unsavedOutputFiles)

View File

@ -24,7 +24,7 @@ struct FilePropertyView: View {
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack {
Text(selectedFile?.id ?? "No file selected")
Text(selectedFile?.identifier ?? "No file selected")
Spacer()
Button("Select") {
showFileSelectionSheet = true

View File

@ -36,7 +36,7 @@ struct OptionalImagePropertyView: View {
}
HStack {
Text(selectedImage?.id ?? "No file selected")
Text(selectedImage?.identifier ?? "No file selected")
Spacer()
Button("Select") {
showSelectionSheet = true

View File

@ -15,7 +15,7 @@ struct PagePropertyView: View {
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack {
Text(selectedPage?.id ?? "No page selected")
Text(selectedPage?.identifier ?? "No page selected")
Spacer()
Button("Select") {
showPageSelectionSheet = true

View File

@ -16,7 +16,7 @@ struct TagDisplayView: View {
var body: some View {
FlowHStack {
ForEach(tags, id: \.id) { tag in
ForEach(tags, id: \.identifier) { tag in
TagView(text: tag.localized(in: language).name)
.foregroundStyle(.white)
}

View File

@ -27,7 +27,7 @@ struct TagPickerView: View {
Text("Select a tag to link to")
List(content.tags, selection: $newSelection) { tag in
let loc = tag.localized(in: language)
Text("\(loc.title) (\(tag.id))")
Text("\(loc.title) (\(tag.identifier))")
.tag(tag)
}
.frame(minHeight: 300)

View File

@ -15,7 +15,7 @@ struct TagPropertyView: View {
var body: some View {
GenericPropertyView(title: title, footer: footer) {
HStack {
Text(selectedTag?.id ?? "No tag selected")
Text(selectedTag?.identifier ?? "No tag selected")
Spacer()
Button("Select") {
showTagSelectionSheet = true

View File

@ -20,7 +20,7 @@ final class InsertableFileButton: ObservableObject {
"""
icon: \(label.icon.rawValue)
text: \(label.value)
file: \(file.id)
file: \(file.identifier)
"""
guard let downloadedFileName else {
return result
@ -86,7 +86,7 @@ struct InsertableButtons: View, InsertableCommandView {
var id: String {
switch self {
case .file(let file):
return "file-\(file.file?.id ?? "none")"
return "file-\(file.file?.identifier ?? "none")"
case .url(let url):
return "url-\(url.url)"
case .event(let event):
@ -161,7 +161,7 @@ private struct FileButtonView: View {
var body: some View {
HStack {
LabelEditingView(label: $content.label)
Button("\(content.file?.id ?? "Select file")", action: { showFileSelectionSheet = true })
Button("\(content.file?.identifier ?? "Select file")", action: { showFileSelectionSheet = true })
OptionalTextField("", text: $content.downloadedFileName, prompt: "Downloaded file name")
.textFieldStyle(.roundedBorder)
}

View File

@ -28,7 +28,7 @@ struct InsertableGallery: View, InsertableCommandView {
}
return (
["```\(GalleryBlock.blockId)"] +
images.map { $0.id } +
images.map { $0.identifier } +
["```"]
).joined(separator: "\n")
}

View File

@ -24,9 +24,9 @@ struct InsertableImage: View, InsertableCommandView {
return nil
}
guard let caption else {
return "![image](\(selectedImage.id))"
return "![image](\(selectedImage.identifier))"
}
return "![image](\(selectedImage.id);\(caption))"
return "![image](\(selectedImage.identifier);\(caption))"
}
}

View File

@ -45,11 +45,11 @@ struct InsertableLink: View, InsertableCommandView {
case .post, .tagOverview:
return nil
case .page:
return selectedPage?.id
return selectedPage?.identifier
case .tag:
return selectedTag?.id
return selectedTag?.identifier
case .file:
return selectedFile?.id
return selectedFile?.identifier
}
}

View File

@ -40,8 +40,8 @@ struct InsertableRoute: View, InsertableCommandView {
return nil
}
var result = ["```route"]
result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.id)")
result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.id)")
result.append("\(RouteBlock.Key.image.rawValue): \(selectedImage.identifier)")
result.append("\(RouteBlock.Key.file.rawValue): \(dataFile.identifier)")
if components != Set(RouteStatisticType.allCases) {
let list = components
.map { $0.rawValue }

View File

@ -50,16 +50,16 @@ struct InsertableVideo: View, InsertableCommandView {
var lines: [String] = []
lines.append("```video")
if let posterImage {
lines.append("\(VideoBlock.Key.poster): \(posterImage.id)")
lines.append("\(VideoBlock.Key.poster): \(posterImage.identifier)")
}
if let videoH265 {
lines.append("\(VideoBlock.Key.h265): \(videoH265.id)")
lines.append("\(VideoBlock.Key.h265): \(videoH265.identifier)")
}
if let videoH264 {
lines.append("\(VideoBlock.Key.h264): \(videoH264.id)")
lines.append("\(VideoBlock.Key.h264): \(videoH264.identifier)")
}
if let videoWebm {
lines.append("\(VideoBlock.Key.webm): \(videoWebm.id)")
lines.append("\(VideoBlock.Key.webm): \(videoWebm.identifier)")
}
if controls { lines.append(VideoBlock.Key.controls.rawValue) }
if autoplay { lines.append(VideoBlock.Key.autoplay.rawValue) }

View File

@ -35,7 +35,7 @@ struct PageContentResultsView: View {
TextWithSymbol(
symbol: $0.type.category.symbol,
color: .blue,
text: $0.id)
text: $0.identifier)
}
+ results.missingFiles.keys.map {
TextWithSymbol(

View File

@ -32,7 +32,7 @@ struct PageContentView: View {
if page.isExternalUrl {
VStack {
PageTitleView(page: page.localized(in: language))
.id(page.id + language.rawValue)
.id(page.identifier + language.rawValue)
Spacer()
Text("No content available for external page")
.font(.title)
@ -42,10 +42,10 @@ struct PageContentView: View {
} else {
VStack(alignment: .leading) {
PageTitleView(page: page.localized(in: language))
.id(page.id + language.rawValue)
.id(page.identifier + language.rawValue)
TagDisplayView(tags: $page.tags)
LocalizedPageContentView(page: page, language: language)
.id(page.id + language.rawValue)
.id(page.identifier + language.rawValue)
}
.padding()
}

View File

@ -30,7 +30,7 @@ struct PageDetailView: View {
title: "Page",
text: "A page contains longer content")
IdPropertyView(
id: $page.id,
id: $page.identifier,
footer: "The page id is used to link to it internally.",
validation: page.isValid,
update: { page.update(id: $0) })
@ -75,7 +75,7 @@ struct PageDetailView: View {
isExternalPage: page.isExternalUrl,
page: page.localized(in: language),
transferImage: transferImage)
.id(page.id + language.rawValue)
.id(page.identifier + language.rawValue)
ColoredButton(delete: deletePage)
}
.padding()
@ -83,8 +83,8 @@ struct PageDetailView: View {
}
private func deletePage() {
guard content.storage.delete(page: page.id) else {
print("Page '\(page.id)': Failed to delete file in content folder")
guard content.storage.delete(page: page.identifier) else {
print("Page '\(page.identifier)': Failed to delete file in content folder")
return
}
content.remove(page)

View File

@ -27,7 +27,7 @@ struct PagePickerView: View {
Text("Select a page to link to")
List(content.pages, selection: $newSelection) { page in
let loc = page.localized(in: language)
Text("\(loc.title) (\(page.id))")
Text("\(loc.title) (\(page.identifier))")
.tag(page)
}
.frame(minHeight: 300)

View File

@ -43,7 +43,7 @@ struct PostDetailView: View {
}
IdPropertyView(
id: $post.id,
id: $post.identifier,
footer: "The id is used to link to post and store them",
validation: post.isValid,
update: { post.update(id: $0) })
@ -99,8 +99,8 @@ struct PostDetailView: View {
}
private func deletePost() {
guard content.storage.delete(post: post.id) else {
print("Post '\(post.id)': Failed to delete file in content folder")
guard content.storage.delete(post: post.identifier) else {
print("Post '\(post.identifier)': Failed to delete file in content folder")
return
}
content.remove(post)

View File

@ -18,7 +18,7 @@ struct PostImageView: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
Text(image.id)
Text(image.identifier)
.font(.title)
Text("Failed to load image")
.font(.body)
@ -32,7 +32,7 @@ struct PostImageView: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
Text(image.id)
Text(image.identifier)
.font(.title)
Button("Generate preview") {
generateVideoPreview(image)
@ -48,7 +48,7 @@ struct PostImageView: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
Text(image.id)
Text(image.identifier)
.font(.title)
Text("Invalid media type")
.font(.body)

View File

@ -10,7 +10,7 @@ private struct PostListItem: View {
var body: some View {
HStack {
LocalizedPostListItem(id: post.id, post: post.localized(in: language))
LocalizedPostListItem(id: post.identifier, post: post.localized(in: language))
if post.isDraft {
TextIndicator(text: "Draft", background: .yellow)
} else {

View File

@ -32,7 +32,7 @@ struct TagDetailView: View {
footer: "Indicate if the tag should appear in the tag list of posts and pages. If the tag is not visible, then it can still be used as a filter.")
IdPropertyView(
id: $tag.id,
id: $tag.identifier,
title: "Tag id",
footer: "The unique id of the tag for references",
validation: tag.isValid) {
@ -42,7 +42,7 @@ struct TagDetailView: View {
LocalizedTagDetailView(
tag: tag.localized(in: language),
transferImage: transferImage)
.id(tag.id + language.rawValue)
.id(tag.identifier + language.rawValue)
ColoredButton(delete: deleteTag)
}
.padding()
@ -50,8 +50,8 @@ struct TagDetailView: View {
}
private func deleteTag() {
guard content.storage.delete(tag: tag.id) else {
print("Tag '\(tag.id)': Failed to delete file in content folder")
guard content.storage.delete(tag: tag.identifier) else {
print("Tag '\(tag.identifier)': Failed to delete file in content folder")
return
}
content.remove(tag)