Improve path settings, add icons
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -33,6 +33,12 @@ extension ContentLanguage: Comparable {
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentLanguage: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentLanguage {
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
39
CHDataManagement/Model/Settings/ToolSettings.swift
Normal file
39
CHDataManagement/Model/Settings/ToolSettings.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SFSafeSymbols
|
||||
import SwiftUICore
|
||||
import SwiftUI
|
||||
|
||||
enum SaveState {
|
||||
case storageNotInitialized
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SFSafeSymbols
|
||||
import SwiftUICore
|
||||
import SwiftUI
|
||||
|
||||
enum IssueStatus {
|
||||
case nominal
|
||||
|
||||
Reference in New Issue
Block a user