@thelacanians/vue-native-cli 0.4.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import VueNativeShared
|
|
3
|
+
|
|
4
|
+
/// Native module for NSView animations.
|
|
5
|
+
/// Exposes timing, spring, keyframe, sequence, and parallel animations to JS composables.
|
|
6
|
+
final class AnimationModule: NativeModule {
|
|
7
|
+
|
|
8
|
+
let moduleName = "Animation"
|
|
9
|
+
private let viewLookup: (Int) -> NSView?
|
|
10
|
+
|
|
11
|
+
init(viewLookup: @escaping (Int) -> NSView?) {
|
|
12
|
+
self.viewLookup = viewLookup
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
16
|
+
switch method {
|
|
17
|
+
case "timing":
|
|
18
|
+
handleTiming(args: args, callback: callback)
|
|
19
|
+
case "spring":
|
|
20
|
+
handleSpring(args: args, callback: callback)
|
|
21
|
+
case "keyframe":
|
|
22
|
+
guard args.count >= 3,
|
|
23
|
+
let viewId = coerceInt(args[0]),
|
|
24
|
+
let keyframesData = args[1] as? [[String: Any]],
|
|
25
|
+
let options = args[2] as? [String: Any] else {
|
|
26
|
+
callback(nil, "Invalid args"); return
|
|
27
|
+
}
|
|
28
|
+
let duration = (options["duration"] as? Double ?? 300) / 1000.0
|
|
29
|
+
animateKeyframes(viewId: viewId, keyframes: keyframesData, duration: duration, callback: callback)
|
|
30
|
+
case "sequence":
|
|
31
|
+
guard let animationsData = args.first as? [[String: Any]] else {
|
|
32
|
+
callback(nil, "Invalid args"); return
|
|
33
|
+
}
|
|
34
|
+
runSequence(animationsData: animationsData, index: 0, callback: callback)
|
|
35
|
+
case "parallel":
|
|
36
|
+
guard let animationsData = args.first as? [[String: Any]] else {
|
|
37
|
+
callback(nil, "Invalid args"); return
|
|
38
|
+
}
|
|
39
|
+
runParallel(animationsData: animationsData, callback: callback)
|
|
40
|
+
default:
|
|
41
|
+
callback(nil, "AnimationModule: unknown method '\(method)'")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func invokeSync(method: String, args: [Any]) -> Any? {
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - timing(viewId, styles, options)
|
|
50
|
+
|
|
51
|
+
private func handleTiming(args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
52
|
+
guard let viewId = args.first.flatMap({ coerceInt($0) }) else {
|
|
53
|
+
callback(nil, "timing: invalid arguments")
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let styles = args.count > 1 ? (args[1] as? [String: Any] ?? [:]) : [:]
|
|
58
|
+
let options = args.count > 2 ? (args[2] as? [String: Any] ?? [:]) : [:]
|
|
59
|
+
let duration = (options["duration"] as? Double ?? 300) / 1000.0
|
|
60
|
+
let easing = options["easing"] as? String ?? "ease"
|
|
61
|
+
|
|
62
|
+
let timingFunction: CAMediaTimingFunction
|
|
63
|
+
switch easing {
|
|
64
|
+
case "linear": timingFunction = CAMediaTimingFunction(name: .linear)
|
|
65
|
+
case "ease-in", "easeIn": timingFunction = CAMediaTimingFunction(name: .easeIn)
|
|
66
|
+
case "ease-out", "easeOut": timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
67
|
+
default: timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
DispatchQueue.main.async { [weak self] in
|
|
71
|
+
guard let view = self?.viewLookup(viewId) else {
|
|
72
|
+
callback(nil, "timing: view \(viewId) not found")
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
NSAnimationContext.runAnimationGroup({ ctx in
|
|
77
|
+
ctx.duration = duration
|
|
78
|
+
ctx.timingFunction = timingFunction
|
|
79
|
+
ctx.allowsImplicitAnimation = true
|
|
80
|
+
|
|
81
|
+
for (key, value) in styles {
|
|
82
|
+
self?.applyAnimatableStyle(key: key, value: value, to: view)
|
|
83
|
+
}
|
|
84
|
+
view.layoutSubtreeIfNeeded()
|
|
85
|
+
}, completionHandler: {
|
|
86
|
+
callback(nil, nil)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MARK: - spring(viewId, styles, options)
|
|
92
|
+
|
|
93
|
+
private func handleSpring(args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
94
|
+
guard let viewId = args.first.flatMap({ coerceInt($0) }) else {
|
|
95
|
+
callback(nil, "spring: invalid arguments")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let styles = args.count > 1 ? (args[1] as? [String: Any] ?? [:]) : [:]
|
|
100
|
+
let options = args.count > 2 ? (args[2] as? [String: Any] ?? [:]) : [:]
|
|
101
|
+
let duration = (options["duration"] as? Double ?? 500) / 1000.0
|
|
102
|
+
let damping = CGFloat(options["damping"] as? Double ?? 0.7)
|
|
103
|
+
|
|
104
|
+
DispatchQueue.main.async { [weak self] in
|
|
105
|
+
guard let view = self?.viewLookup(viewId) else {
|
|
106
|
+
callback(nil, "spring: view \(viewId) not found")
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Use CASpringAnimation for true spring physics
|
|
111
|
+
let springAnim = CASpringAnimation(keyPath: "transform")
|
|
112
|
+
springAnim.damping = damping * 20 // scale to CA range
|
|
113
|
+
springAnim.duration = duration
|
|
114
|
+
springAnim.isRemovedOnCompletion = false
|
|
115
|
+
springAnim.fillMode = .forwards
|
|
116
|
+
|
|
117
|
+
// Fall back to NSAnimationContext for property changes
|
|
118
|
+
NSAnimationContext.runAnimationGroup({ ctx in
|
|
119
|
+
ctx.duration = duration
|
|
120
|
+
ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
121
|
+
ctx.allowsImplicitAnimation = true
|
|
122
|
+
|
|
123
|
+
for (key, value) in styles {
|
|
124
|
+
self?.applyAnimatableStyle(key: key, value: value, to: view)
|
|
125
|
+
}
|
|
126
|
+
view.layoutSubtreeIfNeeded()
|
|
127
|
+
}, completionHandler: {
|
|
128
|
+
callback(nil, nil)
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// MARK: - keyframe(viewId, keyframes, options)
|
|
134
|
+
|
|
135
|
+
private func animateKeyframes(viewId: Int, keyframes: [[String: Any]], duration: TimeInterval, callback: @escaping (Any?, String?) -> Void) {
|
|
136
|
+
DispatchQueue.main.async { [weak self] in
|
|
137
|
+
guard let view = self?.viewLookup(viewId) else {
|
|
138
|
+
callback(nil, "View not found for id \(viewId)"); return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
guard let layer = view.layer else {
|
|
142
|
+
callback(nil, "View has no layer"); return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let group = CAAnimationGroup()
|
|
146
|
+
group.duration = duration
|
|
147
|
+
group.fillMode = .forwards
|
|
148
|
+
group.isRemovedOnCompletion = false
|
|
149
|
+
|
|
150
|
+
var animations: [CAAnimation] = []
|
|
151
|
+
|
|
152
|
+
// Collect all property names from keyframes
|
|
153
|
+
var propertyKeys = Set<String>()
|
|
154
|
+
for kf in keyframes { propertyKeys.formUnion(kf.keys.filter { $0 != "offset" }) }
|
|
155
|
+
|
|
156
|
+
for propKey in propertyKeys {
|
|
157
|
+
let keyPath: String
|
|
158
|
+
var values: [Any] = []
|
|
159
|
+
var keyTimes: [NSNumber] = []
|
|
160
|
+
|
|
161
|
+
switch propKey {
|
|
162
|
+
case "opacity":
|
|
163
|
+
keyPath = "opacity"
|
|
164
|
+
for kf in keyframes {
|
|
165
|
+
if let v = kf[propKey] as? Double {
|
|
166
|
+
values.append(v)
|
|
167
|
+
keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
case "translateX":
|
|
171
|
+
keyPath = "transform.translation.x"
|
|
172
|
+
for kf in keyframes {
|
|
173
|
+
if let v = kf[propKey] as? Double {
|
|
174
|
+
values.append(v)
|
|
175
|
+
keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
case "translateY":
|
|
179
|
+
keyPath = "transform.translation.y"
|
|
180
|
+
for kf in keyframes {
|
|
181
|
+
if let v = kf[propKey] as? Double {
|
|
182
|
+
values.append(v)
|
|
183
|
+
keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
case "scale", "scaleX":
|
|
187
|
+
keyPath = "transform.scale.x"
|
|
188
|
+
for kf in keyframes {
|
|
189
|
+
if let v = kf[propKey] as? Double {
|
|
190
|
+
values.append(v)
|
|
191
|
+
keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
case "scaleY":
|
|
195
|
+
keyPath = "transform.scale.y"
|
|
196
|
+
for kf in keyframes {
|
|
197
|
+
if let v = kf[propKey] as? Double {
|
|
198
|
+
values.append(v)
|
|
199
|
+
keyTimes.append(NSNumber(value: kf["offset"] as? Double ?? 0))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if !values.isEmpty {
|
|
207
|
+
let anim = CAKeyframeAnimation(keyPath: keyPath)
|
|
208
|
+
anim.values = values
|
|
209
|
+
anim.keyTimes = keyTimes
|
|
210
|
+
anim.duration = duration
|
|
211
|
+
animations.append(anim)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if animations.isEmpty { callback(nil, nil); return }
|
|
216
|
+
group.animations = animations
|
|
217
|
+
|
|
218
|
+
CATransaction.begin()
|
|
219
|
+
CATransaction.setCompletionBlock { callback(nil, nil) }
|
|
220
|
+
layer.add(group, forKey: "keyframeAnimation")
|
|
221
|
+
CATransaction.commit()
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// MARK: - sequence([animData])
|
|
226
|
+
|
|
227
|
+
private func runSequence(animationsData: [[String: Any]], index: Int, callback: @escaping (Any?, String?) -> Void) {
|
|
228
|
+
guard index < animationsData.count else { callback(nil, nil); return }
|
|
229
|
+
let animData = animationsData[index]
|
|
230
|
+
let method = animData["type"] as? String ?? "timing"
|
|
231
|
+
let viewId = animData["viewId"] as? Int ?? 0
|
|
232
|
+
let toStyles = animData["toStyles"] as? [String: Any] ?? [:]
|
|
233
|
+
let options = animData["options"] as? [String: Any] ?? [:]
|
|
234
|
+
|
|
235
|
+
runSingleAnimation(method: method, viewId: viewId, toStyles: toStyles, options: options) { [weak self] _, error in
|
|
236
|
+
if let error = error { callback(nil, error); return }
|
|
237
|
+
self?.runSequence(animationsData: animationsData, index: index + 1, callback: callback)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private func runSingleAnimation(method: String, viewId: Int, toStyles: [String: Any], options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
|
|
242
|
+
switch method {
|
|
243
|
+
case "timing":
|
|
244
|
+
invoke(method: "timing", args: [viewId, toStyles, options], callback: callback)
|
|
245
|
+
case "spring":
|
|
246
|
+
invoke(method: "spring", args: [viewId, toStyles, options], callback: callback)
|
|
247
|
+
default:
|
|
248
|
+
callback(nil, "Unknown animation type: \(method)")
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// MARK: - parallel([animData])
|
|
253
|
+
|
|
254
|
+
private func runParallel(animationsData: [[String: Any]], callback: @escaping (Any?, String?) -> Void) {
|
|
255
|
+
guard !animationsData.isEmpty else { callback(nil, nil); return }
|
|
256
|
+
let total = animationsData.count
|
|
257
|
+
var completed = 0
|
|
258
|
+
let lock = NSLock()
|
|
259
|
+
|
|
260
|
+
for animData in animationsData {
|
|
261
|
+
let method = animData["type"] as? String ?? "timing"
|
|
262
|
+
let viewId = animData["viewId"] as? Int ?? 0
|
|
263
|
+
let toStyles = animData["toStyles"] as? [String: Any] ?? [:]
|
|
264
|
+
let options = animData["options"] as? [String: Any] ?? [:]
|
|
265
|
+
|
|
266
|
+
runSingleAnimation(method: method, viewId: viewId, toStyles: toStyles, options: options) { _, _ in
|
|
267
|
+
lock.lock()
|
|
268
|
+
completed += 1
|
|
269
|
+
let allDone = completed == total
|
|
270
|
+
lock.unlock()
|
|
271
|
+
if allDone { callback(nil, nil) }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// MARK: - Helpers
|
|
277
|
+
|
|
278
|
+
private func coerceInt(_ value: Any) -> Int? {
|
|
279
|
+
if let i = value as? Int { return i }
|
|
280
|
+
if let d = value as? Double { return Int(d) }
|
|
281
|
+
return nil
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// Apply an animatable style property to an NSView.
|
|
285
|
+
/// For full style application, the bridge's StyleEngine should be used;
|
|
286
|
+
/// this handles the common animatable subset.
|
|
287
|
+
private func applyAnimatableStyle(key: String, value: Any?, to view: NSView) {
|
|
288
|
+
guard let layer = view.layer else { return }
|
|
289
|
+
switch key {
|
|
290
|
+
case "opacity":
|
|
291
|
+
if let v = value as? Double {
|
|
292
|
+
view.alphaValue = CGFloat(v)
|
|
293
|
+
}
|
|
294
|
+
case "backgroundColor":
|
|
295
|
+
if let hex = value as? String {
|
|
296
|
+
layer.backgroundColor = NSColor.fromHex(hex)?.cgColor
|
|
297
|
+
}
|
|
298
|
+
case "translateX":
|
|
299
|
+
if let v = value as? Double {
|
|
300
|
+
var transform = view.layer?.affineTransform() ?? .identity
|
|
301
|
+
transform.tx = CGFloat(v)
|
|
302
|
+
view.layer?.setAffineTransform(transform)
|
|
303
|
+
}
|
|
304
|
+
case "translateY":
|
|
305
|
+
if let v = value as? Double {
|
|
306
|
+
var transform = view.layer?.affineTransform() ?? .identity
|
|
307
|
+
transform.ty = CGFloat(v)
|
|
308
|
+
view.layer?.setAffineTransform(transform)
|
|
309
|
+
}
|
|
310
|
+
case "scale":
|
|
311
|
+
if let v = value as? Double {
|
|
312
|
+
let cv = CGFloat(v)
|
|
313
|
+
view.layer?.setAffineTransform(CGAffineTransform(scaleX: cv, y: cv))
|
|
314
|
+
}
|
|
315
|
+
case "borderRadius":
|
|
316
|
+
if let v = value as? Double {
|
|
317
|
+
layer.cornerRadius = CGFloat(v)
|
|
318
|
+
}
|
|
319
|
+
default:
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// MARK: - NSColor hex helper
|
|
326
|
+
|
|
327
|
+
private extension NSColor {
|
|
328
|
+
static func fromHex(_ hex: String) -> NSColor? {
|
|
329
|
+
var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
330
|
+
if hexString.hasPrefix("#") { hexString.removeFirst() }
|
|
331
|
+
|
|
332
|
+
var rgb: UInt64 = 0
|
|
333
|
+
guard Scanner(string: hexString).scanHexInt64(&rgb) else { return nil }
|
|
334
|
+
|
|
335
|
+
switch hexString.count {
|
|
336
|
+
case 6:
|
|
337
|
+
return NSColor(
|
|
338
|
+
red: CGFloat((rgb >> 16) & 0xFF) / 255.0,
|
|
339
|
+
green: CGFloat((rgb >> 8) & 0xFF) / 255.0,
|
|
340
|
+
blue: CGFloat(rgb & 0xFF) / 255.0,
|
|
341
|
+
alpha: 1.0
|
|
342
|
+
)
|
|
343
|
+
case 8:
|
|
344
|
+
return NSColor(
|
|
345
|
+
red: CGFloat((rgb >> 24) & 0xFF) / 255.0,
|
|
346
|
+
green: CGFloat((rgb >> 16) & 0xFF) / 255.0,
|
|
347
|
+
blue: CGFloat((rgb >> 8) & 0xFF) / 255.0,
|
|
348
|
+
alpha: CGFloat(rgb & 0xFF) / 255.0
|
|
349
|
+
)
|
|
350
|
+
default:
|
|
351
|
+
return nil
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import VueNativeShared
|
|
3
|
+
|
|
4
|
+
/// Native module providing application lifecycle state notifications.
|
|
5
|
+
///
|
|
6
|
+
/// Events dispatched:
|
|
7
|
+
/// - appState:change { state: "active"|"inactive"|"background" }
|
|
8
|
+
///
|
|
9
|
+
/// Methods:
|
|
10
|
+
/// - getState() -> String
|
|
11
|
+
final class AppStateModule: NSObject, NativeModule {
|
|
12
|
+
var moduleName: String { "AppState" }
|
|
13
|
+
private weak var dispatcher: NativeEventDispatcher?
|
|
14
|
+
private var observers: [NSObjectProtocol] = []
|
|
15
|
+
|
|
16
|
+
init(dispatcher: NativeEventDispatcher) {
|
|
17
|
+
self.dispatcher = dispatcher
|
|
18
|
+
super.init()
|
|
19
|
+
setupObservers()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private func setupObservers() {
|
|
23
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
24
|
+
forName: NSApplication.didBecomeActiveNotification, object: nil, queue: .main
|
|
25
|
+
) { [weak self] _ in
|
|
26
|
+
self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "active"])
|
|
27
|
+
})
|
|
28
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
29
|
+
forName: NSApplication.didResignActiveNotification, object: nil, queue: .main
|
|
30
|
+
) { [weak self] _ in
|
|
31
|
+
self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "inactive"])
|
|
32
|
+
})
|
|
33
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
34
|
+
forName: NSApplication.didHideNotification, object: nil, queue: .main
|
|
35
|
+
) { [weak self] _ in
|
|
36
|
+
self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "background"])
|
|
37
|
+
})
|
|
38
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
39
|
+
forName: NSApplication.didUnhideNotification, object: nil, queue: .main
|
|
40
|
+
) { [weak self] _ in
|
|
41
|
+
self?.dispatcher?.dispatchGlobalEvent("appState:change", payload: ["state": "active"])
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
deinit {
|
|
46
|
+
observers.forEach { NotificationCenter.default.removeObserver($0) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
50
|
+
switch method {
|
|
51
|
+
case "getState":
|
|
52
|
+
DispatchQueue.main.async {
|
|
53
|
+
let state = NSApp.isActive ? "active" : "inactive"
|
|
54
|
+
callback(state, nil)
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
callback(nil, "AppStateModule: Unknown method '\(method)'")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func invokeSync(method: String, args: [Any]) -> Any? { nil }
|
|
62
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import LocalAuthentication
|
|
2
|
+
import VueNativeShared
|
|
3
|
+
|
|
4
|
+
/// Native module providing biometric authentication (Touch ID) on macOS.
|
|
5
|
+
///
|
|
6
|
+
/// Methods:
|
|
7
|
+
/// - isAvailable() -> { available: Bool, biometryType: "touchId"/"none" }
|
|
8
|
+
/// - authenticate(reason: String) -> { success: Bool }
|
|
9
|
+
final class BiometryModule: NativeModule {
|
|
10
|
+
let moduleName = "Biometry"
|
|
11
|
+
|
|
12
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
13
|
+
switch method {
|
|
14
|
+
case "isAvailable":
|
|
15
|
+
let context = LAContext()
|
|
16
|
+
var error: NSError?
|
|
17
|
+
let canEvaluate = context.canEvaluatePolicy(
|
|
18
|
+
.deviceOwnerAuthenticationWithBiometrics,
|
|
19
|
+
error: &error
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
let biometryType: String
|
|
23
|
+
if canEvaluate {
|
|
24
|
+
switch context.biometryType {
|
|
25
|
+
case .touchID:
|
|
26
|
+
biometryType = "touchId"
|
|
27
|
+
default:
|
|
28
|
+
biometryType = "none"
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
biometryType = "none"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let result: [String: Any] = [
|
|
35
|
+
"available": canEvaluate,
|
|
36
|
+
"biometryType": biometryType
|
|
37
|
+
]
|
|
38
|
+
callback(result, nil)
|
|
39
|
+
|
|
40
|
+
case "authenticate":
|
|
41
|
+
let reason = args.first as? String ?? "Authenticate"
|
|
42
|
+
let context = LAContext()
|
|
43
|
+
|
|
44
|
+
context.evaluatePolicy(
|
|
45
|
+
.deviceOwnerAuthenticationWithBiometrics,
|
|
46
|
+
localizedReason: reason
|
|
47
|
+
) { success, error in
|
|
48
|
+
if success {
|
|
49
|
+
callback(["success": true], nil)
|
|
50
|
+
} else {
|
|
51
|
+
let message = error?.localizedDescription ?? "Authentication failed"
|
|
52
|
+
callback(nil, message)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
callback(nil, "BiometryModule: Unknown method '\(method)'")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import AppKit
|
|
3
|
+
import VueNativeShared
|
|
4
|
+
|
|
5
|
+
/// Native module providing camera access on macOS.
|
|
6
|
+
///
|
|
7
|
+
/// Methods:
|
|
8
|
+
/// - checkPermission() -> "granted"/"denied"/"undetermined"
|
|
9
|
+
/// - requestPermission() -> Bool
|
|
10
|
+
/// - takePicture() -> { uri, width, height }
|
|
11
|
+
/// - getAvailableCameras() -> [{ id, name, position }]
|
|
12
|
+
final class CameraModule: NativeModule {
|
|
13
|
+
let moduleName = "Camera"
|
|
14
|
+
|
|
15
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
16
|
+
switch method {
|
|
17
|
+
case "checkPermission":
|
|
18
|
+
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
19
|
+
let result: String
|
|
20
|
+
switch status {
|
|
21
|
+
case .authorized:
|
|
22
|
+
result = "granted"
|
|
23
|
+
case .denied, .restricted:
|
|
24
|
+
result = "denied"
|
|
25
|
+
case .notDetermined:
|
|
26
|
+
result = "undetermined"
|
|
27
|
+
@unknown default:
|
|
28
|
+
result = "undetermined"
|
|
29
|
+
}
|
|
30
|
+
callback(result, nil)
|
|
31
|
+
|
|
32
|
+
case "requestPermission":
|
|
33
|
+
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
34
|
+
callback(granted, nil)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case "takePicture":
|
|
38
|
+
takePicture(callback: callback)
|
|
39
|
+
|
|
40
|
+
case "getAvailableCameras":
|
|
41
|
+
let discoverySession = AVCaptureDevice.DiscoverySession(
|
|
42
|
+
deviceTypes: [.builtInWideAngleCamera, .externalUnknown],
|
|
43
|
+
mediaType: .video,
|
|
44
|
+
position: .unspecified
|
|
45
|
+
)
|
|
46
|
+
let cameras: [[String: Any]] = discoverySession.devices.map { device in
|
|
47
|
+
let position: String
|
|
48
|
+
switch device.position {
|
|
49
|
+
case .front: position = "front"
|
|
50
|
+
case .back: position = "back"
|
|
51
|
+
default: position = "unspecified"
|
|
52
|
+
}
|
|
53
|
+
return [
|
|
54
|
+
"id": device.uniqueID,
|
|
55
|
+
"name": device.localizedName,
|
|
56
|
+
"position": position
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
callback(cameras, nil)
|
|
60
|
+
|
|
61
|
+
default:
|
|
62
|
+
callback(nil, "CameraModule: Unknown method '\(method)'")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - Photo capture
|
|
67
|
+
|
|
68
|
+
private func takePicture(callback: @escaping (Any?, String?) -> Void) {
|
|
69
|
+
guard let device = AVCaptureDevice.default(for: .video) else {
|
|
70
|
+
callback(nil, "No camera available")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let session = AVCaptureSession()
|
|
75
|
+
session.sessionPreset = .photo
|
|
76
|
+
|
|
77
|
+
do {
|
|
78
|
+
let input = try AVCaptureDeviceInput(device: device)
|
|
79
|
+
guard session.canAddInput(input) else {
|
|
80
|
+
callback(nil, "Cannot add camera input")
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
session.addInput(input)
|
|
84
|
+
} catch {
|
|
85
|
+
callback(nil, "Camera input error: \(error.localizedDescription)")
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let photoOutput = AVCapturePhotoOutput()
|
|
90
|
+
guard session.canAddOutput(photoOutput) else {
|
|
91
|
+
callback(nil, "Cannot add photo output")
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
session.addOutput(photoOutput)
|
|
95
|
+
|
|
96
|
+
let delegate = PhotoCaptureDelegate(session: session, callback: callback)
|
|
97
|
+
// Prevent delegate from being deallocated before capture completes
|
|
98
|
+
PhotoCaptureDelegate.activeDelegates.insert(delegate)
|
|
99
|
+
|
|
100
|
+
session.startRunning()
|
|
101
|
+
|
|
102
|
+
let settings = AVCapturePhotoSettings()
|
|
103
|
+
photoOutput.capturePhoto(with: settings, delegate: delegate)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - PhotoCaptureDelegate
|
|
108
|
+
|
|
109
|
+
private final class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|
110
|
+
|
|
111
|
+
/// Strong references to active delegates to prevent premature deallocation.
|
|
112
|
+
static var activeDelegates = Set<PhotoCaptureDelegate>()
|
|
113
|
+
|
|
114
|
+
private let session: AVCaptureSession
|
|
115
|
+
private let callback: (Any?, String?) -> Void
|
|
116
|
+
|
|
117
|
+
init(session: AVCaptureSession, callback: @escaping (Any?, String?) -> Void) {
|
|
118
|
+
self.session = session
|
|
119
|
+
self.callback = callback
|
|
120
|
+
super.init()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func photoOutput(
|
|
124
|
+
_ output: AVCapturePhotoOutput,
|
|
125
|
+
didFinishProcessingPhoto photo: AVCapturePhoto,
|
|
126
|
+
error: Error?
|
|
127
|
+
) {
|
|
128
|
+
defer {
|
|
129
|
+
session.stopRunning()
|
|
130
|
+
PhotoCaptureDelegate.activeDelegates.remove(self)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if let error = error {
|
|
134
|
+
callback(nil, "Photo capture error: \(error.localizedDescription)")
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
guard let imageData = photo.fileDataRepresentation() else {
|
|
139
|
+
callback(nil, "Failed to get photo data")
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
guard let image = NSImage(data: imageData) else {
|
|
144
|
+
callback(nil, "Failed to create image from data")
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Write to temp file
|
|
149
|
+
let tempDir = NSTemporaryDirectory()
|
|
150
|
+
let fileName = "vue_native_photo_\(UUID().uuidString).jpg"
|
|
151
|
+
let filePath = (tempDir as NSString).appendingPathComponent(fileName)
|
|
152
|
+
|
|
153
|
+
do {
|
|
154
|
+
try imageData.write(to: URL(fileURLWithPath: filePath))
|
|
155
|
+
} catch {
|
|
156
|
+
callback(nil, "Failed to save photo: \(error.localizedDescription)")
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let result: [String: Any] = [
|
|
161
|
+
"uri": filePath,
|
|
162
|
+
"width": image.size.width,
|
|
163
|
+
"height": image.size.height
|
|
164
|
+
]
|
|
165
|
+
callback(result, nil)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import VueNativeShared
|
|
3
|
+
|
|
4
|
+
/// Native module providing clipboard access via NSPasteboard.
|
|
5
|
+
///
|
|
6
|
+
/// Methods:
|
|
7
|
+
/// - copy(text: String)
|
|
8
|
+
/// - paste() -> String?
|
|
9
|
+
final class ClipboardModule: NativeModule {
|
|
10
|
+
let moduleName = "Clipboard"
|
|
11
|
+
|
|
12
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
13
|
+
DispatchQueue.main.async {
|
|
14
|
+
switch method {
|
|
15
|
+
case "copy":
|
|
16
|
+
guard let text = args.first as? String else {
|
|
17
|
+
callback(nil, "copy: missing text")
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
let pasteboard = NSPasteboard.general
|
|
21
|
+
pasteboard.clearContents()
|
|
22
|
+
pasteboard.setString(text, forType: .string)
|
|
23
|
+
callback(nil, nil)
|
|
24
|
+
|
|
25
|
+
case "paste":
|
|
26
|
+
let text = NSPasteboard.general.string(forType: .string)
|
|
27
|
+
callback(text, nil)
|
|
28
|
+
|
|
29
|
+
default:
|
|
30
|
+
callback(nil, "ClipboardModule: Unknown method '\(method)'")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|