From 4c009431243a130aa680a1ca701f0dfd7046b469 Mon Sep 17 00:00:00 2001 From: omochimetaru Date: Mon, 22 Apr 2024 22:23:54 +0900 Subject: [PATCH] CartonExample --- CartonExample/.gitignore | 8 ++ CartonExample/Package.resolved | 104 +++++++++++++++++++++ CartonExample/Package.swift | 23 +++++ CartonExample/Sources/main.swift | 155 +++++++++++++++++++++++++++++++ CartonExample/Sources/styles.css | 80 ++++++++++++++++ 5 files changed, 370 insertions(+) create mode 100644 CartonExample/.gitignore create mode 100644 CartonExample/Package.resolved create mode 100644 CartonExample/Package.swift create mode 100644 CartonExample/Sources/main.swift create mode 100644 CartonExample/Sources/styles.css diff --git a/CartonExample/.gitignore b/CartonExample/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/CartonExample/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/CartonExample/Package.resolved b/CartonExample/Package.resolved new file mode 100644 index 0000000..b0879e0 --- /dev/null +++ b/CartonExample/Package.resolved @@ -0,0 +1,104 @@ +{ + "pins" : [ + { + "identity" : "carton", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftwasm/carton", + "state" : { + "revision" : "85fa55e4f9cbfa68d37dc7050ba7952fed300a2c", + "version" : "1.0.3" + } + }, + { + "identity" : "javascriptkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftwasm/JavaScriptKit", + "state" : { + "revision" : "3b5af3d442179900455307c725fe6a111a714b27", + "version" : "0.19.2" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "46989693916f56d1186bd59ac15124caef896560", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "fc63f0cf4e55a4597407a9fc95b16a2bc44b4982", + "version" : "2.64.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", + "version" : "1.2.1" + } + }, + { + "identity" : "wasmtransformer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftwasm/WasmTransformer", + "state" : { + "revision" : "d04b31f61b6f528a9a96ebfe4fa4275e333eba82", + "version" : "0.5.0" + } + } + ], + "version" : 2 +} diff --git a/CartonExample/Package.swift b/CartonExample/Package.swift new file mode 100644 index 0000000..ca516c1 --- /dev/null +++ b/CartonExample/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "TicTacToe", + platforms: [.macOS(.v14)], + dependencies: [ + .package(url: "https://github.com/swiftwasm/carton", from: "1.0.3"), + .package(path: "../") + ], + targets: [ + .executableTarget( + name: "TicTacToe", + dependencies: [ + .product(name: "React", package: "swift-react") + ], + resources: [ + .copy("styles.css") + ] + ) + ] +) diff --git a/CartonExample/Sources/main.swift b/CartonExample/Sources/main.swift new file mode 100644 index 0000000..ea8c1b3 --- /dev/null +++ b/CartonExample/Sources/main.swift @@ -0,0 +1,155 @@ +import React +import JavaScriptKit +import SRTDOM + +// https://ja.react.dev/learn/tutorial-tic-tac-toe + +func addCSS(path: String) throws { + let document = JSWindow.global.document + + let head = try document.querySelector("head").unwrap("head") + + let tag = try document.createElement("link") + try tag.setAttribute("rel", "stylesheet") + try tag.setAttribute("type", "text/css") + try tag.setAttribute("href", "/.build/wasm32-unknown-wasi/debug/" + path) + try head.appendChild(tag) +} + +func calculateWinner(squares: [String?]) -> String? { + let lines: [[Int]] = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6] + ] + for line in lines { + let (a, b, c) = (line[0], line[1], line[2]) + + if let x = squares[a], + squares[b] == x, + squares[c] == x { + return x + } + } + return nil +} + +struct Square: Component { + var value: String? + var onSquareClick: EventListener + + func render() -> Node { + return button( + attributes: ["class": "square"], + listeners: ["click": onSquareClick] + ) { + value + } + } +} + +struct Board: Component { + var xIsNext: Bool = true + var squares: [String?] = Array(repeating: nil, count: 9) + var onPlay: Function + + func handleClick(index: Int) { + if squares[index] != nil || + calculateWinner(squares: squares) != nil { return } + + var squares = self.squares + + if xIsNext { + squares[index] = "X" + } else { + squares[index] = "O" + } + + onPlay(squares) + } + + func render() -> Node { + let winner = calculateWinner(squares: squares) + let status: String + if let winner { + status = "Winner: " + winner; + } else { + status = "Next player: " + (xIsNext ? "X" : "O") + } + + return Fragment { + div(attributes: ["class": "status"]) { status } + div(attributes: ["class": "board-row"]) { + Square(value: squares[0], onSquareClick: EventListener { (_) in handleClick(index: 0) }) + Square(value: squares[1], onSquareClick: EventListener { (_) in handleClick(index: 1) }) + Square(value: squares[2], onSquareClick: EventListener { (_) in handleClick(index: 2) }) + } + div(attributes: ["class": "board-row"]) { + Square(value: squares[3], onSquareClick: EventListener { (_) in handleClick(index: 3) }) + Square(value: squares[4], onSquareClick: EventListener { (_) in handleClick(index: 4) }) + Square(value: squares[5], onSquareClick: EventListener { (_) in handleClick(index: 5) }) + } + div(attributes: ["class": "board-row"]) { + Square(value: squares[6], onSquareClick: EventListener { (_) in handleClick(index: 6) }) + Square(value: squares[7], onSquareClick: EventListener { (_) in handleClick(index: 7) }) + Square(value: squares[8], onSquareClick: EventListener { (_) in handleClick(index: 8) }) + } + } + } +} + +struct Game: Component { + @State var history: [[String?]] = [Array(repeating: nil, count: 9)] + @State var currentMove: Int = 0 + + func render() -> Node { + let xIsNext = currentMove % 2 == 0 + + let currentSquares = history[currentMove] + + let handlePlay = Function { (nextSquares) in + history = history[...currentMove] + [nextSquares] + currentMove = history.count - 1 + } + + func jumpTo(nextMove: Int) { + currentMove = nextMove + } + + let moves: [Node] = history.enumerated().map { (move, squares) -> Node in + let description: String + if move > 0 { + description = "Go to move #\(move)" + } else { + description = "Go to game start" + } + return li(key: move) { + button(listeners: ["click": EventListener { (e) in jumpTo(nextMove: move) }]) { + description + } + } + } + + return div(attributes: ["class": "game"]) { + div(attributes: ["class": "game-board"]) { + Board(xIsNext: xIsNext, squares: currentSquares, onPlay: handlePlay) + } + div(attributes: ["class": "game-info"]) { + ol { + moves + } + } + } + } +} + +try addCSS(path: "TicTacToe_TicTacToe.resources/styles.css") + +let body = try JSWindow.global.document.body.unwrap("body") +let root = ReactRoot(element: body) +root.render(node: Game()) diff --git a/CartonExample/Sources/styles.css b/CartonExample/Sources/styles.css new file mode 100644 index 0000000..b241674 --- /dev/null +++ b/CartonExample/Sources/styles.css @@ -0,0 +1,80 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +h1 { + margin-top: 0; + font-size: 22px; +} + +h2 { + margin-top: 0; + font-size: 20px; +} + +h3 { + margin-top: 0; + font-size: 18px; +} + +h4 { + margin-top: 0; + font-size: 16px; +} + +h5 { + margin-top: 0; + font-size: 14px; +} + +h6 { + margin-top: 0; + font-size: 12px; +} + +code { + font-size: 1.2em; +} + +ul { + padding-inline-start: 20px; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.board-row:after { + clear: both; + content: ''; + display: table; +} + +.status { + margin-bottom: 10px; +} +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +}