Improve JSON editor for macOS
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Frameworks/Runestone"]
|
||||||
|
path = Frameworks/Runestone
|
||||||
|
url = https://github.com/nekohasekai/Runestone.git
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
--exclude Frameworks
|
||||||
@@ -11,20 +11,65 @@ import SwiftUI
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStackCompat {
|
#if os(macOS)
|
||||||
Group {
|
macOSBody
|
||||||
if configuration.isLoading {
|
#else
|
||||||
ProgressView()
|
iOSBody
|
||||||
} else {
|
#endif
|
||||||
listContent
|
}
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
private var macOSBody: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text("Dashboard Items")
|
||||||
|
.font(.headline)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
.padding(.bottom, 12)
|
||||||
|
|
||||||
|
Group {
|
||||||
|
if configuration.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
} else {
|
||||||
|
listContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Dashboard Items")
|
.toolbar {
|
||||||
#if os(iOS)
|
ToolbarItem(placement: .destructiveAction) {
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
Button("Reset", role: .destructive) {
|
||||||
#endif
|
Task {
|
||||||
.toolbar {
|
await configuration.resetToDefault()
|
||||||
#if os(iOS) || os(tvOS)
|
configurationVersion += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
Button("Done") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onExitCommand {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
private var iOSBody: some View {
|
||||||
|
NavigationStackCompat {
|
||||||
|
Group {
|
||||||
|
if configuration.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
listContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Dashboard Items")
|
||||||
|
#if os(iOS)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
#endif
|
||||||
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button("Reset", role: .destructive) {
|
Button("Reset", role: .destructive) {
|
||||||
Task {
|
Task {
|
||||||
@@ -33,24 +78,10 @@ import SwiftUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
}
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
}
|
||||||
Button("Reset", role: .destructive) {
|
|
||||||
Task {
|
|
||||||
await configuration.resetToDefault()
|
|
||||||
configurationVersion += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
|
||||||
Button("Done") {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
#endif
|
||||||
|
|
||||||
private var listContent: some View {
|
private var listContent: some View {
|
||||||
List {
|
List {
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ public struct ProfileCard: View {
|
|||||||
|
|
||||||
private var manageProfilesSheet: some View {
|
private var manageProfilesSheet: some View {
|
||||||
NavigationSheet(
|
NavigationSheet(
|
||||||
title: "Profiles",
|
title: String(localized: "Manage profiles"),
|
||||||
showDoneButton: true,
|
showDoneButton: true,
|
||||||
onDismiss: { viewModel.showManageProfiles = false }
|
onDismiss: { viewModel.showManageProfiles = false }
|
||||||
) {
|
) {
|
||||||
@@ -227,11 +227,20 @@ public struct ProfileCard: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func editProfileSheet(for profile: Profile) -> some View {
|
private func editProfileSheet(for profile: Profile) -> some View {
|
||||||
NavigationSheet(title: "Edit Profile") {
|
#if os(macOS)
|
||||||
EditProfileView()
|
NavigationSheet {
|
||||||
.environmentObject(profile)
|
EditProfileView()
|
||||||
.environmentObject(environments)
|
.environmentObject(profile)
|
||||||
}
|
.environmentObject(environments)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 500, minHeight: 400)
|
||||||
|
#else
|
||||||
|
NavigationSheet(title: "Edit Profile") {
|
||||||
|
EditProfileView()
|
||||||
|
.environmentObject(profile)
|
||||||
|
.environmentObject(environments)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +281,16 @@ extension ProfileCard {
|
|||||||
@State private var createdProfile: Profile?
|
@State private var createdProfile: Profile?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStackCompat {
|
#if os(macOS)
|
||||||
|
macOSBody
|
||||||
|
#else
|
||||||
|
iOSBody
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
@ViewBuilder
|
||||||
|
private var macOSBody: some View {
|
||||||
if let profile = createdProfile {
|
if let profile = createdProfile {
|
||||||
EditProfileView()
|
EditProfileView()
|
||||||
.environmentObject(profile)
|
.environmentObject(profile)
|
||||||
@@ -282,15 +300,28 @@ extension ProfileCard {
|
|||||||
createdProfile = profile
|
createdProfile = profile
|
||||||
}
|
}
|
||||||
.environmentObject(environments)
|
.environmentObject(environments)
|
||||||
#if os(iOS)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS) || os(tvOS)
|
#else
|
||||||
.presentationDetentsIfAvailable()
|
private var iOSBody: some View {
|
||||||
#endif
|
NavigationStackCompat {
|
||||||
}
|
if let profile = createdProfile {
|
||||||
|
EditProfileView()
|
||||||
|
.environmentObject(profile)
|
||||||
|
.environmentObject(environments)
|
||||||
|
} else {
|
||||||
|
NewProfileView { profile in
|
||||||
|
createdProfile = profile
|
||||||
|
}
|
||||||
|
.environmentObject(environments)
|
||||||
|
#if os(iOS)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.presentationDetentsIfAvailable()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,12 +336,14 @@ extension ProfileCard {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView()
|
||||||
viewModel.setEnvironments(environments)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
Task {
|
.onAppear {
|
||||||
await viewModel.doReload()
|
viewModel.setEnvironments(environments)
|
||||||
|
Task {
|
||||||
|
await viewModel.doReload()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
FormView {
|
FormView {
|
||||||
if viewModel.profileList.isEmpty {
|
if viewModel.profileList.isEmpty {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@Environment(\.profileEditor) private var profileEditor
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
viewBuilder {
|
||||||
@@ -29,30 +30,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if os(iOS)
|
editorView
|
||||||
RunestoneTextView(
|
|
||||||
text: readOnly ? .constant(viewModel.profileContent) : $viewModel.profileContent,
|
|
||||||
isEditable: !readOnly
|
|
||||||
)
|
|
||||||
.onChangeCompat(of: viewModel.profileContent) {
|
.onChangeCompat(of: viewModel.profileContent) {
|
||||||
viewModel.markAsChanged()
|
viewModel.markAsChanged()
|
||||||
}
|
}
|
||||||
#elseif os(macOS)
|
|
||||||
viewBuilder {
|
|
||||||
if readOnly {
|
|
||||||
TextEditor(text: .constant(viewModel.profileContent))
|
|
||||||
} else {
|
|
||||||
TextEditor(text: $viewModel.profileContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(Font.system(.caption2, design: .monospaced))
|
|
||||||
.autocorrectionDisabled(true)
|
|
||||||
.textContentType(.init(rawValue: ""))
|
|
||||||
.padding()
|
|
||||||
.onChangeCompat(of: viewModel.profileContent) {
|
|
||||||
viewModel.markAsChanged()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alertBinding($viewModel.alert)
|
.alertBinding($viewModel.alert)
|
||||||
@@ -105,6 +86,38 @@
|
|||||||
return String(localized: "Edit Content")
|
return String(localized: "Edit Content")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var editorView: some View {
|
||||||
|
if let profileEditor {
|
||||||
|
profileEditor(
|
||||||
|
readOnly ? .constant(viewModel.profileContent) : $viewModel.profileContent,
|
||||||
|
!readOnly
|
||||||
|
)
|
||||||
|
#if os(macOS)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
defaultEditorView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var defaultEditorView: some View {
|
||||||
|
viewBuilder {
|
||||||
|
if readOnly {
|
||||||
|
TextEditor(text: .constant(viewModel.profileContent))
|
||||||
|
} else {
|
||||||
|
TextEditor(text: $viewModel.profileContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(Font.system(.caption2, design: .monospaced))
|
||||||
|
.autocorrectionDisabled(true)
|
||||||
|
#if os(macOS)
|
||||||
|
.textContentType(.init(rawValue: ""))
|
||||||
|
.padding()
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,6 +21,14 @@
|
|||||||
isChanged = true
|
isChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
isLoading = true
|
||||||
|
profile = nil
|
||||||
|
profileContent = ""
|
||||||
|
isChanged = false
|
||||||
|
alert = nil
|
||||||
|
}
|
||||||
|
|
||||||
public func loadContent() async {
|
public func loadContent() async {
|
||||||
do {
|
do {
|
||||||
try await loadContentBackground()
|
try await loadContentBackground()
|
||||||
|
|||||||
@@ -10,7 +10,16 @@ public struct EditProfileView: View {
|
|||||||
@StateObject private var viewModel = EditProfileViewModel()
|
@StateObject private var viewModel = EditProfileViewModel()
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
macOSBody
|
||||||
|
#else
|
||||||
|
iOSBody
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var formContent: some View {
|
||||||
FormView {
|
FormView {
|
||||||
FormItem(String(localized: "Name")) {
|
FormItem(String(localized: "Name")) {
|
||||||
TextField("Name", text: $profile.name, prompt: Text("Required"))
|
TextField("Name", text: $profile.name, prompt: Text("Required"))
|
||||||
@@ -60,48 +69,61 @@ public struct EditProfileView: View {
|
|||||||
ProfileActionToolbar(profile: profile, viewModel: viewModel)
|
ProfileActionToolbar(profile: profile, viewModel: viewModel)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
|
||||||
.safeAreaInset(edge: .bottom) {
|
|
||||||
ProfileActionToolbar(profile: profile, viewModel: viewModel)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
.onChangeCompat(of: profile.name) {
|
|
||||||
viewModel.markAsChanged()
|
|
||||||
}
|
|
||||||
.onChangeCompat(of: profile.remoteURL) {
|
|
||||||
viewModel.markAsChanged()
|
|
||||||
}
|
|
||||||
.onChangeCompat(of: profile.autoUpdate) {
|
|
||||||
viewModel.markAsChanged()
|
|
||||||
}
|
|
||||||
.disabled(viewModel.isLoading)
|
|
||||||
#if os(macOS)
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItemGroup(placement: .navigation) {
|
|
||||||
Button {
|
|
||||||
viewModel.isLoading = true
|
|
||||||
Task {
|
|
||||||
await viewModel.saveProfile(profile, environments: environments)
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image("save", bundle: ApplicationLibrary.bundle, label: Text("Save"))
|
|
||||||
}
|
|
||||||
.disabled(viewModel.isLoading || !viewModel.isChanged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#elseif os(iOS)
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
|
||||||
Button("Save") {
|
|
||||||
viewModel.isLoading = true
|
|
||||||
Task {
|
|
||||||
await viewModel.saveProfile(profile, environments: environments)
|
|
||||||
}
|
|
||||||
}.disabled(!viewModel.isChanged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
.alertBinding($viewModel.alert)
|
|
||||||
.navigationTitle("Edit Profile")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
private var macOSBody: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text("Edit Profile")
|
||||||
|
.font(.headline)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
.padding(.bottom, 12)
|
||||||
|
|
||||||
|
formContent
|
||||||
|
}
|
||||||
|
.safeAreaInset(edge: .bottom) {
|
||||||
|
ProfileActionToolbar(profile: profile, viewModel: viewModel)
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: profile.name) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: profile.remoteURL) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: profile.autoUpdate) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
.disabled(viewModel.isLoading)
|
||||||
|
.alertBinding($viewModel.alert)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
private var iOSBody: some View {
|
||||||
|
formContent
|
||||||
|
.onChangeCompat(of: profile.name) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: profile.remoteURL) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: profile.autoUpdate) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
.disabled(viewModel.isLoading)
|
||||||
|
#if os(iOS)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("Save") {
|
||||||
|
viewModel.isLoading = true
|
||||||
|
Task {
|
||||||
|
await viewModel.saveProfile(profile, environments: environments)
|
||||||
|
}
|
||||||
|
}.disabled(!viewModel.isChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
.alertBinding($viewModel.alert)
|
||||||
|
.navigationTitle("Edit Profile")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ public struct NewProfileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
macOSBody
|
||||||
|
#else
|
||||||
|
iOSBody
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var formContent: some View {
|
||||||
FormView {
|
FormView {
|
||||||
FormItem(String(localized: "Name")) {
|
FormItem(String(localized: "Name")) {
|
||||||
TextField("Name", text: $viewModel.profileName, prompt: Text("Required"))
|
TextField("Name", text: $viewModel.profileName, prompt: Text("Required"))
|
||||||
@@ -113,8 +121,19 @@ public struct NewProfileView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.navigationTitle("New Profile")
|
}
|
||||||
#if os(macOS)
|
|
||||||
|
#if os(macOS)
|
||||||
|
private var macOSBody: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text("New Profile")
|
||||||
|
.font(.headline)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
.padding(.bottom, 12)
|
||||||
|
|
||||||
|
formContent
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
Button("Cancel") {
|
Button("Cancel") {
|
||||||
@@ -138,10 +157,8 @@ public struct NewProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
.disabled(viewModel.isSaving)
|
.disabled(viewModel.isSaving)
|
||||||
.alertBinding($viewModel.alert)
|
.alertBinding($viewModel.alert)
|
||||||
#if os(iOS) || os(macOS)
|
|
||||||
.fileImporter(
|
.fileImporter(
|
||||||
isPresented: $viewModel.pickerPresented,
|
isPresented: $viewModel.pickerPresented,
|
||||||
allowedContentTypes: [.json],
|
allowedContentTypes: [.json],
|
||||||
@@ -157,6 +174,30 @@ public struct NewProfileView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
}
|
#else
|
||||||
|
private var iOSBody: some View {
|
||||||
|
formContent
|
||||||
|
.navigationTitle("New Profile")
|
||||||
|
.disabled(viewModel.isSaving)
|
||||||
|
.alertBinding($viewModel.alert)
|
||||||
|
#if os(iOS)
|
||||||
|
.fileImporter(
|
||||||
|
isPresented: $viewModel.pickerPresented,
|
||||||
|
allowedContentTypes: [.json],
|
||||||
|
allowsMultipleSelection: false
|
||||||
|
) { result in
|
||||||
|
do {
|
||||||
|
let urls = try result.get()
|
||||||
|
if !urls.isEmpty {
|
||||||
|
viewModel.fileURL = urls[0]
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
viewModel.alert = Alert(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import SwiftUI
|
|||||||
public struct ProfileActionToolbar: View {
|
public struct ProfileActionToolbar: View {
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
#if os(macOS)
|
||||||
|
@Environment(\.openWindow) private var openWindow
|
||||||
|
#endif
|
||||||
@ObservedObject private var profile: Profile
|
@ObservedObject private var profile: Profile
|
||||||
@ObservedObject private var viewModel: EditProfileViewModel
|
@ObservedObject private var viewModel: EditProfileViewModel
|
||||||
|
|
||||||
@@ -69,16 +72,12 @@ public struct ProfileActionToolbar: View {
|
|||||||
|
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
if profile.type != .remote {
|
if profile.type != .remote {
|
||||||
NavigationLink {
|
Button("Edit Content") {
|
||||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
openWindow(value: EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
||||||
} label: {
|
|
||||||
Text("Edit Content")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NavigationLink {
|
Button("View Content") {
|
||||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
openWindow(value: EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
||||||
} label: {
|
|
||||||
Text("View Content")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@@ -92,14 +91,27 @@ public struct ProfileActionToolbar: View {
|
|||||||
.disabled(viewModel.isLoading)
|
.disabled(viewModel.isLoading)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button("Delete", role: .destructive) {
|
Button("Delete", role: .destructive) {
|
||||||
Task {
|
Task {
|
||||||
await viewModel.deleteProfile(profile, environments: environments, dismiss: dismiss)
|
await viewModel.deleteProfile(profile, environments: environments, dismiss: dismiss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button("Cancel") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Save") {
|
||||||
|
viewModel.isLoading = true
|
||||||
|
Task {
|
||||||
|
await viewModel.saveProfile(profile, environments: environments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.disabled(viewModel.isLoading || !viewModel.isChanged)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color(NSColor.controlBackgroundColor))
|
.background(Color(NSColor.controlBackgroundColor))
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#if os(iOS) || os(macOS)
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct ProfileEditorKey: EnvironmentKey {
|
||||||
|
public static let defaultValue: ((Binding<String>, Bool) -> AnyView)? = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension EnvironmentValues {
|
||||||
|
var profileEditor: ((Binding<String>, Bool) -> AnyView)? {
|
||||||
|
get { self[ProfileEditorKey.self] }
|
||||||
|
set { self[ProfileEditorKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#if os(iOS)
|
|
||||||
import Runestone
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
final class ProfileEditorTheme: Theme {
|
|
||||||
let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
|
||||||
let textColor: UIColor = .label
|
|
||||||
|
|
||||||
let gutterBackgroundColor: UIColor = .secondarySystemBackground
|
|
||||||
let gutterHairlineColor: UIColor = .separator
|
|
||||||
|
|
||||||
let lineNumberColor: UIColor = .secondaryLabel
|
|
||||||
let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
|
||||||
|
|
||||||
let selectedLineBackgroundColor: UIColor = .systemFill
|
|
||||||
let selectedLinesLineNumberColor: UIColor = .label
|
|
||||||
let selectedLinesGutterBackgroundColor: UIColor = .secondarySystemBackground
|
|
||||||
|
|
||||||
let invisibleCharactersColor: UIColor = .tertiaryLabel
|
|
||||||
|
|
||||||
let pageGuideHairlineColor: UIColor = .separator
|
|
||||||
let pageGuideBackgroundColor: UIColor = .secondarySystemBackground
|
|
||||||
|
|
||||||
let markedTextBackgroundColor: UIColor = .systemFill
|
|
||||||
let markedTextBackgroundCornerRadius: CGFloat = 4
|
|
||||||
|
|
||||||
func textColor(for rawHighlightName: String) -> UIColor? {
|
|
||||||
guard let highlightName = HighlightName(rawHighlightName) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch highlightName {
|
|
||||||
case .comment:
|
|
||||||
return .secondaryLabel
|
|
||||||
case .property:
|
|
||||||
return .systemCyan
|
|
||||||
case .string:
|
|
||||||
return .systemGreen
|
|
||||||
case .number:
|
|
||||||
return .systemOrange
|
|
||||||
case .constantBuiltin:
|
|
||||||
return .systemPurple
|
|
||||||
case .error:
|
|
||||||
return .systemRed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fontTraits(for _: String) -> FontTraits {
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum HighlightName: String {
|
|
||||||
case comment
|
|
||||||
case property
|
|
||||||
case string
|
|
||||||
case number
|
|
||||||
case constantBuiltin = "constant.builtin"
|
|
||||||
case error
|
|
||||||
|
|
||||||
init?(_ rawHighlightName: String) {
|
|
||||||
var components = rawHighlightName.split(separator: ".")
|
|
||||||
while !components.isEmpty {
|
|
||||||
let candidateRawHighlightName = components.joined(separator: ".")
|
|
||||||
if let highlightName = Self(rawValue: candidateRawHighlightName) {
|
|
||||||
self = highlightName
|
|
||||||
return
|
|
||||||
}
|
|
||||||
components.removeLast()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -18,14 +18,14 @@ public enum SheetSize {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct NavigationSheet<Content: View>: View {
|
public struct NavigationSheet<Content: View>: View {
|
||||||
private let title: String
|
private let title: String?
|
||||||
private let size: SheetSize
|
private let size: SheetSize
|
||||||
private let showDoneButton: Bool
|
private let showDoneButton: Bool
|
||||||
private let onDismiss: (() -> Void)?
|
private let onDismiss: (() -> Void)?
|
||||||
private let content: () -> Content
|
private let content: () -> Content
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
title: String,
|
title: String? = nil,
|
||||||
size: SheetSize = .large,
|
size: SheetSize = .large,
|
||||||
showDoneButton: Bool = false,
|
showDoneButton: Bool = false,
|
||||||
onDismiss: (() -> Void)? = nil,
|
onDismiss: (() -> Void)? = nil,
|
||||||
@@ -39,13 +39,25 @@ public struct NavigationSheet<Content: View>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStackCompat {
|
#if os(macOS)
|
||||||
content()
|
macOSBody
|
||||||
.navigationTitle(title)
|
#else
|
||||||
#if os(iOS)
|
iOSBody
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
#endif
|
||||||
#endif
|
}
|
||||||
#if os(macOS)
|
|
||||||
|
#if os(macOS)
|
||||||
|
private var macOSBody: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
if let title {
|
||||||
|
Text(title)
|
||||||
|
.font(.headline)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
.padding(.bottom, 12)
|
||||||
|
}
|
||||||
|
content()
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if showDoneButton {
|
if showDoneButton {
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
@@ -55,12 +67,19 @@ public struct NavigationSheet<Content: View>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#if os(iOS) || os(tvOS)
|
#else
|
||||||
.sheetDetent(size)
|
private var iOSBody: some View {
|
||||||
#endif
|
NavigationStackCompat {
|
||||||
}
|
content()
|
||||||
|
.navigationTitle(title ?? "")
|
||||||
|
#if os(iOS)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.sheetDetent(size)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
#if os(iOS)
|
|
||||||
import Runestone
|
|
||||||
import SwiftUI
|
|
||||||
import TreeSitterJSON5Runestone
|
|
||||||
|
|
||||||
struct RunestoneTextView: UIViewRepresentable {
|
|
||||||
@Binding var text: String
|
|
||||||
let isEditable: Bool
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> TextView {
|
|
||||||
let textView = TextView()
|
|
||||||
|
|
||||||
textView.showLineNumbers = true
|
|
||||||
textView.isLineWrappingEnabled = false
|
|
||||||
textView.showTabs = false
|
|
||||||
textView.showSpaces = false
|
|
||||||
textView.showLineBreaks = false
|
|
||||||
textView.showSoftLineBreaks = false
|
|
||||||
textView.showNonBreakingSpaces = false
|
|
||||||
|
|
||||||
textView.autocorrectionType = .no
|
|
||||||
textView.autocapitalizationType = .none
|
|
||||||
textView.smartDashesType = .no
|
|
||||||
textView.smartQuotesType = .no
|
|
||||||
textView.smartInsertDeleteType = .no
|
|
||||||
|
|
||||||
textView.backgroundColor = .secondarySystemGroupedBackground
|
|
||||||
textView.contentInsetAdjustmentBehavior = .always
|
|
||||||
textView.alwaysBounceVertical = true
|
|
||||||
|
|
||||||
textView.kern = 0.3
|
|
||||||
textView.lineHeightMultiplier = 1.3
|
|
||||||
|
|
||||||
textView.characterPairs = [
|
|
||||||
BasicCharacterPair(leading: "{", trailing: "}"),
|
|
||||||
BasicCharacterPair(leading: "[", trailing: "]"),
|
|
||||||
BasicCharacterPair(leading: "\"", trailing: "\""),
|
|
||||||
]
|
|
||||||
|
|
||||||
let theme = ProfileEditorTheme()
|
|
||||||
let state = TextViewState(text: text, theme: theme, language: .json5)
|
|
||||||
textView.setState(state)
|
|
||||||
|
|
||||||
textView.isEditable = isEditable
|
|
||||||
textView.editorDelegate = context.coordinator
|
|
||||||
|
|
||||||
return textView
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ textView: TextView, context _: Context) {
|
|
||||||
if textView.text != text {
|
|
||||||
textView.text = text
|
|
||||||
}
|
|
||||||
if textView.isEditable != isEditable {
|
|
||||||
textView.isEditable = isEditable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
|
||||||
Coordinator(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
final class Coordinator: TextViewDelegate {
|
|
||||||
var parent: RunestoneTextView
|
|
||||||
|
|
||||||
init(_ parent: RunestoneTextView) {
|
|
||||||
self.parent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
func textViewDidChange(_ textView: TextView) {
|
|
||||||
parent.text = textView.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class BasicCharacterPair: CharacterPair {
|
|
||||||
let leading: String
|
|
||||||
let trailing: String
|
|
||||||
|
|
||||||
init(leading: String, trailing: String) {
|
|
||||||
self.leading = leading
|
|
||||||
self.trailing = trailing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
Submodule
+1
Submodule Frameworks/Runestone added at b3ef61c54b
@@ -13,7 +13,7 @@ let package = Package(
|
|||||||
.library(name: "TreeSitterJSON5Runestone", targets: ["TreeSitterJSON5Runestone"]),
|
.library(name: "TreeSitterJSON5Runestone", targets: ["TreeSitterJSON5Runestone"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/simonbs/Runestone", from: "0.3.0"),
|
.package(path: "../Runestone"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(name: "TreeSitterJSON5", cSettings: [.headerSearchPath("src")]),
|
.target(name: "TreeSitterJSON5", cSettings: [.headerSearchPath("src")]),
|
||||||
|
|||||||
@@ -195,6 +195,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Cancel" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Chain" : {
|
"Chain" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -507,6 +510,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Do you want to save the changes you made?" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Documentation" : {
|
"Documentation" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -527,6 +533,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Don't Save" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Done" : {
|
"Done" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -1017,6 +1026,9 @@
|
|||||||
},
|
},
|
||||||
"Machine: " : {
|
"Machine: " : {
|
||||||
"shouldTranslate" : false
|
"shouldTranslate" : false
|
||||||
|
},
|
||||||
|
"Manage profiles" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Match Rule" : {
|
"Match Rule" : {
|
||||||
"shouldTranslate" : false
|
"shouldTranslate" : false
|
||||||
@@ -1295,6 +1307,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Profiles" : {
|
"Profiles" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -1746,6 +1759,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Unsaved Changes" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Update" : {
|
"Update" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import AppKit
|
||||||
|
import CodeEditLanguages
|
||||||
|
import CodeEditSourceEditor
|
||||||
|
import CodeEditTextView
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private extension NSColor {
|
||||||
|
var forEditor: NSColor {
|
||||||
|
usingColorSpace(.sRGB) ?? self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeTheme() -> EditorTheme {
|
||||||
|
EditorTheme(
|
||||||
|
text: .init(color: NSColor.labelColor.forEditor),
|
||||||
|
insertionPoint: NSColor.labelColor.forEditor,
|
||||||
|
invisibles: .init(color: NSColor.tertiaryLabelColor.forEditor),
|
||||||
|
background: NSColor.textBackgroundColor.forEditor,
|
||||||
|
lineHighlight: NSColor.quaternaryLabelColor.forEditor,
|
||||||
|
selection: NSColor.selectedTextBackgroundColor.forEditor,
|
||||||
|
keywords: .init(color: NSColor.systemPurple.forEditor),
|
||||||
|
commands: .init(color: NSColor.systemCyan.forEditor),
|
||||||
|
types: .init(color: NSColor.systemCyan.forEditor),
|
||||||
|
attributes: .init(color: NSColor.systemCyan.forEditor),
|
||||||
|
variables: .init(color: NSColor.labelColor.forEditor),
|
||||||
|
values: .init(color: NSColor.systemOrange.forEditor),
|
||||||
|
numbers: .init(color: NSColor.systemOrange.forEditor),
|
||||||
|
strings: .init(color: NSColor.systemGreen.forEditor),
|
||||||
|
characters: .init(color: NSColor.systemGreen.forEditor),
|
||||||
|
comments: .init(color: NSColor.secondaryLabelColor.forEditor)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeConfiguration(isEditable: Bool) -> SourceEditorConfiguration {
|
||||||
|
SourceEditorConfiguration(
|
||||||
|
appearance: .init(
|
||||||
|
theme: makeTheme(),
|
||||||
|
font: .monospacedSystemFont(ofSize: 14, weight: .regular),
|
||||||
|
lineHeightMultiple: 1.3,
|
||||||
|
wrapLines: false
|
||||||
|
),
|
||||||
|
behavior: .init(
|
||||||
|
isEditable: isEditable,
|
||||||
|
isSelectable: true
|
||||||
|
),
|
||||||
|
peripherals: .init(
|
||||||
|
showMinimap: false,
|
||||||
|
showFoldingRibbon: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CodeEditTextView: NSViewRepresentable {
|
||||||
|
@Binding var text: String
|
||||||
|
let isEditable: Bool
|
||||||
|
|
||||||
|
func makeNSView(context: Context) -> NSView {
|
||||||
|
let controller = TextViewController(
|
||||||
|
string: text,
|
||||||
|
language: .json,
|
||||||
|
configuration: makeConfiguration(isEditable: isEditable),
|
||||||
|
cursorPositions: []
|
||||||
|
)
|
||||||
|
controller.loadView()
|
||||||
|
|
||||||
|
let containerView = NSView()
|
||||||
|
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
let controllerView = controller.view
|
||||||
|
controllerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
containerView.addSubview(controllerView)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
controllerView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
||||||
|
controllerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||||
|
controllerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
|
||||||
|
controllerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
context.coordinator.controller = controller
|
||||||
|
context.coordinator.setupObservation()
|
||||||
|
return containerView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_: NSView, context: Context) {
|
||||||
|
guard let controller = context.coordinator.controller else { return }
|
||||||
|
if controller.text != text {
|
||||||
|
controller.text = text
|
||||||
|
}
|
||||||
|
if controller.configuration.behavior.isEditable != isEditable {
|
||||||
|
controller.configuration = makeConfiguration(isEditable: isEditable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(text: $text)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator: NSObject {
|
||||||
|
var controller: TextViewController?
|
||||||
|
@Binding var text: String
|
||||||
|
private var observation: NSObjectProtocol?
|
||||||
|
|
||||||
|
init(text: Binding<String>) {
|
||||||
|
_text = text
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupObservation() {
|
||||||
|
guard let controller else { return }
|
||||||
|
observation = NotificationCenter.default.addObserver(
|
||||||
|
forName: TextView.textDidChangeNotification,
|
||||||
|
object: controller.textView,
|
||||||
|
queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
guard let self, let controller = self.controller else { return }
|
||||||
|
self.text = controller.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let observation {
|
||||||
|
NotificationCenter.default.removeObserver(observation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
import ApplicationLibrary
|
||||||
|
import Library
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct EditProfileContentWindow: View {
|
||||||
|
let context: EditProfileContentView.Context?
|
||||||
|
|
||||||
|
@StateObject private var viewModel: EditProfileContentViewModel
|
||||||
|
@State private var showDiscardAlert = false
|
||||||
|
@State private var windowState = WindowState()
|
||||||
|
|
||||||
|
private let readOnly: Bool
|
||||||
|
|
||||||
|
init(context: EditProfileContentView.Context?) {
|
||||||
|
self.context = context
|
||||||
|
readOnly = context?.readOnly == true
|
||||||
|
_viewModel = StateObject(wrappedValue: EditProfileContentViewModel(profileID: context?.profileID))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Environment(\.profileEditor) private var profileEditor
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if viewModel.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.task {
|
||||||
|
await viewModel.loadContent()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editorView
|
||||||
|
.onChangeCompat(of: viewModel.profileContent) {
|
||||||
|
viewModel.markAsChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(minWidth: 600, minHeight: 400)
|
||||||
|
.background(WindowAccessor { window in
|
||||||
|
guard let window else { return }
|
||||||
|
if windowState.window == nil {
|
||||||
|
windowState.window = window
|
||||||
|
windowState.onClose = { [weak viewModel] in
|
||||||
|
viewModel?.reset()
|
||||||
|
}
|
||||||
|
let delegate = WindowCloseDelegate(
|
||||||
|
windowState: windowState,
|
||||||
|
hasUnsavedChanges: { [weak viewModel] in
|
||||||
|
viewModel?.isChanged == true
|
||||||
|
},
|
||||||
|
showAlert: {
|
||||||
|
showDiscardAlert = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
windowState.delegate = delegate
|
||||||
|
window.delegate = delegate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onExitCommand {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
.alert("Unsaved Changes", isPresented: $showDiscardAlert) {
|
||||||
|
Button("Don't Save", role: .destructive) {
|
||||||
|
windowState.forceClose()
|
||||||
|
}
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
if !readOnly {
|
||||||
|
Button("Save") {
|
||||||
|
Task {
|
||||||
|
await viewModel.saveContent()
|
||||||
|
if viewModel.alert == nil {
|
||||||
|
windowState.forceClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("Do you want to save the changes you made?")
|
||||||
|
}
|
||||||
|
.alertBinding($viewModel.alert)
|
||||||
|
.navigationTitle(navigationTitle)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .navigation) {
|
||||||
|
if !readOnly {
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
await viewModel.saveContent()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Save", image: "save")
|
||||||
|
}
|
||||||
|
.keyboardShortcut("s", modifiers: .command)
|
||||||
|
.disabled(!viewModel.isChanged)
|
||||||
|
} else {
|
||||||
|
Button {
|
||||||
|
NSPasteboard.general.setString(viewModel.profileContent, forType: .string)
|
||||||
|
} label: {
|
||||||
|
Label("Copy", systemImage: "doc.on.clipboard")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var navigationTitle: String {
|
||||||
|
if readOnly {
|
||||||
|
return String(localized: "View Content")
|
||||||
|
} else {
|
||||||
|
return String(localized: "Edit Content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var editorView: some View {
|
||||||
|
if let profileEditor {
|
||||||
|
profileEditor(
|
||||||
|
readOnly ? .constant(viewModel.profileContent) : $viewModel.profileContent,
|
||||||
|
!readOnly
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
} else {
|
||||||
|
defaultEditorView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var defaultEditorView: some View {
|
||||||
|
Group {
|
||||||
|
if readOnly {
|
||||||
|
TextEditor(text: .constant(viewModel.profileContent))
|
||||||
|
} else {
|
||||||
|
TextEditor(text: $viewModel.profileContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(Font.system(.caption2, design: .monospaced))
|
||||||
|
.autocorrectionDisabled(true)
|
||||||
|
.textContentType(.init(rawValue: ""))
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleClose() {
|
||||||
|
if viewModel.isChanged, !readOnly {
|
||||||
|
showDiscardAlert = true
|
||||||
|
} else {
|
||||||
|
windowState.forceClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WindowState {
|
||||||
|
weak var window: NSWindow?
|
||||||
|
var delegate: WindowCloseDelegate?
|
||||||
|
var onClose: (() -> Void)?
|
||||||
|
|
||||||
|
func forceClose() {
|
||||||
|
delegate?.allowClose = true
|
||||||
|
window?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct WindowAccessor: NSViewRepresentable {
|
||||||
|
let callback: (NSWindow?) -> Void
|
||||||
|
|
||||||
|
func makeNSView(context _: Context) -> NSView {
|
||||||
|
let view = NSView()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
callback(view.window)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_: NSView, context _: Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WindowCloseDelegate: NSObject, NSWindowDelegate {
|
||||||
|
var allowClose = false
|
||||||
|
private let windowState: WindowState
|
||||||
|
private let hasUnsavedChanges: () -> Bool
|
||||||
|
private let showAlert: () -> Void
|
||||||
|
|
||||||
|
init(windowState: WindowState, hasUnsavedChanges: @escaping () -> Bool, showAlert: @escaping () -> Void) {
|
||||||
|
self.windowState = windowState
|
||||||
|
self.hasUnsavedChanges = hasUnsavedChanges
|
||||||
|
self.showAlert = showAlert
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowShouldClose(_: NSWindow) -> Bool {
|
||||||
|
if allowClose {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if hasUnsavedChanges() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.showAlert()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowWillClose(_: Notification) {
|
||||||
|
windowState.onClose?()
|
||||||
|
allowClose = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,10 @@ public struct MacApplication: Scene {
|
|||||||
@State private var isMenuPresented = false
|
@State private var isMenuPresented = false
|
||||||
@StateObject private var environments = ExtensionEnvironments()
|
@StateObject private var environments = ExtensionEnvironments()
|
||||||
|
|
||||||
|
private let profileEditor: (Binding<String>, Bool) -> AnyView = { text, isEditable in
|
||||||
|
AnyView(CodeEditTextView(text: text, isEditable: isEditable))
|
||||||
|
}
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some Scene {
|
public var body: some Scene {
|
||||||
Window("sing-box", id: "main", content: {
|
Window("sing-box", id: "main", content: {
|
||||||
@@ -52,6 +56,13 @@ public struct MacApplication: Scene {
|
|||||||
}
|
}
|
||||||
.menuBarExtraStyle(.window)
|
.menuBarExtraStyle(.window)
|
||||||
.menuBarExtraAccess(isPresented: $isMenuPresented)
|
.menuBarExtraAccess(isPresented: $isMenuPresented)
|
||||||
|
|
||||||
|
WindowGroup(for: EditProfileContentView.Context.self) { $context in
|
||||||
|
EditProfileContentWindow(context: context)
|
||||||
|
.environment(\.profileEditor, profileEditor)
|
||||||
|
}
|
||||||
|
.windowResizability(.contentMinSize)
|
||||||
|
.defaultSize(width: 700, height: 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func initialize() async {
|
private func initialize() async {
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ public struct MainView: View {
|
|||||||
@State private var showCardManagement = false
|
@State private var showCardManagement = false
|
||||||
@State private var cardConfigurationVersion = 0
|
@State private var cardConfigurationVersion = 0
|
||||||
|
|
||||||
|
private let profileEditor: (Binding<String>, Bool) -> AnyView = { text, isEditable in
|
||||||
|
AnyView(CodeEditTextView(text: text, isEditable: isEditable))
|
||||||
|
}
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
@@ -59,6 +63,7 @@ public struct MainView: View {
|
|||||||
.environment(\.selection, $viewModel.selection)
|
.environment(\.selection, $viewModel.selection)
|
||||||
.environment(\.importProfile, $viewModel.importProfile)
|
.environment(\.importProfile, $viewModel.importProfile)
|
||||||
.environment(\.importRemoteProfile, $viewModel.importRemoteProfile)
|
.environment(\.importRemoteProfile, $viewModel.importRemoteProfile)
|
||||||
|
.environment(\.profileEditor, profileEditor)
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||||
.onOpenURL(perform: viewModel.openURL)
|
.onOpenURL(perform: viewModel.openURL)
|
||||||
.sheet(isPresented: $showCardManagement) {
|
.sheet(isPresented: $showCardManagement) {
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ struct MainView: View {
|
|||||||
@State private var showConnections = false
|
@State private var showConnections = false
|
||||||
@State private var buttonState = ButtonVisibilityState()
|
@State private var buttonState = ButtonVisibilityState()
|
||||||
|
|
||||||
|
private let profileEditor: (Binding<String>, Bool) -> AnyView = { text, isEditable in
|
||||||
|
AnyView(RunestoneTextView(text: text, isEditable: isEditable))
|
||||||
|
}
|
||||||
|
|
||||||
private var shouldShowBottomAccessory: Bool {
|
private var shouldShowBottomAccessory: Bool {
|
||||||
guard !environments.extensionProfileLoading else {
|
guard !environments.extensionProfileLoading else {
|
||||||
return false
|
return false
|
||||||
@@ -124,6 +128,7 @@ struct MainView: View {
|
|||||||
.environment(\.selection, $selection)
|
.environment(\.selection, $selection)
|
||||||
.environment(\.importProfile, $importProfile)
|
.environment(\.importProfile, $importProfile)
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||||
|
.environment(\.profileEditor, profileEditor)
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||||
.onOpenURL(perform: openURL)
|
.onOpenURL(perform: openURL)
|
||||||
.sheet(isPresented: $showGroups) {
|
.sheet(isPresented: $showGroups) {
|
||||||
@@ -160,6 +165,7 @@ struct MainView: View {
|
|||||||
.environment(\.selection, $selection)
|
.environment(\.selection, $selection)
|
||||||
.environment(\.importProfile, $importProfile)
|
.environment(\.importProfile, $importProfile)
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||||
|
.environment(\.profileEditor, profileEditor)
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||||
.onOpenURL(perform: openURL)
|
.onOpenURL(perform: openURL)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import Runestone
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class ProfileEditorTheme: Theme {
|
||||||
|
let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
||||||
|
let textColor: UIColor = .label
|
||||||
|
|
||||||
|
let gutterBackgroundColor: UIColor = .secondarySystemBackground
|
||||||
|
let gutterHairlineColor: UIColor = .separator
|
||||||
|
|
||||||
|
let lineNumberColor: UIColor = .secondaryLabel
|
||||||
|
let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
||||||
|
|
||||||
|
let selectedLineBackgroundColor: UIColor = .systemFill
|
||||||
|
let selectedLinesLineNumberColor: UIColor = .label
|
||||||
|
let selectedLinesGutterBackgroundColor: UIColor = .secondarySystemBackground
|
||||||
|
|
||||||
|
let invisibleCharactersColor: UIColor = .tertiaryLabel
|
||||||
|
|
||||||
|
let pageGuideHairlineColor: UIColor = .separator
|
||||||
|
let pageGuideBackgroundColor: UIColor = .secondarySystemBackground
|
||||||
|
|
||||||
|
let markedTextBackgroundColor: UIColor = .systemFill
|
||||||
|
let markedTextBackgroundCornerRadius: CGFloat = 4
|
||||||
|
|
||||||
|
func textColor(for rawHighlightName: String) -> UIColor? {
|
||||||
|
guard let highlightName = HighlightName(rawHighlightName) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch highlightName {
|
||||||
|
case .comment:
|
||||||
|
return .secondaryLabel
|
||||||
|
case .property:
|
||||||
|
return .systemCyan
|
||||||
|
case .string:
|
||||||
|
return .systemGreen
|
||||||
|
case .number:
|
||||||
|
return .systemOrange
|
||||||
|
case .constantBuiltin:
|
||||||
|
return .systemPurple
|
||||||
|
case .error:
|
||||||
|
return .systemRed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fontTraits(for _: String) -> FontTraits {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum HighlightName: String {
|
||||||
|
case comment
|
||||||
|
case property
|
||||||
|
case string
|
||||||
|
case number
|
||||||
|
case constantBuiltin = "constant.builtin"
|
||||||
|
case error
|
||||||
|
|
||||||
|
init?(_ rawHighlightName: String) {
|
||||||
|
var components = rawHighlightName.split(separator: ".")
|
||||||
|
while !components.isEmpty {
|
||||||
|
let candidateRawHighlightName = components.joined(separator: ".")
|
||||||
|
if let highlightName = Self(rawValue: candidateRawHighlightName) {
|
||||||
|
self = highlightName
|
||||||
|
return
|
||||||
|
}
|
||||||
|
components.removeLast()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import Runestone
|
||||||
|
import SwiftUI
|
||||||
|
import TreeSitterJSON5Runestone
|
||||||
|
|
||||||
|
struct RunestoneTextView: UIViewRepresentable {
|
||||||
|
@Binding var text: String
|
||||||
|
let isEditable: Bool
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> TextView {
|
||||||
|
let textView = TextView()
|
||||||
|
|
||||||
|
textView.showLineNumbers = true
|
||||||
|
textView.isLineWrappingEnabled = false
|
||||||
|
textView.showTabs = false
|
||||||
|
textView.showSpaces = false
|
||||||
|
textView.showLineBreaks = false
|
||||||
|
textView.showSoftLineBreaks = false
|
||||||
|
textView.showNonBreakingSpaces = false
|
||||||
|
|
||||||
|
textView.autocorrectionType = .no
|
||||||
|
textView.autocapitalizationType = .none
|
||||||
|
textView.smartDashesType = .no
|
||||||
|
textView.smartQuotesType = .no
|
||||||
|
textView.smartInsertDeleteType = .no
|
||||||
|
|
||||||
|
textView.backgroundColor = .secondarySystemGroupedBackground
|
||||||
|
textView.contentInsetAdjustmentBehavior = .always
|
||||||
|
textView.alwaysBounceVertical = true
|
||||||
|
|
||||||
|
textView.kern = 0.3
|
||||||
|
textView.lineHeightMultiplier = 1.3
|
||||||
|
|
||||||
|
textView.characterPairs = [
|
||||||
|
BasicCharacterPair(leading: "{", trailing: "}"),
|
||||||
|
BasicCharacterPair(leading: "[", trailing: "]"),
|
||||||
|
BasicCharacterPair(leading: "\"", trailing: "\""),
|
||||||
|
]
|
||||||
|
|
||||||
|
let theme = ProfileEditorTheme()
|
||||||
|
let state = TextViewState(text: text, theme: theme, language: .json5)
|
||||||
|
textView.setState(state)
|
||||||
|
|
||||||
|
textView.isEditable = isEditable
|
||||||
|
textView.editorDelegate = context.coordinator
|
||||||
|
|
||||||
|
return textView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ textView: TextView, context _: Context) {
|
||||||
|
if textView.text != text {
|
||||||
|
textView.text = text
|
||||||
|
}
|
||||||
|
if textView.isEditable != isEditable {
|
||||||
|
textView.isEditable = isEditable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Coordinator: TextViewDelegate {
|
||||||
|
var parent: RunestoneTextView
|
||||||
|
|
||||||
|
init(_ parent: RunestoneTextView) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func textViewDidChange(_ textView: TextView) {
|
||||||
|
parent.text = textView.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class BasicCharacterPair: CharacterPair {
|
||||||
|
let leading: String
|
||||||
|
let trailing: String
|
||||||
|
|
||||||
|
init(leading: String, trailing: String) {
|
||||||
|
self.leading = leading
|
||||||
|
self.trailing = trailing
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||||
3A9759212A4EB69C00E4404B /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
3A9759212A4EB69C00E4404B /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
3AC194492A50013F00BD8CB9 /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
3AC194492A50013F00BD8CB9 /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
3ACE5E032EE1A91300644196 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 3ACE5E022EE1A91200644196 /* CodeEditSourceEditor */; };
|
||||||
3AE1719A2A8128DD00393060 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF342B12A4AA520002B34AC /* NetworkExtension.framework */; };
|
3AE1719A2A8128DD00393060 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF342B12A4AA520002B34AC /* NetworkExtension.framework */; };
|
||||||
3AE171A62A81294400393060 /* TVExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AE171992A8128DD00393060 /* TVExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
3AE171A62A81294400393060 /* TVExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AE171992A8128DD00393060 /* TVExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
3AE171A92A81297300393060 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
3AE171A92A81297300393060 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||||
@@ -536,8 +537,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
3A4EAD1B2A4FEB02005435B3 /* Library.framework in Frameworks */,
|
3A4EAD1B2A4FEB02005435B3 /* Library.framework in Frameworks */,
|
||||||
3A2E87FB2ED5ABDA00644195 /* TreeSitterJSON5Runestone in Frameworks */,
|
|
||||||
3A2E87F22ED5A91100644195 /* Runestone in Frameworks */,
|
|
||||||
3A4A020D2B53E3DC004EFB87 /* QRCode in Frameworks */,
|
3A4A020D2B53E3DC004EFB87 /* QRCode in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -585,6 +584,8 @@
|
|||||||
files = (
|
files = (
|
||||||
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */,
|
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */,
|
||||||
3A4EAD372A4FEC20005435B3 /* ApplicationLibrary.framework in Frameworks */,
|
3A4EAD372A4FEC20005435B3 /* ApplicationLibrary.framework in Frameworks */,
|
||||||
|
3A2E87F22ED5A91100644195 /* Runestone in Frameworks */,
|
||||||
|
3A2E87FB2ED5ABDA00644195 /* TreeSitterJSON5Runestone in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -633,6 +634,7 @@
|
|||||||
files = (
|
files = (
|
||||||
3AEECC452A6DFE61006A0E0C /* ApplicationLibrary.framework in Frameworks */,
|
3AEECC452A6DFE61006A0E0C /* ApplicationLibrary.framework in Frameworks */,
|
||||||
3AEECC4E2A6DFF13006A0E0C /* MacControlCenterUI in Frameworks */,
|
3AEECC4E2A6DFF13006A0E0C /* MacControlCenterUI in Frameworks */,
|
||||||
|
3ACE5E032EE1A91300644196 /* CodeEditSourceEditor in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -758,8 +760,6 @@
|
|||||||
name = ApplicationLibrary;
|
name = ApplicationLibrary;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
3A4A020C2B53E3DC004EFB87 /* QRCode */,
|
3A4A020C2B53E3DC004EFB87 /* QRCode */,
|
||||||
3A2E87F12ED5A91100644195 /* Runestone */,
|
|
||||||
3A2E87FA2ED5ABDA00644195 /* TreeSitterJSON5Runestone */,
|
|
||||||
);
|
);
|
||||||
productName = ApplicationLibrary;
|
productName = ApplicationLibrary;
|
||||||
productReference = 3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */;
|
productReference = 3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */;
|
||||||
@@ -872,6 +872,8 @@
|
|||||||
);
|
);
|
||||||
name = SFI;
|
name = SFI;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
3A2E87F12ED5A91100644195 /* Runestone */,
|
||||||
|
3A2E87FA2ED5ABDA00644195 /* TreeSitterJSON5Runestone */,
|
||||||
);
|
);
|
||||||
productName = SFI;
|
productName = SFI;
|
||||||
productReference = 3AEC20F32A459AB400A63465 /* sing-box.app */;
|
productReference = 3AEC20F32A459AB400A63465 /* sing-box.app */;
|
||||||
@@ -992,6 +994,7 @@
|
|||||||
name = MacLibrary;
|
name = MacLibrary;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
3AEECC4D2A6DFF13006A0E0C /* MacControlCenterUI */,
|
3AEECC4D2A6DFF13006A0E0C /* MacControlCenterUI */,
|
||||||
|
3ACE5E022EE1A91200644196 /* CodeEditSourceEditor */,
|
||||||
);
|
);
|
||||||
productName = MacLibrary;
|
productName = MacLibrary;
|
||||||
productReference = 3AEECC2F2A6DFDAD006A0E0C /* MacLibrary.framework */;
|
productReference = 3AEECC2F2A6DFDAD006A0E0C /* MacLibrary.framework */;
|
||||||
@@ -1064,8 +1067,9 @@
|
|||||||
3A017F902A4AB2E4009149FA /* XCRemoteSwiftPackageReference "GRDB" */,
|
3A017F902A4AB2E4009149FA /* XCRemoteSwiftPackageReference "GRDB" */,
|
||||||
3A57DF3A2A4D705000690BC5 /* XCRemoteSwiftPackageReference "MacControlCenterUI" */,
|
3A57DF3A2A4D705000690BC5 /* XCRemoteSwiftPackageReference "MacControlCenterUI" */,
|
||||||
3A4A020B2B53E3DC004EFB87 /* XCRemoteSwiftPackageReference "qrcode" */,
|
3A4A020B2B53E3DC004EFB87 /* XCRemoteSwiftPackageReference "qrcode" */,
|
||||||
3A2E87F02ED5A91100644195 /* XCRemoteSwiftPackageReference "Runestone" */,
|
3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */,
|
||||||
3A2E87F92ED5ABCF00644195 /* XCLocalSwiftPackageReference "Frameworks/TreeSitterJSON5" */,
|
3A2E87F92ED5ABCF00644195 /* XCLocalSwiftPackageReference "Frameworks/TreeSitterJSON5" */,
|
||||||
|
3ACE5E012EE1A91100644196 /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 3AEC20C72A45991900A63465 /* Products */;
|
productRefGroup = 3AEC20C72A45991900A63465 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -2562,6 +2566,10 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCLocalSwiftPackageReference section */
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = Frameworks/Runestone;
|
||||||
|
};
|
||||||
3A2E87F92ED5ABCF00644195 /* XCLocalSwiftPackageReference "Frameworks/TreeSitterJSON5" */ = {
|
3A2E87F92ED5ABCF00644195 /* XCLocalSwiftPackageReference "Frameworks/TreeSitterJSON5" */ = {
|
||||||
isa = XCLocalSwiftPackageReference;
|
isa = XCLocalSwiftPackageReference;
|
||||||
relativePath = Frameworks/TreeSitterJSON5;
|
relativePath = Frameworks/TreeSitterJSON5;
|
||||||
@@ -2577,14 +2585,6 @@
|
|||||||
minimumVersion = 6.15.1;
|
minimumVersion = 6.15.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
3A2E87F02ED5A91100644195 /* XCRemoteSwiftPackageReference "Runestone" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/simonbs/Runestone";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 0.5.1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
3A4A020B2B53E3DC004EFB87 /* XCRemoteSwiftPackageReference "qrcode" */ = {
|
3A4A020B2B53E3DC004EFB87 /* XCRemoteSwiftPackageReference "qrcode" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/dagronf/qrcode.git";
|
repositoryURL = "https://github.com/dagronf/qrcode.git";
|
||||||
@@ -2609,6 +2609,14 @@
|
|||||||
minimumVersion = 2.0.0;
|
minimumVersion = 2.0.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
3ACE5E012EE1A91100644196 /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 0.15.2;
|
||||||
|
};
|
||||||
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@@ -2619,7 +2627,7 @@
|
|||||||
};
|
};
|
||||||
3A2E87F12ED5A91100644195 /* Runestone */ = {
|
3A2E87F12ED5A91100644195 /* Runestone */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 3A2E87F02ED5A91100644195 /* XCRemoteSwiftPackageReference "Runestone" */;
|
package = 3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */;
|
||||||
productName = Runestone;
|
productName = Runestone;
|
||||||
};
|
};
|
||||||
3A2E87FA2ED5ABDA00644195 /* TreeSitterJSON5Runestone */ = {
|
3A2E87FA2ED5ABDA00644195 /* TreeSitterJSON5Runestone */ = {
|
||||||
@@ -2637,6 +2645,11 @@
|
|||||||
package = 3A7E90362A46778E00D53052 /* XCRemoteSwiftPackageReference "BinaryCodable" */;
|
package = 3A7E90362A46778E00D53052 /* XCRemoteSwiftPackageReference "BinaryCodable" */;
|
||||||
productName = BinaryCodable;
|
productName = BinaryCodable;
|
||||||
};
|
};
|
||||||
|
3ACE5E022EE1A91200644196 /* CodeEditSourceEditor */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 3ACE5E012EE1A91100644196 /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */;
|
||||||
|
productName = CodeEditSourceEditor;
|
||||||
|
};
|
||||||
3AEECC4D2A6DFF13006A0E0C /* MacControlCenterUI */ = {
|
3AEECC4D2A6DFF13006A0E0C /* MacControlCenterUI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 3A57DF3A2A4D705000690BC5 /* XCRemoteSwiftPackageReference "MacControlCenterUI" */;
|
package = 3A57DF3A2A4D705000690BC5 /* XCRemoteSwiftPackageReference "MacControlCenterUI" */;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "48cbd083120736a1a1ce4b717dcbfdb7900aa5e2aa21991865021fb98adc2093",
|
"originHash" : "3ae592e476adf467b19c2cde1d4ba44683fb6f05a27fc1959105b8eb14cce1d2",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "binarycodable",
|
"identity" : "binarycodable",
|
||||||
@@ -10,6 +10,42 @@
|
|||||||
"version" : "2.0.3"
|
"version" : "2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "codeeditlanguages",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/CodeEditApp/CodeEditLanguages.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "331d5dbc5fc8513be5848fce8a2a312908f36a11",
|
||||||
|
"version" : "0.1.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "codeeditsourceeditor",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/CodeEditApp/CodeEditSourceEditor.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "424453d2232c9912933a3b5a1f3d3df669404ed0",
|
||||||
|
"version" : "0.15.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "codeeditsymbols",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/CodeEditApp/CodeEditSymbols.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "ae69712b08571c4469c2ed5cd38ad9f19439793e",
|
||||||
|
"version" : "0.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "codeedittextview",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/CodeEditApp/CodeEditTextView.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d7ac3f11f22ec2e820187acce8f3a3fb7aa8ddec",
|
||||||
|
"version" : "0.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "grdb.swift",
|
"identity" : "grdb.swift",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -47,12 +83,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identity" : "runestone",
|
"identity" : "rearrange",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/simonbs/Runestone",
|
"location" : "https://github.com/ChimeHQ/Rearrange",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "1fad339aab99cf2136ce6bf8c32da3265b2e85e5",
|
"revision" : "f1d74e1642956f0300756ad8d1d64e9034857bc3",
|
||||||
"version" : "0.5.1"
|
"version" : "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-collections",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-collections.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
|
||||||
|
"version" : "1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,13 +118,49 @@
|
|||||||
"version" : "1.6.1"
|
"version" : "1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftlintplugin",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/lukepistrol/SwiftLintPlugin",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9bbc46a4cf8275ceb39334f6276b6b215d67d5d5",
|
||||||
|
"version" : "0.62.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swifttreesitter",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ChimeHQ/SwiftTreeSitter.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "08ef81eb8620617b55b08868126707ad72bf754f",
|
||||||
|
"version" : "0.25.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "textformation",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ChimeHQ/TextFormation",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b1ce9a14bd86042bba4de62236028dc4ce9db6a1",
|
||||||
|
"version" : "0.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "textstory",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ChimeHQ/TextStory",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "91df6fc9bd817f9712331a4a3e826f7bdc823e1d",
|
||||||
|
"version" : "0.9.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "tree-sitter",
|
"identity" : "tree-sitter",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/tree-sitter/tree-sitter",
|
"location" : "https://github.com/tree-sitter/tree-sitter",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "98be227227af10cc7a269cb3ffb23686c0610b17",
|
"revision" : "da6fe9beb4f7f67beb75914ca8e0d48ae48d6406",
|
||||||
"version" : "0.20.9"
|
"version" : "0.25.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user