Files
luma/Luma/CodeShareConfigView.swift
Ole André Vadla Ravnås b88a9be290 Replace SwiftyMonaco with native editors
Drop the Monaco webview. Edit in a GtkSourceView on GTK and in a
native text view on Apple, both speaking to Frida.Compiler's
in-process language server for completions, signature help,
hovers and diagnostics.

Color, complete and indent code natively, matching the GitHub
theme in light and dark.
2026-09-08 19:52:31 +02:00

180 lines
5.1 KiB
Swift

import LumaCore
import SwiftUI
struct CodeShareConfigView: View {
@Binding var config: CodeShareConfig
let engine: Engine
@State private var draftSource: String = ""
@State private var isDirty = false
@State private var showSavedCheck = false
@State private var errorMessage: String?
@StateObject private var introspector = CodeIntrospector()
var body: some View {
VStack(spacing: 12) {
header
trustBanner
formFields
Divider()
editor
}
.padding(.top, 4)
.padding(.horizontal, 8)
.padding(.bottom, 8)
.onAppear {
draftSource = config.source
}
.onChange(of: draftSource) { _, newValue in
isDirty = (newValue != config.source)
}
}
private var header: some View {
HStack(spacing: 8) {
VStack(alignment: .leading, spacing: 2) {
Text(config.name)
.font(.headline)
if let project = config.project {
Text("@\(project.owner)/\(project.slug)")
.font(.caption)
.foregroundStyle(.secondary)
} else {
Text("Local snippet (not published)")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
HStack(spacing: 6) {
saveStatusIcon
Button("Save") {
saveDraft()
}
.disabled(!isDirty)
}
if let project = config.project {
Button {
Platform.openURL(project.url)
} label: {
Image(systemName: "safari")
}
.help("Open on CodeShare")
}
}
}
private var trustBanner: some View {
let current = config.currentSourceHash
let reviewed = config.lastReviewedHash
let synced = config.lastSyncedHash
return Group {
if reviewed == nil {
Label(
"Not yet reviewed. Please audit this script before enabling.",
systemImage: "exclamationmark.triangle"
)
.font(.caption)
.padding(6)
.background(Color.yellow.opacity(0.15))
.cornerRadius(6)
} else if current != reviewed {
HStack {
Label(
"Locally modified since last review.",
systemImage: "pencil.and.outline"
)
Spacer()
Button("Mark as reviewed") {
config.lastReviewedHash = current
}
}
.font(.caption)
.padding(6)
.background(Color.orange.opacity(0.12))
.cornerRadius(6)
} else if let synced, synced != current {
Label(
"Differs from last synced version on CodeShare.",
systemImage: "arrow.triangle.2.circlepath"
)
.font(.caption)
.padding(6)
.background(Color.blue.opacity(0.08))
.cornerRadius(6)
}
}
}
private var formFields: some View {
VStack(alignment: .leading, spacing: 8) {
TextField("Name", text: $config.name)
TextField(
"Description",
text: $config.description,
axis: .vertical
)
.lineLimit(2...4)
}
}
private var editor: some View {
VStack(alignment: .leading, spacing: 6) {
if let errorMessage {
Text(errorMessage)
.font(.caption2)
.foregroundStyle(.red)
}
CodeEditorView(
text: $draftSource,
profile: EditorProfile.fridaCodeShare(),
introspector: introspector,
engine: engine,
)
}
}
private var saveStatusIcon: some View {
ZStack {
if isDirty {
Circle()
.frame(width: 6, height: 6)
}
if showSavedCheck {
Image(systemName: "checkmark.circle.fill")
.transition(.opacity.combined(with: .scale))
}
}
.frame(width: 14, height: 14)
}
private func saveDraft() {
errorMessage = nil
Task { @MainActor in
let symbols = await introspector.topLevelSymbols()
config.source = draftSource
config.exports = symbols.map(\.text)
config.lastReviewedHash = config.currentSourceHash
isDirty = false
showSavedCheck = true
try? await Task.sleep(nanoseconds: 1_000_000_000)
withAnimation {
showSavedCheck = false
}
}
}
}