Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize style and environment in DOMRenderer #215

Merged
merged 2 commits into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Sources/TokamakCore/Styles/ToggleStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public struct _AnyToggleStyle: ToggleStyle {
}
}

public enum ToggleStyleKey: EnvironmentKey {
public enum _ToggleStyleKey: EnvironmentKey {
public static var defaultValue: _AnyToggleStyle {
fatalError("\(self) must have a renderer-provided default value")
}
Expand All @@ -61,10 +61,10 @@ public enum ToggleStyleKey: EnvironmentKey {
extension EnvironmentValues {
var toggleStyle: _AnyToggleStyle {
get {
self[ToggleStyleKey.self]
self[_ToggleStyleKey.self]
}
set {
self[ToggleStyleKey.self] = newValue
self[_ToggleStyleKey.self] = newValue
}
}
}
Expand Down
53 changes: 53 additions & 0 deletions Sources/TokamakDOM/DOMNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import JavaScriptKit
import TokamakCore

public final class DOMNode: Target {
let ref: JSObjectRef
private var listeners: [String: JSClosure]

init<V: View>(_ view: V, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
self.ref = ref
self.listeners = [:]
super.init(view)
reinstall(listeners)
}

init<A: App>(_ app: A, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
self.ref = ref
self.listeners = [:]
super.init(app)
reinstall(listeners)
}

/// Removes all existing event listeners on this DOM node and install new ones from
/// the `listeners` argument
func reinstall(_ listeners: [String: Listener]) {
for (event, jsClosure) in self.listeners {
_ = ref.removeEventListener!(event, jsClosure)
}
self.listeners = [:]

for (event, listener) in listeners {
let jsClosure = JSClosure {
listener($0[0].object!)
return .undefined
}
_ = ref.addEventListener!(event, jsClosure)
self.listeners[event] = jsClosure
}
}
}
158 changes: 52 additions & 106 deletions Sources/TokamakDOM/DOMRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,22 @@
import JavaScriptKit
import TokamakCore

public final class DOMNode: Target {
let ref: JSObjectRef
private var listeners: [String: JSClosure]

init<V: View>(_ view: V, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
self.ref = ref
self.listeners = [:]
super.init(view)
reinstall(listeners)
}

init<A: App>(_ app: A, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
self.ref = ref
self.listeners = [:]
super.init(app)
reinstall(listeners)
}

/// Removes all existing event listeners on this DOM node and install new ones from
/// the `listeners` argument
func reinstall(_ listeners: [String: Listener]) {
for (event, jsClosure) in self.listeners {
_ = ref.removeEventListener!(event, jsClosure)
}
self.listeners = [:]
extension EnvironmentValues {
/// Returns default settings for the DOM environment
static var defaultEnvironment: Self {
var environment = EnvironmentValues()
environment[_ToggleStyleKey] = _AnyToggleStyle(DefaultToggleStyle())
environment[keyPath: \._defaultAppStorage] = LocalStorage.standard
_DefaultSceneStorageProvider.default = SessionStorage.standard

for (event, listener) in listeners {
let jsClosure = JSClosure {
listener($0[0].object!)
return .undefined
}
_ = ref.addEventListener!(event, jsClosure)
self.listeners[event] = jsClosure
}
return environment
}
}

/** `SpacerContainer` is part of TokamakDOM, as not all renderers will handle flexible
sizing the way browsers do. Their parent element could already know that if a child is
requesting full width, then it needs to expand.
*/
private extension AnyView {
var axes: [SpacerContainerAxis] {
var axes = [SpacerContainerAxis]()
Expand All @@ -78,83 +57,58 @@ let log = JSObjectRef.global.console.object!.log.function!
let document = JSObjectRef.global.document.object!
let head = document.head.object!

let timeoutScheduler = { (closure: @escaping () -> ()) in
let fn = JSClosure { _ in
closure()
return .undefined
}
_ = JSObjectRef.global.setTimeout!(fn, 0)
}

func appendRootStyle(_ rootNode: JSObjectRef) {
rootNode.style = .string(rootNodeStyles)
let rootStyle = document.createElement!("style").object!
rootStyle.innerHTML = .string(tokamakStyles)
_ = head.appendChild!(rootStyle)
}

public final class DOMRenderer: Renderer {
public private(set) var reconciler: StackReconciler<DOMRenderer>?

private let rootRef: JSObjectRef

public init<V: View>(_ view: V,
_ ref: JSObjectRef,
_ rootEnvironment: EnvironmentValues? = nil) {
public init<V: View>(
_ view: V,
_ ref: JSObjectRef,
_ rootEnvironment: EnvironmentValues? = nil
) {
rootRef = ref
rootRef.style = """
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
overflow: hidden;
"""

let rootStyle = document.createElement!("style").object!
rootStyle.innerHTML = .string(tokamakStyles)
_ = head.appendChild!(rootStyle)

// Establish default settings
var environment = EnvironmentValues()
environment[ToggleStyleKey] = _AnyToggleStyle(DefaultToggleStyle())
environment[keyPath: \._defaultAppStorage] = LocalStorage.standard
_DefaultSceneStorageProvider.default = SessionStorage.standard
appendRootStyle(ref)

reconciler = StackReconciler(
view: view,
target: DOMNode(view, ref),
environment: environment,
renderer: self
) { closure in
let fn = JSClosure { _ in
closure()
return .undefined
}
_ = JSObjectRef.global.setTimeout!(fn, 0)
}
environment: .defaultEnvironment,
renderer: self,
scheduler: timeoutScheduler
)
}

init<A: App>(_ app: A,
_ ref: JSObjectRef,
_ rootEnvironment: EnvironmentValues? = nil) {
init<A: App>(
_ app: A,
_ ref: JSObjectRef,
_ rootEnvironment: EnvironmentValues? = nil
) {
rootRef = ref
rootRef.style = """
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
overflow: hidden;
"""

let rootStyle = document.createElement!("style").object!
rootStyle.innerHTML = .string(tokamakStyles)
_ = head.appendChild!(rootStyle)

// Establish default settings
var environment = EnvironmentValues()
environment[ToggleStyleKey] = _AnyToggleStyle(DefaultToggleStyle())
environment[keyPath: \._defaultAppStorage] = LocalStorage.standard
_DefaultSceneStorageProvider.default = SessionStorage.standard
appendRootStyle(ref)

reconciler = StackReconciler(
app: app,
target: DOMNode(app, ref),
environment: environment,
renderer: self
) { closure in
let fn = JSClosure { _ in
closure()
return .undefined
}
_ = JSObjectRef.global.setTimeout!(fn, 0)
}
environment: .defaultEnvironment,
renderer: self,
scheduler: timeoutScheduler
)
}

public func mountTarget(to parent: DOMNode, with host: MountedHost) -> DOMNode? {
Expand Down Expand Up @@ -191,10 +145,8 @@ public final class DOMRenderer: Renderer {
}

public func update(target: DOMNode, with host: MountedHost) {
guard let html = mapAnyView(
host.view,
transform: { (html: AnyHTML) in html }
) else { return }
guard let html = mapAnyView(host.view, transform: { (html: AnyHTML) in html })
else { return }

html.update(dom: target)
}
Expand All @@ -205,16 +157,10 @@ public final class DOMRenderer: Renderer {
with host: MountedHost,
completion: @escaping () -> ()
) {
defer {
completion()
}
defer { completion() }

guard mapAnyView(
host.view,
transform: { (html: AnyHTML) in html }
) != nil else {
return
}
guard mapAnyView(host.view, transform: { (html: AnyHTML) in html }) != nil
else { return }

_ = parent.ref.removeChild!(target.ref)
}
Expand Down
9 changes: 9 additions & 0 deletions Sources/TokamakDOM/Resources/TokamakStyles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,12 @@ let tokamakStyles = """
margin-left: 1em;
}
"""

let rootNodeStyles = """
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
overflow: hidden;
"""