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

@@ -1,5 +1,4 @@
import Foundation
import _math
extension Double {
@@ -8,7 +7,7 @@ extension 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
}
}

View File

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

View File

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

View File

@@ -354,7 +354,7 @@ final class FileResource: Item, LocalizedItem {
}
private func determineVideoType() -> String? {
#warning("TODO: Move ffmpeg path to settings")
let ffmpegPath = content.settings.tools.ffprobePath
switch type {
case .webm:
return "video/webm"
@@ -371,7 +371,7 @@ final class FileResource: Item, LocalizedItem {
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())"
.components(separatedBy: " ")
process.launchPath = "/opt/homebrew/bin/ffprobe"
process.launchPath = ffmpegPath
process.arguments = Array(arguments)
let pipe = Pipe()

View File

@@ -22,6 +22,9 @@ final class Settings: ChangeObservableItem {
@Published
var audioPlayer: AudioPlayerSettings
@Published
var tools: ToolSettings
weak var content: Content?
var cancellables: Set<AnyCancellable> = []
@@ -31,13 +34,15 @@ final class Settings: ChangeObservableItem {
navigation: NavigationSettings,
posts: PostSettings,
pages: PageSettings,
audioPlayer: AudioPlayerSettings) {
audioPlayer: AudioPlayerSettings,
tools: ToolSettings) {
self.general = general
self.paths = paths
self.navigation = navigation
self.posts = posts
self.pages = pages
self.audioPlayer = audioPlayer
self.tools = tools
observeChildChanges()
}
@@ -58,6 +63,7 @@ final class Settings: ChangeObservableItem {
observe(posts)
observe(pages)
observe(audioPlayer)
observe(tools)
}
}
@@ -72,7 +78,8 @@ extension Settings {
navigation: .init(context: context, data: data.navigation),
posts: .init(context: context, data: data.posts),
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
}
@@ -84,7 +91,8 @@ extension Settings {
posts: posts.data,
pages: pages.data,
audioPlayer: audioPlayer.data,
tagOverview: tagOverview?.data)
tagOverview: tagOverview?.data,
tools: tools.data)
}
struct Data: Codable, Equatable {
@@ -95,6 +103,7 @@ extension Settings {
let pages: PageSettings.Data
let audioPlayer: AudioPlayerSettings.Data
let tagOverview: Tag.Data?
let tools: ToolSettings.Data
}
func saveToDisk(_ data: Data) -> Bool {
@@ -110,7 +119,8 @@ extension Settings {
navigation: .default,
posts: .default,
pages: .default,
audioPlayer: .default)
audioPlayer: .default,
tools: .default)
}
extension GeneralSettings {
@@ -195,3 +205,11 @@ extension PageSettings {
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 pencil
case personPlus = "person-plus"
// MARK: Statistics
case statisticsTime = "time"
@@ -97,6 +101,8 @@ enum PageIcon: String, CaseIterable {
case .leftRightArrow: Icon.LeftRightArrow.self
case .bell: Icon.Bell.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 .bell: "Bell"
case .bellSlash: "Bell With Slash"
case .pencil: "Pencil"
case .personPlus: "Person Plus"
case .leftRightArrow: "LeftRightArrow"
case .buttonExternalLink: "Button: External Link"
case .buttonGitLink: "Button: Git Link"

View File

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

View File

@@ -183,7 +183,7 @@ struct SecurityBookmark {
with(relativePath: relativeSource) { source in
if !exists(source) {
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
}

View File

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

View File

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