Add browser
This commit is contained in:
68
CHDataManagement/Server/TryFilesMiddleware.swift
Normal file
68
CHDataManagement/Server/TryFilesMiddleware.swift
Normal file
@ -0,0 +1,68 @@
|
||||
import Vapor
|
||||
|
||||
struct TryFilesMiddleware: AsyncMiddleware {
|
||||
|
||||
let publicDirectory: String
|
||||
|
||||
func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
|
||||
let relativePath = request.url.path
|
||||
let potentialPaths = [
|
||||
publicDirectory + relativePath,
|
||||
publicDirectory + relativePath + ".html",
|
||||
publicDirectory + relativePath + "/1.html"
|
||||
]
|
||||
|
||||
for path in potentialPaths {
|
||||
var isDirectory: ObjCBool = false
|
||||
guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory),
|
||||
!isDirectory.boolValue else {
|
||||
continue
|
||||
}
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let data = try Data(contentsOf: url)
|
||||
let response = Response(status: .ok, body: .init(data: data))
|
||||
response.headers.replaceOrAdd(name: .contentType, value: url.mimeType)
|
||||
response.headers.replaceOrAdd(name: .contentLength, value: "\(data.count)")
|
||||
return response
|
||||
}
|
||||
return .init(status: .notFound)
|
||||
}
|
||||
}
|
||||
|
||||
private extension URL {
|
||||
|
||||
var mimeType: String {
|
||||
switch pathExtension.lowercased() {
|
||||
case "html": return "text/html"
|
||||
case "css": return "text/css"
|
||||
case "js": return "application/javascript"
|
||||
case "json": return "application/json"
|
||||
case "xml": return "application/xml"
|
||||
case "txt": return "text/plain"
|
||||
|
||||
case "jpg", "jpeg": return "image/jpeg"
|
||||
case "png": return "image/png"
|
||||
case "gif": return "image/gif"
|
||||
case "svg": return "image/svg+xml"
|
||||
case "webp": return "image/webp"
|
||||
case "ico": return "image/x-icon"
|
||||
|
||||
case "pdf": return "application/pdf"
|
||||
case "zip": return "application/zip"
|
||||
case "tar": return "application/x-tar"
|
||||
case "gz": return "application/gzip"
|
||||
case "rar": return "application/vnd.rar"
|
||||
|
||||
case "mp3": return "audio/mpeg"
|
||||
case "wav": return "audio/wav"
|
||||
case "ogg": return "audio/ogg"
|
||||
|
||||
case "mp4": return "video/mp4"
|
||||
case "mov": return "video/quicktime"
|
||||
case "avi": return "video/x-msvideo"
|
||||
case "webm": return "video/webm"
|
||||
|
||||
default: return "application/octet-stream" // Default binary data type
|
||||
}
|
||||
}
|
||||
}
|
20
CHDataManagement/Server/WebContentView.swift
Normal file
20
CHDataManagement/Server/WebContentView.swift
Normal file
@ -0,0 +1,20 @@
|
||||
import SwiftUI
|
||||
|
||||
struct WebContentView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var server: WebServer
|
||||
|
||||
var body: some View {
|
||||
if server.isRunning {
|
||||
WebView(viewModel: server)
|
||||
} else {
|
||||
VStack {
|
||||
Text("Webserver disabled")
|
||||
.font(.title)
|
||||
Text("Enable it to check out the generated content")
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
51
CHDataManagement/Server/WebDetailView.swift
Normal file
51
CHDataManagement/Server/WebDetailView.swift
Normal file
@ -0,0 +1,51 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct WebDetailView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@EnvironmentObject
|
||||
private var server: WebServer
|
||||
|
||||
var text: String {
|
||||
server.isRunning ? "Stop" : "Start"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
TextField("", text: $server.currentUrl)
|
||||
.disabled(true)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
HStack {
|
||||
Button(text, action: toggleWebServer)
|
||||
.disabled(!server.isRunning && content.storage.outputScope == nil)
|
||||
Button(action: { server.reloadPage() }) {
|
||||
Label("Reload", systemSymbol: .arrowClockwise)
|
||||
}
|
||||
.disabled(!server.isRunning)
|
||||
Button(action: { server.loadHomeUrl() }) {
|
||||
Label("Home", systemSymbol: .house)
|
||||
}
|
||||
.disabled(!server.isRunning)
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func toggleWebServer() {
|
||||
guard !server.isRunning else {
|
||||
server.stopServer()
|
||||
return
|
||||
}
|
||||
guard let folder = content.storage.outputScope?.url.path() else {
|
||||
print("No output folder to start server")
|
||||
return
|
||||
}
|
||||
|
||||
server.startServer(in: folder)
|
||||
}
|
||||
}
|
93
CHDataManagement/Server/WebServer.swift
Normal file
93
CHDataManagement/Server/WebServer.swift
Normal file
@ -0,0 +1,93 @@
|
||||
import Vapor
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
@MainActor
|
||||
final class WebServer: NSObject, ObservableObject, WKNavigationDelegate {
|
||||
|
||||
private var app: Application?
|
||||
|
||||
@Published
|
||||
var isRunning = false
|
||||
|
||||
@Published
|
||||
var port: Int
|
||||
|
||||
@Published
|
||||
var webView = WKWebView()
|
||||
|
||||
@Published
|
||||
var currentUrl: String = ""
|
||||
|
||||
init(port: Int) {
|
||||
self.port = port
|
||||
super.init()
|
||||
|
||||
webView.navigationDelegate = self
|
||||
}
|
||||
|
||||
func loadHomeUrl() {
|
||||
let url = URL(string: "http://localhost:\(port)/feed")!
|
||||
webView.load(URLRequest(url: url))
|
||||
}
|
||||
|
||||
func reloadPage() {
|
||||
webView.reload()
|
||||
}
|
||||
|
||||
func startServer(in directory: String) {
|
||||
if let app, !app.didShutdown {
|
||||
print("WebServer: Already running")
|
||||
return
|
||||
}
|
||||
Task {
|
||||
var vaporArgs = CommandLine.arguments
|
||||
let allowedCommands = ["serve", "routes"]
|
||||
vaporArgs = vaporArgs.filter { allowedCommands.contains($0) || $0 == CommandLine.arguments.first }
|
||||
|
||||
let app = try await Application.make(.detect(arguments: vaporArgs))
|
||||
app.logger.logLevel = .warning
|
||||
self.app = app
|
||||
|
||||
app.middleware.use(TryFilesMiddleware(publicDirectory: directory))
|
||||
app.http.server.configuration.port = 8000
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.isRunning = true
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
|
||||
self.loadHomeUrl()
|
||||
}
|
||||
print("WebServer: Starting")
|
||||
try await app.execute()
|
||||
try await app.asyncShutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func stopServer() {
|
||||
guard let app else {
|
||||
print("WebServer: Already stopped")
|
||||
return
|
||||
}
|
||||
print("WebServer: Stopping")
|
||||
Task {
|
||||
do {
|
||||
try await app.asyncShutdown()
|
||||
} catch {
|
||||
print("Failed to stop web server: \(error)")
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isRunning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WebServer {
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
DispatchQueue.main.async {
|
||||
self.currentUrl = webView.url?.absoluteString ?? "Unknown URL"
|
||||
}
|
||||
}
|
||||
}
|
15
CHDataManagement/Server/WebView.swift
Normal file
15
CHDataManagement/Server/WebView.swift
Normal file
@ -0,0 +1,15 @@
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
|
||||
// WebView Wrapper for SwiftUI
|
||||
struct WebView: NSViewRepresentable {
|
||||
|
||||
@ObservedObject
|
||||
var viewModel: WebServer
|
||||
|
||||
func makeNSView(context: Context) -> WKWebView {
|
||||
return viewModel.webView
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: WKWebView, context: Context) {}
|
||||
}
|
Reference in New Issue
Block a user