From e548ab62efeb89e6fd0a22f04d992d1a01e70949 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 08:56:22 -0600 Subject: [PATCH 01/13] feat: useExposeState --- ReactDemo/src/hooks/useExposeState.ts | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 ReactDemo/src/hooks/useExposeState.ts diff --git a/ReactDemo/src/hooks/useExposeState.ts b/ReactDemo/src/hooks/useExposeState.ts new file mode 100644 index 0000000..a295e12 --- /dev/null +++ b/ReactDemo/src/hooks/useExposeState.ts @@ -0,0 +1,36 @@ +import { useState, useEffect, useMemo } from 'react'; + +function useExposeState( + initialValue: T, + key: string +): [T, React.Dispatch>] { + const [state, setState] = useState(initialValue); + + // proxy handler to intercept changes + const handler = useMemo( + () => ({ + set: (obj: any, prop: string, value: T) => { + if (prop === 'value') { + setState(value); + } + return true; + }, + }), + [] + ); + + // create a proxy for state + const stateProxy = useMemo( + () => new Proxy({ value: state }, handler), + [state, handler] + ); + + // expose the proxy to the global scope + useEffect(() => { + (window as any)[key] = stateProxy; + }, [key, stateProxy]); + + return [state, setState]; +} + +export default useExposeState; From 70b3f6f0c30d0d815af3e05052ff4e5436e478a3 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 08:57:07 -0600 Subject: [PATCH 02/13] test: mutable total via direct usage --- DemoApp/Demo/TypeSwift/TypeSwift.swift | 3 +++ DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift | 4 ++-- ReactDemo/src/app/swift-components/page.tsx | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/DemoApp/Demo/TypeSwift/TypeSwift.swift b/DemoApp/Demo/TypeSwift/TypeSwift.swift index 8c34e14..e875b13 100644 --- a/DemoApp/Demo/TypeSwift/TypeSwift.swift +++ b/DemoApp/Demo/TypeSwift/TypeSwift.swift @@ -10,6 +10,8 @@ /// An enumeration of TypeScript identifiers generated to be used in Swift code. enum TypeSwift { + // Variables + case total(_ value: Double) // Functions case updateTotal(_ value: Double) @@ -20,6 +22,7 @@ enum TypeSwift { var jsString: String { switch self { + case .total(let value): return "total.value = \(value)" case .updateTotal(let value): return "updateTotal(\(value))" case .updateDeviceDropdown(let device): return "updateDeviceDropdown(Device.\(device))" case .updateOSDropdown(let os): return "updateOSDropdown(OperatingSystems.\(os))" diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index 941e586..fbddcb5 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -44,10 +44,10 @@ struct ComponentsView: View { ComponentSection(header: "Buttons") { HStack { PrimaryButton("+1", foreground: .white, background: .blue) { - manager.ts(.updateTotal(total + 1)) + manager.ts(.total(total + 1))//manager.ts(.updateTotal(total + 1)) } PrimaryButton("-1", foreground: .white, background: .red) { - manager.ts(.updateTotal(total - 1)) + manager.ts(.total(total - 1))//manager.ts(.updateTotal(total - 1)) } Text("\(total, specifier: "%.0f")") .font(.system(size: 14, weight: .medium)) diff --git a/ReactDemo/src/app/swift-components/page.tsx b/ReactDemo/src/app/swift-components/page.tsx index d351833..4589a7f 100644 --- a/ReactDemo/src/app/swift-components/page.tsx +++ b/ReactDemo/src/app/swift-components/page.tsx @@ -8,6 +8,7 @@ import ComponentSection from '../../components/sections'; import useExpose from '../../hooks/useExpose'; import useExposeType from '../../hooks/useExposeType'; import { handlers } from '@/utils/handlers'; +import useExposeState from '@/hooks/useExposeState'; export enum Device { Phone = 'Phone', @@ -32,7 +33,9 @@ export const exposedTypes = { }; const SplitComponentsView: FC = () => { - const [total, setTotal] = useState(0); + //const [total, setTotal] = useState(0); + const [total, setTotal] = useExposeState(0, 'total'); + const [selectedDevice, setSelectedDevice] = useState(); const [selectedOS, setSelectedOS] = useState(); const [textFieldValue, setTextFieldValue] = useState(''); From 0b0f97ad89cde825a43dab1ca8ffc0e8c23810fa Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 09:41:13 -0600 Subject: [PATCH 03/13] refactor: working postMessage script handler --- ReactDemo/src/hooks/useExposeState.ts | 10 ++++++---- ReactDemo/src/types/global.d.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ReactDemo/src/hooks/useExposeState.ts b/ReactDemo/src/hooks/useExposeState.ts index a295e12..b19a370 100644 --- a/ReactDemo/src/hooks/useExposeState.ts +++ b/ReactDemo/src/hooks/useExposeState.ts @@ -6,26 +6,28 @@ function useExposeState( ): [T, React.Dispatch>] { const [state, setState] = useState(initialValue); - // proxy handler to intercept changes const handler = useMemo( () => ({ set: (obj: any, prop: string, value: T) => { if (prop === 'value') { setState(value); + if (window.webkit?.messageHandlers?.[key]) { + window.webkit.messageHandlers[key].postMessage(value); + } else { + console.warn(`Message handler '${key}' is not available.`); + } } return true; }, }), - [] + [key] ); - // create a proxy for state const stateProxy = useMemo( () => new Proxy({ value: state }, handler), [state, handler] ); - // expose the proxy to the global scope useEffect(() => { (window as any)[key] = stateProxy; }, [key, stateProxy]); diff --git a/ReactDemo/src/types/global.d.ts b/ReactDemo/src/types/global.d.ts index 165117a..0600c84 100644 --- a/ReactDemo/src/types/global.d.ts +++ b/ReactDemo/src/types/global.d.ts @@ -4,6 +4,15 @@ export {}; export type AllHandlers = ComponentHandlers; +declare global { + interface Window { + webkit: { + messageHandlers; + }; + } +} + +/* declare global { interface Window { webkit: { @@ -11,3 +20,4 @@ declare global { }; } } +*/ From f5544ff01c93e719a1e44f6a0f77b0568a186882 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 09:47:24 -0600 Subject: [PATCH 04/13] fix: correct usage of total postMessage test --- DemoApp/Demo/TypeSwift/TypeSwift.swift | 9 +++++++++ .../Demo/Views/Pages/ComponentsView/ComponentsView.swift | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/DemoApp/Demo/TypeSwift/TypeSwift.swift b/DemoApp/Demo/TypeSwift/TypeSwift.swift index e875b13..8ed618d 100644 --- a/DemoApp/Demo/TypeSwift/TypeSwift.swift +++ b/DemoApp/Demo/TypeSwift/TypeSwift.swift @@ -46,6 +46,8 @@ import WebKit extension TypeSwift { enum MessageHandlers { + case total((Double) -> Void) + case updateTotal((Double) -> Void) case updateTextField((String) -> Void) case updateDeviceDropdown((Device) -> Void) @@ -54,6 +56,8 @@ extension TypeSwift { var name: String { switch self { + case .total: return "total" + case .updateTotal: return "updateTotal" case .updateTextField: return "updateTextField" case .updateDeviceDropdown: return "updateDeviceDropdown" @@ -64,6 +68,11 @@ extension TypeSwift { func handle(message: WKScriptMessage) { switch self { + case .total(let callback): + if let value = message.body as? Double { + callback(value) + } + case .updateTotal(let callback): if let value = message.body as? Double { callback(value) diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index fbddcb5..552395c 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -21,9 +21,14 @@ struct ComponentsView: View { HStack(spacing: 0) { ObservableWebView(manager: manager) .frame(width: geometry.size.width / 2) + .tsMessageHandler(.total { newValue in + total = newValue + }, manager: manager) + /* .tsMessageHandler(.updateTotal { newValue in total = newValue }, manager: manager) + */ .tsMessageHandler(.updateTextField { newValue in textFieldValue = newValue }, manager: manager) From ecc6e07a04fb7016202d092d67efa47adfcc2cb9 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 10:49:52 -0600 Subject: [PATCH 05/13] refactor: properly intercept state change, set proxy state --- ReactDemo/src/hooks/useExposeState.ts | 31 +++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/ReactDemo/src/hooks/useExposeState.ts b/ReactDemo/src/hooks/useExposeState.ts index b19a370..32e5460 100644 --- a/ReactDemo/src/hooks/useExposeState.ts +++ b/ReactDemo/src/hooks/useExposeState.ts @@ -1,16 +1,26 @@ -import { useState, useEffect, useMemo } from 'react'; +import { + useState, + useEffect, + useMemo, + useRef, + Dispatch, + SetStateAction, +} from 'react'; function useExposeState( initialValue: T, key: string -): [T, React.Dispatch>] { - const [state, setState] = useState(initialValue); +): [T, Dispatch>] { + const [state, _setState] = useState(initialValue); + + const stateRef = useRef(state); + stateRef.current = state; const handler = useMemo( () => ({ set: (obj: any, prop: string, value: T) => { if (prop === 'value') { - setState(value); + _setState(value); if (window.webkit?.messageHandlers?.[key]) { window.webkit.messageHandlers[key].postMessage(value); } else { @@ -24,14 +34,23 @@ function useExposeState( ); const stateProxy = useMemo( - () => new Proxy({ value: state }, handler), - [state, handler] + () => new Proxy({ value: stateRef.current }, handler), + [stateRef, handler] ); useEffect(() => { (window as any)[key] = stateProxy; }, [key, stateProxy]); + const setState: Dispatch> = (newValue) => { + if (typeof newValue === 'function') { + const valueFn = newValue as (prevState: T) => T; + stateProxy.value = valueFn(stateRef.current); + } else { + stateProxy.value = newValue as T; + } + }; + return [state, setState]; } From ab379abb9306f764394016f9634aa06e041a005e Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 10:50:46 -0600 Subject: [PATCH 06/13] test: on change text field value based on textFieldValue --- DemoApp/Demo/TypeSwift/TypeSwift.swift | 9 +++++++ .../Pages/ComponentsView/ComponentsView.swift | 8 ++++-- ReactDemo/src/app/swift-components/page.tsx | 26 ++++++------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/DemoApp/Demo/TypeSwift/TypeSwift.swift b/DemoApp/Demo/TypeSwift/TypeSwift.swift index 8ed618d..643620f 100644 --- a/DemoApp/Demo/TypeSwift/TypeSwift.swift +++ b/DemoApp/Demo/TypeSwift/TypeSwift.swift @@ -12,6 +12,7 @@ enum TypeSwift { // Variables case total(_ value: Double) + case textFieldValue(_ value: String) // Functions case updateTotal(_ value: Double) @@ -23,6 +24,8 @@ enum TypeSwift { var jsString: String { switch self { case .total(let value): return "total.value = \(value)" + case .textFieldValue(let value): return "textFieldValue.value = `\(value)`" + case .updateTotal(let value): return "updateTotal(\(value))" case .updateDeviceDropdown(let device): return "updateDeviceDropdown(Device.\(device))" case .updateOSDropdown(let os): return "updateOSDropdown(OperatingSystems.\(os))" @@ -47,6 +50,7 @@ import WebKit extension TypeSwift { enum MessageHandlers { case total((Double) -> Void) + case textFieldValue((String) -> Void) case updateTotal((Double) -> Void) case updateTextField((String) -> Void) @@ -57,6 +61,7 @@ extension TypeSwift { var name: String { switch self { case .total: return "total" + case .textFieldValue: return "textFieldValue" case .updateTotal: return "updateTotal" case .updateTextField: return "updateTextField" @@ -72,6 +77,10 @@ extension TypeSwift { if let value = message.body as? Double { callback(value) } + case .textFieldValue(let callback): + if let value = message.body as? String { + callback(value) + } case .updateTotal(let callback): if let value = message.body as? Double { diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index 552395c..3a21e32 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -24,14 +24,18 @@ struct ComponentsView: View { .tsMessageHandler(.total { newValue in total = newValue }, manager: manager) + .tsMessageHandler(.textFieldValue { newValue in + textFieldValue = newValue + }, manager: manager) /* .tsMessageHandler(.updateTotal { newValue in total = newValue }, manager: manager) - */ + .tsMessageHandler(.updateTextField { newValue in textFieldValue = newValue }, manager: manager) + */ .tsMessageHandler(.updateDeviceDropdown { newValue in selectedDevice = newValue }, manager: manager) @@ -62,7 +66,7 @@ struct ComponentsView: View { ComponentSection(header: "TextField") { PrimaryTextField(text: $textFieldValue) .onChange(of: textFieldValue) { - manager.ts(.updateTextField(textFieldValue)) + manager.ts(.textFieldValue(textFieldValue))//manager.ts(.updateTextField(textFieldValue)) } } diff --git a/ReactDemo/src/app/swift-components/page.tsx b/ReactDemo/src/app/swift-components/page.tsx index 4589a7f..0a82750 100644 --- a/ReactDemo/src/app/swift-components/page.tsx +++ b/ReactDemo/src/app/swift-components/page.tsx @@ -34,16 +34,18 @@ export const exposedTypes = { const SplitComponentsView: FC = () => { //const [total, setTotal] = useState(0); - const [total, setTotal] = useExposeState(0, 'total'); - const [selectedDevice, setSelectedDevice] = useState(); const [selectedOS, setSelectedOS] = useState(); - const [textFieldValue, setTextFieldValue] = useState(''); + //const [textFieldValue, setTextFieldValue] = useState(''); const [switchValue, setSwitchValue] = useState(false); - useEffect(() => { - postTotal(total); - }, [total]); + const [total, setTotal] = useExposeState(0, 'total'); + //const [selectedDevice, setSelectedDevice] = useExposeState('selectedDevice'); + + const [textFieldValue, setTextFieldValue] = useExposeState( + '', + 'textFieldValue' + ); useEffect(() => { if (selectedDevice) { @@ -61,16 +63,6 @@ const SplitComponentsView: FC = () => { postSwitch(switchValue); }, [switchValue]); - const postTotal = (value: number) => { - if (handlers.updateTotal) { - handlers.updateTotal.postMessage(value); - } - }; - - const postTextField = (value: string) => { - handlers.updateTextField.postMessage(value); - }; - const postDeviceDropdown = (value: Device) => { handlers.updateDeviceDropdown.postMessage(JSON.stringify(value)); }; @@ -105,7 +97,6 @@ const SplitComponentsView: FC = () => { const handleTextFieldChange = (event: ChangeEvent) => { const newValue = event.target.value; setTextFieldValue(newValue); - postTextField(newValue); }; const handleSwitchChange = () => { @@ -130,7 +121,6 @@ const SplitComponentsView: FC = () => { const updateTextField = (text: string) => { setTextFieldValue(text); - postTextField(text); }; const updateSwitch = (state: boolean) => { From 85b8d1cd78fbc3e976d46068c0f51fad4f5c0f71 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 10:58:51 -0600 Subject: [PATCH 07/13] feat: direct manipulation of switchValue --- DemoApp/Demo/TypeSwift/TypeSwift.swift | 9 +++++++++ .../Pages/ComponentsView/ComponentsView.swift | 15 ++++----------- ReactDemo/src/app/swift-components/page.tsx | 11 +++++------ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/DemoApp/Demo/TypeSwift/TypeSwift.swift b/DemoApp/Demo/TypeSwift/TypeSwift.swift index 643620f..139c39a 100644 --- a/DemoApp/Demo/TypeSwift/TypeSwift.swift +++ b/DemoApp/Demo/TypeSwift/TypeSwift.swift @@ -10,9 +10,11 @@ /// An enumeration of TypeScript identifiers generated to be used in Swift code. enum TypeSwift { + // Variables case total(_ value: Double) case textFieldValue(_ value: String) + case switchValue(_ value: Bool) // Functions case updateTotal(_ value: Double) @@ -25,6 +27,7 @@ enum TypeSwift { switch self { case .total(let value): return "total.value = \(value)" case .textFieldValue(let value): return "textFieldValue.value = `\(value)`" + case .switchValue(let value): return "switchValue.value = \(value)" case .updateTotal(let value): return "updateTotal(\(value))" case .updateDeviceDropdown(let device): return "updateDeviceDropdown(Device.\(device))" @@ -51,6 +54,7 @@ extension TypeSwift { enum MessageHandlers { case total((Double) -> Void) case textFieldValue((String) -> Void) + case switchValue((Bool) -> Void) case updateTotal((Double) -> Void) case updateTextField((String) -> Void) @@ -62,6 +66,7 @@ extension TypeSwift { switch self { case .total: return "total" case .textFieldValue: return "textFieldValue" + case .switchValue: return "switchValue" case .updateTotal: return "updateTotal" case .updateTextField: return "updateTextField" @@ -81,6 +86,10 @@ extension TypeSwift { if let value = message.body as? String { callback(value) } + case .switchValue(let callback): + if let value = message.body as? Bool { + callback(value) + } case .updateTotal(let callback): if let value = message.body as? Double { diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index 3a21e32..5794dae 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -27,24 +27,17 @@ struct ComponentsView: View { .tsMessageHandler(.textFieldValue { newValue in textFieldValue = newValue }, manager: manager) - /* - .tsMessageHandler(.updateTotal { newValue in - total = newValue - }, manager: manager) - - .tsMessageHandler(.updateTextField { newValue in - textFieldValue = newValue - }, manager: manager) - */ .tsMessageHandler(.updateDeviceDropdown { newValue in selectedDevice = newValue }, manager: manager) .tsMessageHandler(.updateOSDropdown { newValue in selectedOS = newValue }, manager: manager) - .tsMessageHandler(.updateSwitch { newValue in + .tsMessageHandler(.switchValue { newValue in switchValue = newValue }, manager: manager) + // TODO: Simplify for 1:1 state vars + // .sync(.switchValue, $switchValue) ScrollView { VStack(alignment: .leading, spacing: 16) { @@ -88,7 +81,7 @@ struct ComponentsView: View { ComponentSection(header: "Switch") { LargeSwitch(state: $switchValue) .onChange(of: switchValue) { - manager.ts(.updateSwitch(switchValue)) + manager.ts(.switchValue(switchValue)) //manager.ts(.updateSwitch(switchValue)) } } } diff --git a/ReactDemo/src/app/swift-components/page.tsx b/ReactDemo/src/app/swift-components/page.tsx index 0a82750..9950d3d 100644 --- a/ReactDemo/src/app/swift-components/page.tsx +++ b/ReactDemo/src/app/swift-components/page.tsx @@ -37,7 +37,7 @@ const SplitComponentsView: FC = () => { const [selectedDevice, setSelectedDevice] = useState(); const [selectedOS, setSelectedOS] = useState(); //const [textFieldValue, setTextFieldValue] = useState(''); - const [switchValue, setSwitchValue] = useState(false); + //const [switchValue, setSwitchValue] = useState(false); const [total, setTotal] = useExposeState(0, 'total'); //const [selectedDevice, setSelectedDevice] = useExposeState('selectedDevice'); @@ -46,6 +46,10 @@ const SplitComponentsView: FC = () => { '', 'textFieldValue' ); + const [switchValue, setSwitchValue] = useExposeState( + true, + 'switchValue' + ); useEffect(() => { if (selectedDevice) { @@ -59,10 +63,6 @@ const SplitComponentsView: FC = () => { } }, [selectedOS]); - useEffect(() => { - postSwitch(switchValue); - }, [switchValue]); - const postDeviceDropdown = (value: Device) => { handlers.updateDeviceDropdown.postMessage(JSON.stringify(value)); }; @@ -125,7 +125,6 @@ const SplitComponentsView: FC = () => { const updateSwitch = (state: boolean) => { setSwitchValue(state); - postSwitch(state); }; useExposeType(exposedTypes); From 02763e39bab58891627043a936557da98bf7a9c0 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 12:00:41 -0600 Subject: [PATCH 08/13] refactor: argument overrides for empty undefined --- ReactDemo/src/hooks/useExposeState.ts | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ReactDemo/src/hooks/useExposeState.ts b/ReactDemo/src/hooks/useExposeState.ts index 32e5460..0dfeb42 100644 --- a/ReactDemo/src/hooks/useExposeState.ts +++ b/ReactDemo/src/hooks/useExposeState.ts @@ -7,18 +7,34 @@ import { SetStateAction, } from 'react'; +function useExposeState( + key: string +): [T | undefined, Dispatch>]; function useExposeState( initialValue: T, key: string -): [T, Dispatch>] { - const [state, _setState] = useState(initialValue); +): [T, Dispatch>]; + +function useExposeState(...args: [string] | [T, string]) { + let initialValue: T | undefined; + let key: string; + + if (args.length === 1) { + initialValue = undefined; + key = args[0]; + } else { + initialValue = args[0]; + key = args[1]; + } + + const [state, _setState] = useState(initialValue); const stateRef = useRef(state); stateRef.current = state; const handler = useMemo( () => ({ - set: (obj: any, prop: string, value: T) => { + set: (obj: any, prop: string, value: T | undefined) => { if (prop === 'value') { _setState(value); if (window.webkit?.messageHandlers?.[key]) { @@ -42,12 +58,12 @@ function useExposeState( (window as any)[key] = stateProxy; }, [key, stateProxy]); - const setState: Dispatch> = (newValue) => { + const setState: Dispatch> = (newValue) => { if (typeof newValue === 'function') { - const valueFn = newValue as (prevState: T) => T; + const valueFn = newValue as (prevState: T | undefined) => T | undefined; stateProxy.value = valueFn(stateRef.current); } else { - stateProxy.value = newValue as T; + stateProxy.value = newValue; } }; From d4c40585ed65a923411e3461499c7144c648a973 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 12:01:07 -0600 Subject: [PATCH 09/13] refactor: page incorporates Device, OS dropdowns --- ReactDemo/src/app/swift-components/page.tsx | 42 ++------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/ReactDemo/src/app/swift-components/page.tsx b/ReactDemo/src/app/swift-components/page.tsx index 9950d3d..a6cd4ea 100644 --- a/ReactDemo/src/app/swift-components/page.tsx +++ b/ReactDemo/src/app/swift-components/page.tsx @@ -7,7 +7,6 @@ import Switch from '../../components/switches'; import ComponentSection from '../../components/sections'; import useExpose from '../../hooks/useExpose'; import useExposeType from '../../hooks/useExposeType'; -import { handlers } from '@/utils/handlers'; import useExposeState from '@/hooks/useExposeState'; export enum Device { @@ -33,15 +32,11 @@ export const exposedTypes = { }; const SplitComponentsView: FC = () => { - //const [total, setTotal] = useState(0); - const [selectedDevice, setSelectedDevice] = useState(); - const [selectedOS, setSelectedOS] = useState(); - //const [textFieldValue, setTextFieldValue] = useState(''); - //const [switchValue, setSwitchValue] = useState(false); - const [total, setTotal] = useExposeState(0, 'total'); - //const [selectedDevice, setSelectedDevice] = useExposeState('selectedDevice'); - + const [selectedDevice, setSelectedDevice] = + useExposeState('selectedDevice'); + const [selectedOS, setSelectedOS] = + useExposeState('selectedOS'); const [textFieldValue, setTextFieldValue] = useExposeState( '', 'textFieldValue' @@ -51,31 +46,6 @@ const SplitComponentsView: FC = () => { 'switchValue' ); - useEffect(() => { - if (selectedDevice) { - postDeviceDropdown(selectedDevice); - } - }, [selectedDevice]); - - useEffect(() => { - if (selectedOS) { - postOSDropdown(selectedOS); - } - }, [selectedOS]); - - const postDeviceDropdown = (value: Device) => { - handlers.updateDeviceDropdown.postMessage(JSON.stringify(value)); - }; - const postOSDropdown = (value: OperatingSystemType) => { - handlers.updateOSDropdown.postMessage(value); - }; - - const postSwitch = (value: boolean) => { - if (handlers.updateSwitch) { - handlers.updateSwitch.postMessage(value); - } - }; - const handleIncrement = () => { updateTotal(total + 1); }; @@ -86,12 +56,10 @@ const SplitComponentsView: FC = () => { const handleDeviceSelect = (device: Device) => { setSelectedDevice(device); - postDeviceDropdown(device); }; const handleOSSelect = (os: OperatingSystemType) => { setSelectedOS(os); - postOSDropdown(os); }; const handleTextFieldChange = (event: ChangeEvent) => { @@ -111,12 +79,10 @@ const SplitComponentsView: FC = () => { const updateDeviceDropdown = (device: Device) => { handleDeviceSelect(device); - postDeviceDropdown(device); }; const updateOSDropdown = (os: OperatingSystemType) => { handleOSSelect(os); - postOSDropdown(os); }; const updateTextField = (text: string) => { From b830b1d7e76c1f0dae150d32f3d88b2d84a06dc5 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 12:01:54 -0600 Subject: [PATCH 10/13] refactor: components view incorporates new state variables --- DemoApp/Demo/TypeSwift/TypeSwift.swift | 39 ++++++++++++++----- .../Pages/ComponentsView/ComponentsView.swift | 18 ++++----- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/DemoApp/Demo/TypeSwift/TypeSwift.swift b/DemoApp/Demo/TypeSwift/TypeSwift.swift index 139c39a..fd65ede 100644 --- a/DemoApp/Demo/TypeSwift/TypeSwift.swift +++ b/DemoApp/Demo/TypeSwift/TypeSwift.swift @@ -11,10 +11,12 @@ /// An enumeration of TypeScript identifiers generated to be used in Swift code. enum TypeSwift { - // Variables + // State Variables case total(_ value: Double) case textFieldValue(_ value: String) case switchValue(_ value: Bool) + case selectedDevice(_ device: Device) + case selectedOS(_ os: OperatingSystems) // Functions case updateTotal(_ value: Double) @@ -28,7 +30,10 @@ enum TypeSwift { case .total(let value): return "total.value = \(value)" case .textFieldValue(let value): return "textFieldValue.value = `\(value)`" case .switchValue(let value): return "switchValue.value = \(value)" - + case .selectedDevice(let device): return "selectedDevice.value = Device.\(device)" + case .selectedOS(let os): return "selectedOS.value = OperatingSystems.\(os)" + + // Functions case .updateTotal(let value): return "updateTotal(\(value))" case .updateDeviceDropdown(let device): return "updateDeviceDropdown(Device.\(device))" case .updateOSDropdown(let os): return "updateOSDropdown(OperatingSystems.\(os))" @@ -55,7 +60,10 @@ extension TypeSwift { case total((Double) -> Void) case textFieldValue((String) -> Void) case switchValue((Bool) -> Void) + case selectedDevice((Device) -> Void) + case selectedOS((OperatingSystems) -> Void) + // Static Functions case updateTotal((Double) -> Void) case updateTextField((String) -> Void) case updateDeviceDropdown((Device) -> Void) @@ -67,7 +75,10 @@ extension TypeSwift { case .total: return "total" case .textFieldValue: return "textFieldValue" case .switchValue: return "switchValue" - + case .selectedDevice: return "selectedDevice" + case .selectedOS: return "selectedOS" + + // Static Functions case .updateTotal: return "updateTotal" case .updateTextField: return "updateTextField" case .updateDeviceDropdown: return "updateDeviceDropdown" @@ -86,23 +97,33 @@ extension TypeSwift { if let value = message.body as? String { callback(value) } + case .selectedDevice(let callback): + if let deviceData = message.body as? String, + let device = Device(rawValue: deviceData) { + callback(device) + } + case .selectedOS(let callback): + if let osData = message.body as? String, + let os = OperatingSystems(rawValue: osData) { + callback(os) + } case .switchValue(let callback): if let value = message.body as? Bool { callback(value) } + // Static Functions case .updateTotal(let callback): if let value = message.body as? Double { callback(value) } case .updateTextField(let callback): - if let text = message.body as? String { - callback(text) + if let value = message.body as? String { + callback(value) } case .updateDeviceDropdown(let callback): if let deviceData = message.body as? String, - let data = deviceData.data(using: .utf8), - let device = try? JSONDecoder().decode(Device.self, from: data) { + let device = Device(rawValue: deviceData) { callback(device) } case .updateOSDropdown(let callback): @@ -111,8 +132,8 @@ extension TypeSwift { callback(os) } case .updateSwitch(let callback): - if let switchValue = message.body as? Bool { - callback(switchValue) + if let value = message.body as? Bool { + callback(value) } } } diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index 5794dae..c5583a2 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -27,17 +27,17 @@ struct ComponentsView: View { .tsMessageHandler(.textFieldValue { newValue in textFieldValue = newValue }, manager: manager) - .tsMessageHandler(.updateDeviceDropdown { newValue in + .tsMessageHandler(.selectedDevice { newValue in selectedDevice = newValue }, manager: manager) - .tsMessageHandler(.updateOSDropdown { newValue in + .tsMessageHandler(.selectedOS { newValue in selectedOS = newValue }, manager: manager) .tsMessageHandler(.switchValue { newValue in switchValue = newValue }, manager: manager) // TODO: Simplify for 1:1 state vars - // .sync(.switchValue, $switchValue) + // .sync(.switchValue, $switchValue, manager) ScrollView { VStack(alignment: .leading, spacing: 16) { @@ -46,10 +46,10 @@ struct ComponentsView: View { ComponentSection(header: "Buttons") { HStack { PrimaryButton("+1", foreground: .white, background: .blue) { - manager.ts(.total(total + 1))//manager.ts(.updateTotal(total + 1)) + manager.ts(.total(total + 1)) } PrimaryButton("-1", foreground: .white, background: .red) { - manager.ts(.total(total - 1))//manager.ts(.updateTotal(total - 1)) + manager.ts(.total(total - 1)) } Text("\(total, specifier: "%.0f")") .font(.system(size: 14, weight: .medium)) @@ -59,7 +59,7 @@ struct ComponentsView: View { ComponentSection(header: "TextField") { PrimaryTextField(text: $textFieldValue) .onChange(of: textFieldValue) { - manager.ts(.textFieldValue(textFieldValue))//manager.ts(.updateTextField(textFieldValue)) + manager.ts(.textFieldValue(textFieldValue)) } } @@ -68,12 +68,12 @@ struct ComponentsView: View { MonoSubheader("enum") EnumDropdownMenu(selection: $selectedDevice) .onChange(of: selectedDevice) { - manager.ts(.updateDeviceDropdown(selectedDevice)) + manager.ts(.selectedDevice(selectedDevice)) } MonoSubheader("const") EnumDropdownMenu(selection: $selectedOS) .onChange(of: selectedOS) { - manager.ts(.updateOSDropdown(selectedOS)) + manager.ts(.selectedOS(selectedOS)) } } } @@ -81,7 +81,7 @@ struct ComponentsView: View { ComponentSection(header: "Switch") { LargeSwitch(state: $switchValue) .onChange(of: switchValue) { - manager.ts(.switchValue(switchValue)) //manager.ts(.updateSwitch(switchValue)) + manager.ts(.switchValue(switchValue)) } } } From ea56278b157c12b75ce45bc76aba8decb989c27f Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 12:08:22 -0600 Subject: [PATCH 11/13] refactor: proper order of component state values --- DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index c5583a2..6ddfe1f 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -10,8 +10,8 @@ import SwiftUI struct ComponentsView: View { let manager: ObservableWebViewManager - @State private var textFieldValue: String = "" @State private var total: Double = 0 + @State private var textFieldValue: String = "" @State private var selectedDevice: TypeSwift.Device = .Phone @State private var selectedOS: TypeSwift.OperatingSystems = .iOS @State private var switchValue: Bool = true From d748574eb242807ecd778b3e4eecef8b2dda142b Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 14:08:41 -0600 Subject: [PATCH 12/13] refactor: move todo to new branch --- DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift index 6ddfe1f..18bf2ea 100644 --- a/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift +++ b/DemoApp/Demo/Views/Pages/ComponentsView/ComponentsView.swift @@ -36,8 +36,6 @@ struct ComponentsView: View { .tsMessageHandler(.switchValue { newValue in switchValue = newValue }, manager: manager) - // TODO: Simplify for 1:1 state vars - // .sync(.switchValue, $switchValue, manager) ScrollView { VStack(alignment: .leading, spacing: 16) { From 780793622beaffa923a75142927c7b0c4c5a6658 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 9 Jun 2024 14:13:42 -0600 Subject: [PATCH 13/13] =?UTF-8?q?refactor:=20root=20page=20styles=20?= =?UTF-8?q?=E2=86=92=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReactDemo/src/app/swift-components/layout.tsx | 8 ++++++-- ReactDemo/src/app/swift-components/page.tsx | 14 +------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/ReactDemo/src/app/swift-components/layout.tsx b/ReactDemo/src/app/swift-components/layout.tsx index d4d2e7a..83c54d0 100644 --- a/ReactDemo/src/app/swift-components/layout.tsx +++ b/ReactDemo/src/app/swift-components/layout.tsx @@ -2,8 +2,12 @@ import { FC, ReactNode } from 'react'; const SwiftComponentsLayout: FC<{ children: ReactNode }> = ({ children }) => { return ( -
-
{children}
+
+
+

React

+

This is a React web app

+
{children}
+
); }; diff --git a/ReactDemo/src/app/swift-components/page.tsx b/ReactDemo/src/app/swift-components/page.tsx index a6cd4ea..05692e6 100644 --- a/ReactDemo/src/app/swift-components/page.tsx +++ b/ReactDemo/src/app/swift-components/page.tsx @@ -170,16 +170,4 @@ const OperatingSystemDropdown: FC<{ ); }; -const SwiftComponents: FC = () => { - return ( -
-
-

React

-

This is a React web app

- -
-
- ); -}; - -export default SwiftComponents; +export default SplitComponentsView;