@ohah/hwpjs 0.1.0-rc.1
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/Hwpjs.podspec +27 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/android/CMakeLists.txt +50 -0
- package/android/build.gradle +110 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/rs/craby/hwpjs/HwpjsPackage.kt +59 -0
- package/android/src/main/jni/OnLoad.cpp +22 -0
- package/android/src/main/jni/include/CrabySignals.h +53 -0
- package/android/src/main/jni/include/cxx.h +1150 -0
- package/android/src/main/jni/include/ffi.rs.h +1041 -0
- package/android/src/main/jni/libs/arm64-v8a/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/arm64-v8a/libreactnative-prebuilt.a +0 -0
- package/android/src/main/jni/libs/armeabi-v7a/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/armeabi-v7a/libreactnative-prebuilt.a +0 -0
- package/android/src/main/jni/libs/x86/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/x86/libreactnative-prebuilt.a +0 -0
- package/android/src/main/jni/libs/x86_64/libhwpjs-prebuilt.a +0 -0
- package/android/src/main/jni/libs/x86_64/libreactnative-prebuilt.a +0 -0
- package/android/src/main/jni/src/ffi.rs.cc +1179 -0
- package/android/stubs/CMakeLists.txt +31 -0
- package/android/stubs/Hwpjs_stub.cpp +9 -0
- package/android/stubs/Hwpjs_stub.h +12 -0
- package/cpp/CrabyUtils.hpp +91 -0
- package/cpp/CxxHwpjsModule.cpp +125 -0
- package/cpp/CxxHwpjsModule.hpp +53 -0
- package/cpp/bridging-generated.hpp +158 -0
- package/dist/browser.js +1 -0
- package/dist/hwpjs-napi.wasi-browser.js +61 -0
- package/dist/hwpjs-napi.wasi.cjs +113 -0
- package/dist/hwpjs.wasi-browser.js +61 -0
- package/dist/hwpjs.wasi.cjs +113 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +577 -0
- package/dist/react-native/index.cjs +40 -0
- package/dist/react-native/index.cjs.map +1 -0
- package/dist/react-native/index.d.cts +22 -0
- package/dist/react-native/index.d.mts +22 -0
- package/dist/react-native/index.mjs +40 -0
- package/dist/react-native/index.mjs.map +1 -0
- package/dist/wasi-worker-browser.mjs +32 -0
- package/dist/wasi-worker.mjs +63 -0
- package/ios/HwpjsModuleProvider.mm +47 -0
- package/ios/framework/libhwpjs.xcframework/Info.plist +44 -0
- package/ios/framework/libhwpjs.xcframework/ios-arm64/libhwpjs-prebuilt.a +0 -0
- package/ios/framework/libhwpjs.xcframework/ios-arm64_x86_64-simulator/libhwpjs-prebuilt.a +0 -0
- package/ios/framework/libreactnative.xcframework/Info.plist +44 -0
- package/ios/framework/libreactnative.xcframework/ios-arm64/libreactnative-prebuilt.a +0 -0
- package/ios/framework/libreactnative.xcframework/ios-arm64_x86_64-simulator/libreactnative-prebuilt.a +0 -0
- package/ios/include/CrabySignals.h +53 -0
- package/ios/include/cxx.h +1150 -0
- package/ios/include/ffi.rs.h +1041 -0
- package/ios/src/ffi.rs.cc +1179 -0
- package/package.json +147 -0
- package/react-native.config.js +24 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Platform, TurboModuleRegistry } from "react-native";
|
|
2
|
+
|
|
3
|
+
//#region ../../node_modules/.bun/craby-modules@0.1.0-rc.2+0891b211bdee886f/node_modules/craby-modules/dist/index.mjs
|
|
4
|
+
/**
|
|
5
|
+
* Android JNI initialization workaround
|
|
6
|
+
*
|
|
7
|
+
* We need `filesDir` of `Context` for JNI initialization, but it's unavailable during `PackageList` construction.
|
|
8
|
+
* The context is only passed when React Native calls `BaseReactPackage.getModule()`.
|
|
9
|
+
*
|
|
10
|
+
* Workaround: Load a dummy module to trigger `getModule()` before the actual module.
|
|
11
|
+
*
|
|
12
|
+
* - 1. Request non-existent module → triggers `getModule()`
|
|
13
|
+
* - 2. `getModule()` receives `ReactApplicationContext`
|
|
14
|
+
* - 2-1. Calls `nativeSetDataPath()` (C++ extern function) to set `context.filesDir.absolutePath`
|
|
15
|
+
* - 2-2. Returns placeholder module (no-op) instance (Actual C++ TurboModule is now can be initialized with the required values)
|
|
16
|
+
*
|
|
17
|
+
* @param moduleName The name of the module to prepare.
|
|
18
|
+
*/
|
|
19
|
+
function prepareJNI(moduleName) {
|
|
20
|
+
if (Platform.OS !== "android") return;
|
|
21
|
+
TurboModuleRegistry.get(`__craby${moduleName}_JNI_prepare__`);
|
|
22
|
+
}
|
|
23
|
+
const NativeModuleRegistry = {
|
|
24
|
+
get(moduleName) {
|
|
25
|
+
prepareJNI(moduleName);
|
|
26
|
+
return TurboModuleRegistry.get(moduleName);
|
|
27
|
+
},
|
|
28
|
+
getEnforcing(moduleName) {
|
|
29
|
+
prepareJNI(moduleName);
|
|
30
|
+
return TurboModuleRegistry.getEnforcing(moduleName);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src-reactnative/NativeReactNative.ts
|
|
36
|
+
var NativeReactNative_default = NativeModuleRegistry.getEnforcing("Hwpjs");
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { NativeReactNative_default as Hwpjs };
|
|
40
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../node_modules/.bun/craby-modules@0.1.0-rc.2+0891b211bdee886f/node_modules/craby-modules/dist/index.mjs","../../src-reactnative/NativeReactNative.ts"],"sourcesContent":["import { Platform, TurboModuleRegistry } from \"react-native\";\n\n//#region src/index.ts\n/**\n* Android JNI initialization workaround\n*\n* We need `filesDir` of `Context` for JNI initialization, but it's unavailable during `PackageList` construction.\n* The context is only passed when React Native calls `BaseReactPackage.getModule()`.\n*\n* Workaround: Load a dummy module to trigger `getModule()` before the actual module.\n*\n* - 1. Request non-existent module → triggers `getModule()`\n* - 2. `getModule()` receives `ReactApplicationContext`\n* - 2-1. Calls `nativeSetDataPath()` (C++ extern function) to set `context.filesDir.absolutePath`\n* - 2-2. Returns placeholder module (no-op) instance (Actual C++ TurboModule is now can be initialized with the required values)\n*\n* @param moduleName The name of the module to prepare.\n*/\nfunction prepareJNI(moduleName) {\n\tif (Platform.OS !== \"android\") return;\n\tTurboModuleRegistry.get(`__craby${moduleName}_JNI_prepare__`);\n}\nconst NativeModuleRegistry = {\n\tget(moduleName) {\n\t\tprepareJNI(moduleName);\n\t\treturn TurboModuleRegistry.get(moduleName);\n\t},\n\tgetEnforcing(moduleName) {\n\t\tprepareJNI(moduleName);\n\t\treturn TurboModuleRegistry.getEnforcing(moduleName);\n\t}\n};\n\n//#endregion\nexport { NativeModuleRegistry };","import type { NativeModule } from 'craby-modules';\nimport { NativeModuleRegistry } from 'craby-modules';\n\nexport interface ToMarkdownOptions {\n imageOutputDir: string | null;\n image: string | null;\n useHtml: boolean;\n includeVersion: boolean;\n includePageInfo: boolean;\n}\n\nexport interface ImageData {\n id: string;\n data: number[];\n format: string;\n}\n\nexport interface ToMarkdownResult {\n markdown: string;\n}\n\ninterface Spec extends NativeModule {\n toJson(data: number[]): string;\n toMarkdown(data: number[], options: ToMarkdownOptions): ToMarkdownResult;\n fileHeader(data: number[]): string;\n}\n\nexport default NativeModuleRegistry.getEnforcing<Spec>('Hwpjs');\n"],"x_google_ignoreList":[0],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,WAAW,YAAY;AAC/B,KAAI,SAAS,OAAO,UAAW;AAC/B,qBAAoB,IAAI,UAAU,WAAW,gBAAgB;;AAE9D,MAAM,uBAAuB;CAC5B,IAAI,YAAY;AACf,aAAW,WAAW;AACtB,SAAO,oBAAoB,IAAI,WAAW;;CAE3C,aAAa,YAAY;AACxB,aAAW,WAAW;AACtB,SAAO,oBAAoB,aAAa,WAAW;;CAEpD;;;;ACJD,gCAAe,qBAAqB,aAAmB,QAAQ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime'
|
|
2
|
+
|
|
3
|
+
const handler = new MessageHandler({
|
|
4
|
+
onLoad({ wasmModule, wasmMemory }) {
|
|
5
|
+
const wasi = new WASI({
|
|
6
|
+
print: function () {
|
|
7
|
+
// eslint-disable-next-line no-console
|
|
8
|
+
console.log.apply(console, arguments)
|
|
9
|
+
},
|
|
10
|
+
printErr: function() {
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
console.error.apply(console, arguments)
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
return instantiateNapiModuleSync(wasmModule, {
|
|
16
|
+
childThread: true,
|
|
17
|
+
wasi,
|
|
18
|
+
overwriteImports(importObject) {
|
|
19
|
+
importObject.env = {
|
|
20
|
+
...importObject.env,
|
|
21
|
+
...importObject.napi,
|
|
22
|
+
...importObject.emnapi,
|
|
23
|
+
memory: wasmMemory,
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
globalThis.onmessage = function (e) {
|
|
31
|
+
handler.handle(e)
|
|
32
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { parse } from "node:path";
|
|
4
|
+
import { WASI } from "node:wasi";
|
|
5
|
+
import { parentPort, Worker } from "node:worker_threads";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
const { instantiateNapiModuleSync, MessageHandler, getDefaultContext } = require("@napi-rs/wasm-runtime");
|
|
10
|
+
|
|
11
|
+
if (parentPort) {
|
|
12
|
+
parentPort.on("message", (data) => {
|
|
13
|
+
globalThis.onmessage({ data });
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Object.assign(globalThis, {
|
|
18
|
+
self: globalThis,
|
|
19
|
+
require,
|
|
20
|
+
Worker,
|
|
21
|
+
importScripts: function (f) {
|
|
22
|
+
;(0, eval)(fs.readFileSync(f, "utf8") + "//# sourceURL=" + f);
|
|
23
|
+
},
|
|
24
|
+
postMessage: function (msg) {
|
|
25
|
+
if (parentPort) {
|
|
26
|
+
parentPort.postMessage(msg);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const emnapiContext = getDefaultContext();
|
|
32
|
+
|
|
33
|
+
const __rootDir = parse(process.cwd()).root;
|
|
34
|
+
|
|
35
|
+
const handler = new MessageHandler({
|
|
36
|
+
onLoad({ wasmModule, wasmMemory }) {
|
|
37
|
+
const wasi = new WASI({
|
|
38
|
+
version: 'preview1',
|
|
39
|
+
env: process.env,
|
|
40
|
+
preopens: {
|
|
41
|
+
[__rootDir]: __rootDir,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return instantiateNapiModuleSync(wasmModule, {
|
|
46
|
+
childThread: true,
|
|
47
|
+
wasi,
|
|
48
|
+
context: emnapiContext,
|
|
49
|
+
overwriteImports(importObject) {
|
|
50
|
+
importObject.env = {
|
|
51
|
+
...importObject.env,
|
|
52
|
+
...importObject.napi,
|
|
53
|
+
...importObject.emnapi,
|
|
54
|
+
memory: wasmMemory
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
globalThis.onmessage = function (e) {
|
|
62
|
+
handler.handle(e);
|
|
63
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Auto generated by Craby. DO NOT EDIT.
|
|
2
|
+
#import "CxxHwpjsModule.hpp"
|
|
3
|
+
#import <ReactCommon/CxxTurboModuleUtils.h>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
@interface HwpjsModuleProvider : NSObject
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
@implementation HwpjsModuleProvider
|
|
10
|
+
|
|
11
|
+
+ (void)load {
|
|
12
|
+
const char *cDataPath = [[self getDataPath] UTF8String];
|
|
13
|
+
std::string dataPath(cDataPath);
|
|
14
|
+
|
|
15
|
+
craby::hwpjs::modules::CxxHwpjsModule::dataPath = dataPath;
|
|
16
|
+
|
|
17
|
+
facebook::react::registerCxxModuleToGlobalModuleMap(
|
|
18
|
+
craby::hwpjs::modules::CxxHwpjsModule::kModuleName,
|
|
19
|
+
[](std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
|
20
|
+
return std::make_shared<craby::hwpjs::modules::CxxHwpjsModule>(jsInvoker);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
+ (NSString *)getDataPath {
|
|
25
|
+
NSString *appGroupID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroupID"];
|
|
26
|
+
NSString *dataPath = nil;
|
|
27
|
+
|
|
28
|
+
if (appGroupID != nil) {
|
|
29
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
30
|
+
NSURL *containerURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:appGroupID];
|
|
31
|
+
|
|
32
|
+
if (containerURL == nil) {
|
|
33
|
+
throw [NSException exceptionWithName:@"CrabyInitializationException"
|
|
34
|
+
reason:[NSString stringWithFormat:@"Invalid AppGroup ID: %@", appGroupID]
|
|
35
|
+
userInfo:nil];
|
|
36
|
+
} else {
|
|
37
|
+
dataPath = [containerURL path];
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
|
|
41
|
+
dataPath = [paths firstObject];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return dataPath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>AvailableLibraries</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>BinaryPath</key>
|
|
9
|
+
<string>libhwpjs-prebuilt.a</string>
|
|
10
|
+
<key>LibraryIdentifier</key>
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
|
+
<key>LibraryPath</key>
|
|
13
|
+
<string>libhwpjs-prebuilt.a</string>
|
|
14
|
+
<key>SupportedArchitectures</key>
|
|
15
|
+
<array>
|
|
16
|
+
<string>arm64</string>
|
|
17
|
+
</array>
|
|
18
|
+
<key>SupportedPlatform</key>
|
|
19
|
+
<string>ios</string>
|
|
20
|
+
</dict>
|
|
21
|
+
<dict>
|
|
22
|
+
<key>BinaryPath</key>
|
|
23
|
+
<string>libhwpjs-prebuilt.a</string>
|
|
24
|
+
<key>LibraryIdentifier</key>
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
26
|
+
<key>LibraryPath</key>
|
|
27
|
+
<string>libhwpjs-prebuilt.a</string>
|
|
28
|
+
<key>SupportedArchitectures</key>
|
|
29
|
+
<array>
|
|
30
|
+
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>SupportedPlatform</key>
|
|
34
|
+
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
|
+
</dict>
|
|
38
|
+
</array>
|
|
39
|
+
<key>CFBundlePackageType</key>
|
|
40
|
+
<string>XFWK</string>
|
|
41
|
+
<key>XCFrameworkFormatVersion</key>
|
|
42
|
+
<string>1.0</string>
|
|
43
|
+
</dict>
|
|
44
|
+
</plist>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>AvailableLibraries</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>BinaryPath</key>
|
|
9
|
+
<string>libreactnative-prebuilt.a</string>
|
|
10
|
+
<key>LibraryIdentifier</key>
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
|
+
<key>LibraryPath</key>
|
|
13
|
+
<string>libreactnative-prebuilt.a</string>
|
|
14
|
+
<key>SupportedArchitectures</key>
|
|
15
|
+
<array>
|
|
16
|
+
<string>arm64</string>
|
|
17
|
+
</array>
|
|
18
|
+
<key>SupportedPlatform</key>
|
|
19
|
+
<string>ios</string>
|
|
20
|
+
</dict>
|
|
21
|
+
<dict>
|
|
22
|
+
<key>BinaryPath</key>
|
|
23
|
+
<string>libreactnative-prebuilt.a</string>
|
|
24
|
+
<key>LibraryIdentifier</key>
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
26
|
+
<key>LibraryPath</key>
|
|
27
|
+
<string>libreactnative-prebuilt.a</string>
|
|
28
|
+
<key>SupportedArchitectures</key>
|
|
29
|
+
<array>
|
|
30
|
+
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>SupportedPlatform</key>
|
|
34
|
+
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
|
+
</dict>
|
|
38
|
+
</array>
|
|
39
|
+
<key>CFBundlePackageType</key>
|
|
40
|
+
<string>XFWK</string>
|
|
41
|
+
<key>XCFrameworkFormatVersion</key>
|
|
42
|
+
<string>1.0</string>
|
|
43
|
+
</dict>
|
|
44
|
+
</plist>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "cxx.h"
|
|
4
|
+
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
#include <unordered_map>
|
|
9
|
+
|
|
10
|
+
namespace craby {
|
|
11
|
+
namespace reactnative {
|
|
12
|
+
namespace signals {
|
|
13
|
+
|
|
14
|
+
using Delegate = std::function<void(const std::string& signalName)>;
|
|
15
|
+
|
|
16
|
+
class SignalManager {
|
|
17
|
+
public:
|
|
18
|
+
static SignalManager& getInstance() {
|
|
19
|
+
static SignalManager instance;
|
|
20
|
+
return instance;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void emit(uintptr_t id, rust::Str name) const {
|
|
24
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
25
|
+
auto it = delegates_.find(id);
|
|
26
|
+
if (it != delegates_.end()) {
|
|
27
|
+
it->second(std::string(name));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
void registerDelegate(uintptr_t id, Delegate delegate) const {
|
|
32
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
33
|
+
delegates_.insert_or_assign(id, delegate);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void unregisterDelegate(uintptr_t id) const {
|
|
37
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
38
|
+
delegates_.erase(id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private:
|
|
42
|
+
SignalManager() = default;
|
|
43
|
+
mutable std::unordered_map<uintptr_t, Delegate> delegates_;
|
|
44
|
+
mutable std::mutex mutex_;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
inline const SignalManager& getSignalManager() {
|
|
48
|
+
return SignalManager::getInstance();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} // namespace signals
|
|
52
|
+
} // namespace reactnative
|
|
53
|
+
} // namespace craby
|