Skip to content

Commit

Permalink
296
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Sep 23, 2024
1 parent f9865e4 commit 64d10e9
Show file tree
Hide file tree
Showing 48 changed files with 3,613 additions and 0 deletions.
8 changes: 8 additions & 0 deletions 0295-cross-platform-pt6/Counter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
1 change: 1 addition & 0 deletions 0295-cross-platform-pt6/Counter/.swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
wasm-DEVELOPMENT-SNAPSHOT-2024-07-16-a
65 changes: 65 additions & 0 deletions 0295-cross-platform-pt6/Counter/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "Counter",
platforms: [.iOS(.v16), .macOS(.v14)],
products: [
.library(
name: "Counter",
targets: ["Counter"]
),
.library(
name: "FactClient",
targets: ["FactClient"]
),
.library(
name: "FactClientLive",
targets: ["FactClientLive"]
),
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-navigation", from: "2.0.0"),
.package(url: "https://github.com/pointfreeco/swift-perception", from: "1.0.0"),
.package(url: "https://github.com/swiftwasm/carton", from: "1.0.0"),
.package(url: "https://github.com/swiftwasm/JavaScriptKit", exact: "0.19.2"),
],
targets: [
.executableTarget(
name: "WasmApp",
dependencies: [
"Counter",
"FactClientLive",
.product(name: "SwiftNavigation", package: "swift-navigation"),
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
.product(name: "JavaScriptKit", package: "JavaScriptKit"),
]
),
.target(
name: "Counter",
dependencies: [
"FactClient",
.product(name: "SwiftNavigation", package: "swift-navigation"),
.product(name: "Perception", package: "swift-perception")
]
),
.target(
name: "FactClient",
dependencies: [
.product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "DependenciesMacros", package: "swift-dependencies"),
]
),
.target(
name: "FactClientLive",
dependencies: [
"FactClient",
.product(name: "JavaScriptKit", package: "JavaScriptKit", condition: .when(platforms: [.wasi])),
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit", condition: .when(platforms: [.wasi])),
]
)
],
swiftLanguageVersions: [.v6]
)
89 changes: 89 additions & 0 deletions 0295-cross-platform-pt6/Counter/Sources/Counter/CounterModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Dependencies
import FactClient
import Foundation
import Perception
import SwiftNavigation

@MainActor
@Perceptible
public class CounterModel: HashableObject {
@PerceptionIgnored
@Dependency(FactClient.self) var factClient
@PerceptionIgnored
@Dependency(\.continuousClock) var clock

public var count = 0 {
didSet {
isTextFocused = !count.isMultiple(of: 3)
}
}
public var alert: AlertState<Never>?
public var factIsLoading = false
public var isTextFocused = false {
didSet {
print("isTextFocused", isTextFocused)
}
}
public var text = ""
private var timerTask: Task<Void, Error>?

public var isTimerRunning: Bool { timerTask != nil }

public struct Fact: Identifiable {
public var value: String
public var id: String { value }
}

public init() {}

public func incrementButtonTapped() {
count += 1
alert = nil
}

public func decrementButtonTapped() {
count -= 1
alert = nil
}

public func factButtonTapped() async {
alert = nil
factIsLoading = true
defer { factIsLoading = false }

do {
try await Task.sleep(for: .seconds(1))

var count = count
let fact = try await factClient.fetch(count)
alert = AlertState {
TextState("Fact")
} actions: {
ButtonState {
TextState("OK")
}
ButtonState {
TextState("Save")
}
} message: {
TextState(fact)
}
} catch {
// TODO: error handling
}
}

public func toggleTimerButtonTapped() {
timerTask?.cancel()

if isTimerRunning {
timerTask = nil
} else {
timerTask = Task {
for await _ in clock.timer(interval: .seconds(1)) {
count += 1
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Dependencies
import DependenciesMacros

@DependencyClient
public struct FactClient: Sendable {
public var fetch: @Sendable (Int) async throws -> String
}

extension FactClient: TestDependencyKey {
public static let testValue = FactClient()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation
import FactClient
import Dependencies

#if os(WASI)
@preconcurrency import JavaScriptKit
import JavaScriptEventLoop
#endif

extension FactClient: DependencyKey {
public static let liveValue = FactClient { number in
#if os(WASI)
let response = try await JSPromise(
JSObject.global.fetch!("http://www.numberapi.com/\(number)").object!
)!.value
return try await JSPromise(response.text().object!)!.value.string!
#else
return try await String(
decoding: URLSession.shared
.data(
from: URL(string: "http://www.numberapi.com/\(number)")!
).0,
as: UTF8.self
)
#endif
}
}
131 changes: 131 additions & 0 deletions 0295-cross-platform-pt6/Counter/Sources/WasmApp/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import Counter
import IssueReporting
import JavaScriptEventLoop
import JavaScriptKit
import SwiftNavigation

@main
@MainActor
struct App {
static var tokens: Set<ObservationToken> = []

static func main() {
IssueReporters.current = [JavaScriptConsoleWarning()]
JavaScriptEventLoop.installGlobalExecutor()

@UIBindable var model = CounterModel()

let document = JSObject.global.document

// var countLabel = document.createElement("span")
// _ = document.body.appendChild(countLabel)
//
// var decrementButton = document.createElement("button")
// decrementButton.innerText = "-"
// decrementButton.onclick = .object(
// JSClosure { _ in
// model.decrementButtonTapped()
// return .undefined
// }
// )
// _ = document.body.appendChild(decrementButton)
//
// var incrementButton = document.createElement("button")
// incrementButton.innerText = "+"
// incrementButton.onclick = .object(
// JSClosure { _ in
// model.incrementButtonTapped()
// return .undefined
// }
// )
// _ = document.body.appendChild(incrementButton)

var counter = document.createElement("input")
counter.type = "number"
_ = document.body.appendChild(counter)
counter
.bind($model.count.toString, to: \.value, event: \.onchange)
.store(in: &tokens)

var toggleTimerButton = document.createElement("button")
toggleTimerButton.onclick = .object(
JSClosure { _ in
model.toggleTimerButtonTapped()
return .undefined
}
)
_ = document.body.appendChild(toggleTimerButton)

var textField = document.createElement("input")
textField.type = "text"
_ = document.body.appendChild(textField)
textField.bind($model.text, to: \.value, event: \.onkeyup)
.store(in: &tokens)

textField.bind(focus: $model.isTextFocused)
.store(in: &tokens)

// enum Focus {
// case counter
// case textField
// }
// var focus: Focus?
// counter.bind(focus: $model.focus, equals: .counter)
// textField.bind(focus: $model.focus, equals: .textField)

var factButton = document.createElement("button")
factButton.innerText = "Get fact"
factButton.onclick = .object(
JSClosure { _ in
Task { await model.factButtonTapped() }
return .undefined
}
)
_ = document.body.appendChild(factButton)

var factLabel = document.createElement("div")
_ = document.body.appendChild(factLabel)

observe {
//countLabel.innerText = .string("Count: \(model.count)")
toggleTimerButton.innerText = model.isTimerRunning ? "Stop timer" : "Start timer"

if model.factIsLoading {
factLabel.innerText = "Fact is loading..."
} else {
factLabel.innerText = ""
}
}
.store(in: &tokens)

alertDialog($model.alert)
.store(in: &tokens)
}
}

extension Int {
fileprivate var toString: String {
get {
String(self)
}
set {
self = Int(newValue) ?? 0
}
}
}

struct JavaScriptConsoleWarning: IssueReporter {
func reportIssue(
_ message: @autoclosure () -> String?,
fileID: StaticString,
filePath: StaticString,
line: UInt,
column: UInt
) {
#if DEBUG
_ = JSObject.global.console.warn("""
\(fileID):\(line) - \(message() ?? "")
""")
#endif
}
}
Loading

0 comments on commit 64d10e9

Please sign in to comment.