Improve path settings, add icons

This commit is contained in:
Christoph Hagen
2025-12-20 12:06:59 +01:00
parent 9848de02cb
commit 07ba77e337
15 changed files with 126 additions and 21 deletions

View File

@@ -105,6 +105,7 @@
E25DA59B2D024A2B00AEF16D /* DateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA59A2D024A2900AEF16D /* DateItem.swift */; }; E25DA59B2D024A2B00AEF16D /* DateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA59A2D024A2900AEF16D /* DateItem.swift */; };
E26C300F2E634B3A00FEB26D /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26C300E2E634B3A00FEB26D /* TimeInterval+Extensions.swift */; }; E26C300F2E634B3A00FEB26D /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26C300E2E634B3A00FEB26D /* TimeInterval+Extensions.swift */; };
E2720B882DF38BB700FDB543 /* Insert+Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2720B872DF38BB200FDB543 /* Insert+Video.swift */; }; E2720B882DF38BB700FDB543 /* Insert+Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2720B872DF38BB200FDB543 /* Insert+Video.swift */; };
E29A577E2E9E444800B19DA3 /* ToolSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29A577D2E9E444000B19DA3 /* ToolSettings.swift */; };
E29D31202D0320E70051B7F4 /* ContentLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D311F2D0320E20051B7F4 /* ContentLabels.swift */; }; E29D31202D0320E70051B7F4 /* ContentLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D311F2D0320E20051B7F4 /* ContentLabels.swift */; };
E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31212D0363FA0051B7F4 /* ContentButtons.swift */; }; E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31212D0363FA0051B7F4 /* ContentButtons.swift */; };
E29D31242D0366860051B7F4 /* TagList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31232D0366820051B7F4 /* TagList.swift */; }; E29D31242D0366860051B7F4 /* TagList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31232D0366820051B7F4 /* TagList.swift */; };
@@ -415,6 +416,7 @@
E25DA59A2D024A2900AEF16D /* DateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateItem.swift; sourceTree = "<group>"; }; E25DA59A2D024A2900AEF16D /* DateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateItem.swift; sourceTree = "<group>"; };
E26C300E2E634B3A00FEB26D /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = "<group>"; }; E26C300E2E634B3A00FEB26D /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = "<group>"; };
E2720B872DF38BB200FDB543 /* Insert+Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Insert+Video.swift"; sourceTree = "<group>"; }; E2720B872DF38BB200FDB543 /* Insert+Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Insert+Video.swift"; sourceTree = "<group>"; };
E29A577D2E9E444000B19DA3 /* ToolSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSettings.swift; sourceTree = "<group>"; };
E29D311F2D0320E20051B7F4 /* ContentLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabels.swift; sourceTree = "<group>"; }; E29D311F2D0320E20051B7F4 /* ContentLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabels.swift; sourceTree = "<group>"; };
E29D31212D0363FA0051B7F4 /* ContentButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentButtons.swift; sourceTree = "<group>"; }; E29D31212D0363FA0051B7F4 /* ContentButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentButtons.swift; sourceTree = "<group>"; };
E29D31232D0366820051B7F4 /* TagList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagList.swift; sourceTree = "<group>"; }; E29D31232D0366820051B7F4 /* TagList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagList.swift; sourceTree = "<group>"; };
@@ -781,6 +783,7 @@
E25DA53B2D0042EA00AEF16D /* Settings */ = { E25DA53B2D0042EA00AEF16D /* Settings */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E29A577D2E9E444000B19DA3 /* ToolSettings.swift */,
E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */, E2FD1D2D2D37180600B48627 /* GeneralSettings.swift */,
E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */, E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */,
E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */, E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */,
@@ -1389,7 +1392,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1540; LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1630; LastUpgradeCheck = 2600;
TargetAttributes = { TargetAttributes = {
E2DD046F2C276F31003BFF1F = { E2DD046F2C276F31003BFF1F = {
CreatedOnToolsVersion = 15.4; CreatedOnToolsVersion = 15.4;
@@ -1663,6 +1666,7 @@
E2F3B3982DC54F9400CFA712 /* ChangeObservingItem.swift in Sources */, E2F3B3982DC54F9400CFA712 /* ChangeObservingItem.swift in Sources */,
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */, E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */,
E2FD1D642D47EF4200B48627 /* DetailListItem.swift in Sources */, E2FD1D642D47EF4200B48627 /* DetailListItem.swift in Sources */,
E29A577E2E9E444800B19DA3 /* ToolSettings.swift in Sources */,
E2FE0F0B2D2689FF002963B7 /* FeedGeneratorSource.swift in Sources */, E2FE0F0B2D2689FF002963B7 /* FeedGeneratorSource.swift in Sources */,
E2DD04742C276F31003BFF1F /* MainView.swift in Sources */, E2DD04742C276F31003BFF1F /* MainView.swift in Sources */,
E20BCCAD2D53F48100B8DBEB /* IssueStatus.swift in Sources */, E20BCCAD2D53F48100B8DBEB /* IssueStatus.swift in Sources */,
@@ -1811,6 +1815,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
@@ -1867,6 +1872,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
}; };
name = Release; name = Release;
@@ -1899,7 +1905,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.5; MARKETING_VERSION = 1.6;
PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement; PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;
@@ -1938,7 +1944,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.5; MARKETING_VERSION = 1.6;
PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement; PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.CHDataManagement;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto; SDKROOT = auto;

View File

@@ -1,5 +1,5 @@
{ {
"originHash" : "6a373ae0a2cc4ad97293e2b13e76aa783451436d6a17beb2295cd5e9b2067122", "originHash" : "f8a1ac1b6fd2d65b9edf0e288c06780ac6a71414f18592b869bb082fb8c7690d",
"pins" : [ "pins" : [
{ {
"identity" : "async-http-client", "identity" : "async-http-client",
@@ -24,8 +24,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/christophhagen/BinaryCodable", "location" : "https://github.com/christophhagen/BinaryCodable",
"state" : { "state" : {
"revision" : "4febea33ee5d813fd9c94c9158be6c85472480d2", "revision" : "53f057050f3c78a1997ed0218337fd92d2eba2b5",
"version" : "3.1.0" "version" : "3.1.1"
} }
}, },
{ {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1630" LastUpgradeVersion = "2600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@@ -1,5 +1,4 @@
import Foundation import Foundation
import _math
extension Double { extension Double {
@@ -8,7 +7,7 @@ extension Double {
} }
func rounded(decimals: Int) -> Double { func rounded(decimals: Int) -> Double {
let factor = _math.pow(10.0, Double(decimals)) let factor = Double.pow(10.0, Double(decimals))
return (self * factor).rounded() / factor return (self * factor).rounded() / factor
} }
} }

View File

@@ -188,8 +188,7 @@ final class ImageGenerator {
// TODO: Run in security scope // TODO: Run in security scope
let process = Process() let process = Process()
#warning("TODO: Move avifenc path to settings") process.launchPath = settings.tools.avifencPath
process.launchPath = "/opt/homebrew/bin/avifenc" // Adjust based on installation
process.arguments = ["-q", "\(quality)", imagePath, generatedImagePath] process.arguments = ["-q", "\(quality)", imagePath, generatedImagePath]
let pipe = Pipe() let pipe = Pipe()

View File

@@ -33,6 +33,12 @@ extension ContentLanguage: Comparable {
} }
} }
extension ContentLanguage: CustomStringConvertible {
var description: String {
rawValue
}
}
extension ContentLanguage { extension ContentLanguage {

View File

@@ -354,7 +354,7 @@ final class FileResource: Item, LocalizedItem {
} }
private func determineVideoType() -> String? { private func determineVideoType() -> String? {
#warning("TODO: Move ffmpeg path to settings") let ffmpegPath = content.settings.tools.ffprobePath
switch type { switch type {
case .webm: case .webm:
return "video/webm" return "video/webm"
@@ -371,7 +371,7 @@ final class FileResource: Item, LocalizedItem {
let process = Process() let process = Process()
let arguments = "-v error -select_streams v:0 -show_entries stream=codec_tag_string -of default=noprint_wrappers=1:nokey=1 \(path.path())" let arguments = "-v error -select_streams v:0 -show_entries stream=codec_tag_string -of default=noprint_wrappers=1:nokey=1 \(path.path())"
.components(separatedBy: " ") .components(separatedBy: " ")
process.launchPath = "/opt/homebrew/bin/ffprobe" process.launchPath = ffmpegPath
process.arguments = Array(arguments) process.arguments = Array(arguments)
let pipe = Pipe() let pipe = Pipe()

View File

@@ -22,6 +22,9 @@ final class Settings: ChangeObservableItem {
@Published @Published
var audioPlayer: AudioPlayerSettings var audioPlayer: AudioPlayerSettings
@Published
var tools: ToolSettings
weak var content: Content? weak var content: Content?
var cancellables: Set<AnyCancellable> = [] var cancellables: Set<AnyCancellable> = []
@@ -31,13 +34,15 @@ final class Settings: ChangeObservableItem {
navigation: NavigationSettings, navigation: NavigationSettings,
posts: PostSettings, posts: PostSettings,
pages: PageSettings, pages: PageSettings,
audioPlayer: AudioPlayerSettings) { audioPlayer: AudioPlayerSettings,
tools: ToolSettings) {
self.general = general self.general = general
self.paths = paths self.paths = paths
self.navigation = navigation self.navigation = navigation
self.posts = posts self.posts = posts
self.pages = pages self.pages = pages
self.audioPlayer = audioPlayer self.audioPlayer = audioPlayer
self.tools = tools
observeChildChanges() observeChildChanges()
} }
@@ -58,6 +63,7 @@ final class Settings: ChangeObservableItem {
observe(posts) observe(posts)
observe(pages) observe(pages)
observe(audioPlayer) observe(audioPlayer)
observe(tools)
} }
} }
@@ -72,7 +78,8 @@ extension Settings {
navigation: .init(context: context, data: data.navigation), navigation: .init(context: context, data: data.navigation),
posts: .init(context: context, data: data.posts), posts: .init(context: context, data: data.posts),
pages: .init(context: context, data: data.pages), pages: .init(context: context, data: data.pages),
audioPlayer: .init(context: context, data: data.audioPlayer)) audioPlayer: .init(context: context, data: data.audioPlayer),
tools: .init(context: context, data: data.tools))
content = context.content content = context.content
} }
@@ -84,7 +91,8 @@ extension Settings {
posts: posts.data, posts: posts.data,
pages: pages.data, pages: pages.data,
audioPlayer: audioPlayer.data, audioPlayer: audioPlayer.data,
tagOverview: tagOverview?.data) tagOverview: tagOverview?.data,
tools: tools.data)
} }
struct Data: Codable, Equatable { struct Data: Codable, Equatable {
@@ -95,6 +103,7 @@ extension Settings {
let pages: PageSettings.Data let pages: PageSettings.Data
let audioPlayer: AudioPlayerSettings.Data let audioPlayer: AudioPlayerSettings.Data
let tagOverview: Tag.Data? let tagOverview: Tag.Data?
let tools: ToolSettings.Data
} }
func saveToDisk(_ data: Data) -> Bool { func saveToDisk(_ data: Data) -> Bool {
@@ -110,7 +119,8 @@ extension Settings {
navigation: .default, navigation: .default,
posts: .default, posts: .default,
pages: .default, pages: .default,
audioPlayer: .default) audioPlayer: .default,
tools: .default)
} }
extension GeneralSettings { extension GeneralSettings {
@@ -195,3 +205,11 @@ extension PageSettings {
emptyPageText: "This page is empty")) emptyPageText: "This page is empty"))
} }
} }
extension ToolSettings {
static var `default`: ToolSettings {
.init(ffprobePath: "/opt/homebrew/bin/ffprobe",
avifencPath: "/opt/homebrew/bin/avifenc")
}
}

View File

@@ -0,0 +1,39 @@
import Foundation
final class ToolSettings: ObservableObject {
/// The items to show in the navigation bar
@Published
var ffprobePath: String
@Published
var avifencPath: String
init(ffprobePath: String,
avifencPath: String) {
self.ffprobePath = ffprobePath
self.avifencPath = avifencPath
}
}
// MARK: Storage
extension ToolSettings {
convenience init(context: LoadingContext, data: ToolSettings.Data) {
self.init(
ffprobePath: data.ffprobePath,
avifencPath: data.avifencPath)
}
struct Data: Codable, Equatable {
let ffprobePath: String
let avifencPath: String
}
var data: Data {
.init(
ffprobePath: ffprobePath,
avifencPath: avifencPath)
}
}

View File

@@ -133,3 +133,33 @@ extension Icon {
""" """
} }
} }
extension Icon {
struct Pencil: ContentIcon {
static let id = "icon-pencil"
static let attributes = "viewBox='0 0 16 16' fill='currentColor'"
static let content =
"""
<path d="M12.9.1a.5.5 0 0 0-.8 0l-1.6 1.7 3.7 3.7L16 3.9a.5.5 0 0 0 0-.8zm.6 6.1L9.8 2.5 3.3 9h.2a1 1 0 0 1 .5.5v.5h.5a1 1 0 0 1 .5.5v.5h.5a1 1 0 0 1 .5.5v.5h.5a1 1 0 0 1 .5.5v.2zM6 13.7V13h-.5a1 1 0 0 1-.5-.5V12h-.5a1 1 0 0 1-.5-.5V11h-.5a1 1 0 0 1-.5-.5V10h-.7l-.2.1v.2l-2 5a.5.5 0 0 0 .6.7l5-2 .2-.1z"/>
"""
}
}
extension Icon {
struct PersonPlus: ContentIcon {
static let id = "person-plus"
static let attributes = "viewBox='0 0 16 16' fill='currentColor'"
static let content =
"""
<path d="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m.5-5v1h1a.5.5 0 0 1 0 1h-1v1a.5.5 0 0 1-1 0v-1h-1a.5.5 0 0 1 0-1h1v-1a.5.5 0 0 1 1 0m-2-6a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4"/><path d="M8.3 14 8 13H3q0-.5.8-1.7c.7-.6 2-1.3 4.2-1.3h.7l.8-.9L8 9c-5 0-6 3-6 4s1 1 1 1z"/>
"""
}
}

View File

@@ -21,6 +21,10 @@ enum PageIcon: String, CaseIterable {
case bellSlash = "bell-slash" case bellSlash = "bell-slash"
case pencil
case personPlus = "person-plus"
// MARK: Statistics // MARK: Statistics
case statisticsTime = "time" case statisticsTime = "time"
@@ -97,6 +101,8 @@ enum PageIcon: String, CaseIterable {
case .leftRightArrow: Icon.LeftRightArrow.self case .leftRightArrow: Icon.LeftRightArrow.self
case .bell: Icon.Bell.self case .bell: Icon.Bell.self
case .bellSlash: Icon.BellSlash.self case .bellSlash: Icon.BellSlash.self
case .pencil: Icon.Pencil.self
case .personPlus: Icon.PersonPlus.self
} }
} }
@@ -111,6 +117,8 @@ enum PageIcon: String, CaseIterable {
case .video: "Video" case .video: "Video"
case .bell: "Bell" case .bell: "Bell"
case .bellSlash: "Bell With Slash" case .bellSlash: "Bell With Slash"
case .pencil: "Pencil"
case .personPlus: "Person Plus"
case .leftRightArrow: "LeftRightArrow" case .leftRightArrow: "LeftRightArrow"
case .buttonExternalLink: "Button: External Link" case .buttonExternalLink: "Button: External Link"
case .buttonGitLink: "Button: Git Link" case .buttonGitLink: "Button: Git Link"

View File

@@ -1,5 +1,5 @@
import SFSafeSymbols import SFSafeSymbols
import SwiftUICore import SwiftUI
enum SaveState { enum SaveState {
case storageNotInitialized case storageNotInitialized

View File

@@ -183,7 +183,7 @@ struct SecurityBookmark {
with(relativePath: relativeSource) { source in with(relativePath: relativeSource) { source in
if !exists(source) { if !exists(source) {
if !failIfMissing { return true } if !failIfMissing { return true }
reportError("Failed to move \(relativeSource): File does not exist") reportError("Failed to move \(relativeSource): File \(source) does not exist")
return false return false
} }

View File

@@ -82,7 +82,7 @@ struct GenerationContentView: View {
statusWhenNonEmpty: .warning, statusWhenNonEmpty: .warning,
items: $content.results.emptyPages) { pageId in items: $content.results.emptyPages) { pageId in
HStack { HStack {
Text("\(pageId.pageId) (\(pageId.language))") Text("\(pageId.pageId) (\(pageId.language.description))")
Spacer() Spacer()
Button("Show") { Button("Show") {
show(page: pageId.pageId, show(page: pageId.pageId,

View File

@@ -1,5 +1,5 @@
import SFSafeSymbols import SFSafeSymbols
import SwiftUICore import SwiftUI
enum IssueStatus { enum IssueStatus {
case nominal case nominal