(ofType: T.Type) -> [T] {
18 | return compactMap { $0 as? T }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/hud-error-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "hud-error-icon@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "hud-error-icon@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/back-arrow-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "back-arrow-icon@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "back-arrow-icon@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/down-arrow-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "down-arrow-icon@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "down-arrow-icon@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/hud-success-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "hud-success-icon@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "hud-success-icon@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/scan-effect-img.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "scan-effect-img@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "scan-effect-img@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/example/src/components/VModelTest.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Custom Component
4 |
5 |
Text: {{ modelValue }}
6 |
7 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | login 和 checkSession 接口需要根据自生业务在 native 端自行实现。具体请查看 EngineConfig.hooks 的
4 | openAPI。
5 |
6 | Code: {{ code }}
7 |
8 |
9 |
10 |
20 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/SetTitle.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
18 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/back-arrow-icon-dark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "back-arrow-icon-dark@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "back-arrow-icon-dark@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/mp-action-sheet-share-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "mp-action-sheet-share-icon@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "mp-action-sheet-share-icon@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/test/src/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "com.evokerdev.test",
3 | "version": "1.0.0",
4 | "window": {
5 | "navigationBarBackgroundColor": "#ffffff",
6 | "navigationBarTextStyle": "black",
7 | "navigationBarTitleText": "Evoker",
8 | "navigationStyle": "default",
9 | "backgroundColor": "#f3f4f6"
10 | },
11 | "pages": [
12 | "pages/Index",
13 | "pages/Base",
14 | "pages/System",
15 | "pages/Interaction",
16 | "pages/Navigation",
17 | "pages/Storage",
18 | "pages/Network",
19 | "pages/WebSocket"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/PullDownRefresh.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 下滑页面即可刷新
4 |
5 |
6 |
7 |
8 |
21 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/mp-action-sheet-share-icon-dark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "mp-action-sheet-share-icon-dark@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "mp-action-sheet-share-icon-dark@3x.png",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/create-evoker/template-blank/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "evoker dev",
7 | "build": "evoker build"
8 | },
9 | "dependencies": {
10 | "evoker": "^0.13.0",
11 | "vue": "^3.2.39"
12 | },
13 | "devDependencies": {
14 | "@evoker/cli": "^0.13.0",
15 | "@vitejs/plugin-vue": "^3.1.0",
16 | "typescript": "^4.7.4",
17 | "vite": "^3.1.3",
18 | "vue-tsc": "^0.34.17"
19 | },
20 | "browserslist": [
21 | "iOS >= 11",
22 | "Android >= 7.0"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "lib": ["esnext", "dom"],
14 | "skipLibCheck": true
15 | },
16 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
17 | "references": [{ "path": "./tsconfig.node.json" }]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | step: 10
4 |
5 | show value
6 |
7 | min: 50, max: 200
8 |
9 |
10 |
11 |
12 |
19 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/CaptureScreen.vue:
--------------------------------------------------------------------------------
1 |
2 | 请截屏
3 |
4 | 截屏事件{{ captured ? "已触发" : "未触发" }}
5 |
6 |
7 |
8 |
25 |
--------------------------------------------------------------------------------
/packages/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | import minimist from "minimist"
2 | import { build } from "./build"
3 |
4 | const args = minimist(process.argv.slice(2))
5 |
6 | const command = args._[0]
7 |
8 | const enum Commands {
9 | DEV = "dev",
10 | BUILD = "build",
11 | PACK = "pack"
12 | }
13 |
14 | async function main() {
15 | switch (command) {
16 | case Commands.DEV:
17 | await build()
18 | break
19 | case Commands.BUILD:
20 | await build("production")
21 | break
22 | default:
23 | console.error("command is invalid.")
24 | break
25 | }
26 | }
27 |
28 | main()
29 |
--------------------------------------------------------------------------------
/packages/create-evoker/template-blank/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "lib": ["esnext", "dom"],
14 | "skipLibCheck": true
15 | },
16 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
17 | "references": [{ "path": "./tsconfig.node.json" }]
18 | }
19 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Page/Web/View/NativelyContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NativelyContainerView.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public final class NativelyContainerView: UIView {
13 |
14 | public override func conforms(to aProtocol: Protocol) -> Bool {
15 | if NSStringFromProtocol(aProtocol) == "WKNativelyInteractible" {
16 | return true
17 | }
18 | return super.conforms(to: aProtocol)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/example/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{vue,js,ts,jsx,tsx}"],
3 | darkMode: "media",
4 | theme: {
5 | extend: {
6 | minWidth: {
7 | "1/3": "33.333333%"
8 | },
9 | minHeight: {
10 | 6: "1.5rem",
11 | 10: "2.5rem"
12 | },
13 | maxWidth: {
14 | "1/3": "33.333333%"
15 | }
16 | }
17 | },
18 | variants: {
19 | extend: {
20 | backgroundColor: ["active"],
21 | margin: ["first", "last"],
22 | padding: ["first", "last"],
23 | border: ["last"]
24 | }
25 | },
26 | plugins: []
27 | }
28 |
--------------------------------------------------------------------------------
/packages/service/src/bridge/bridge.ts:
--------------------------------------------------------------------------------
1 | import { invoke, invokeCallbackHandler, publish, subscribe, subscribeHandler } from "@evoker/bridge"
2 |
3 | export const InnerJSBridge = {
4 | invoke,
5 | publish,
6 | subscribe,
7 | invokeCallbackHandler,
8 | subscribeHandler
9 | }
10 |
11 | const JSBridge = {
12 | get invoke() {
13 | return invoke
14 | },
15 | get subscribe() {
16 | return subscribe
17 | },
18 | get invokeCallbackHandler() {
19 | return invokeCallbackHandler
20 | },
21 | get subscribeHandler() {
22 | return subscribeHandler
23 | }
24 | }
25 |
26 | globalThis.JSBridge = JSBridge
27 |
--------------------------------------------------------------------------------
/packages/shared/src/devtools.ts:
--------------------------------------------------------------------------------
1 | export const enum DevtoolsBridgeCommands {
2 | APP_SERVICE_INVOKE = "APP_SERVICE_INVOKE",
3 | APP_SERVICE_PUBLISH = "APP_SERVICE_PUBLISH",
4 | WEB_VIEW_INVOKE = "WEB_VIEW_INVOKE",
5 | WEB_VIEW_PUBLISH = "WEB_VIEW_PUBLISH"
6 | }
7 |
8 | export const enum DevtoolsBridgeExecEvents {
9 | INVOKE_CALLBACK = "INVOKE_CALLBACK",
10 | SUBSCRIBE_HANDLER = "SUBSCRIBE_HANDLER"
11 | }
12 |
13 | export interface InvokeArgs {
14 | event: string
15 | params: string
16 | callbackId: number
17 | }
18 |
19 | export interface PublishArgs {
20 | event: string
21 | params: string
22 | webViewId: number
23 | }
24 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/MakePhoneCall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 请在下方输入电话号码
4 |
11 |
12 |
13 |
14 |
15 |
24 |
--------------------------------------------------------------------------------
/packages/webview/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 | Evoker - preload - webview
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./event"
2 | export { SyncFlags } from "./syncFlags"
3 | export * from "./canvas"
4 | export * from "./devtools"
5 |
6 | export const isNumber = (val: unknown): val is number => typeof val === "number"
7 |
8 | export const isBoolean = (val: unknown): val is number => typeof val === "boolean"
9 |
10 | export const isArrayBuffer = (val: unknown): val is ArrayBuffer =>
11 | Object.prototype.toString.call(val) === "[object ArrayBuffer]"
12 |
13 | export const clamp = (value: number, min: number, max: number) =>
14 | Math.min(Math.max(min, value), max)
15 |
16 | export const isDevtools = __Config.platform === "devtools"
17 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/GetBatteryInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ info.level }}
4 | {{ info.isCharging }}
5 |
6 |
7 |
8 |
9 |
23 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Service/AppInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppInfo.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct AppInfo {
12 |
13 | public var appName: String = ""
14 |
15 | public var appIconURL: String = ""
16 |
17 | public var userInfo: [String: Any] = [:]
18 |
19 | public init(appName: String, appIconURL: String, userInfo: [String: Any] = [:]) {
20 | self.appName = appName
21 | self.appIconURL = appIconURL
22 | self.userInfo = userInfo
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/ActionSheet.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
24 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@evoker/shared",
3 | "version": "0.13.0",
4 | "type": "module",
5 | "description": "evoker shared",
6 | "author": "yizhi996",
7 | "homepage": "https://github.com/yizhi996/evoker/tree/main/packages/shared",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/yizhi996/evoker.git"
11 | },
12 | "license": "MIT",
13 | "main": "dist/shared.js",
14 | "module": "dist/shared.js",
15 | "types": "dist/shared.d.ts",
16 | "buildOptions": {
17 | "formats": [
18 | "es"
19 | ]
20 | },
21 | "sideEffects": false,
22 | "dependencies": {
23 | "@vue/shared": "^3.2.39"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "evoker dev",
7 | "build": "evoker build"
8 | },
9 | "dependencies": {
10 | "@vue/shared": "^3.2.39",
11 | "@evoker/shared": "^0.12.0",
12 | "evoker": "^0.12.0",
13 | "lodash.isequal": "^4.5.0",
14 | "vue": "^3.2.39"
15 | },
16 | "devDependencies": {
17 | "@evoker/cli": "0.12.0",
18 | "@types/lodash.isequal": "^4.5.6",
19 | "@vitejs/plugin-vue": "^3.1.0",
20 | "typescript": "^4.7.4",
21 | "vite": "^3.1.3"
22 | },
23 | "browserslist": [
24 | "iOS >= 11",
25 | "Android >= 7.0"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
24 |
--------------------------------------------------------------------------------
/packages/webview/src/bridge/index.ts:
--------------------------------------------------------------------------------
1 | import JSBridge from "./bridge"
2 | import { vibrateShort, vibrateLong, authorize, showModal } from "@evoker/bridge"
3 | import {
4 | navigateTo,
5 | navigateBack,
6 | navigateToMiniProgram,
7 | switchTab,
8 | redirectTo,
9 | reLaunch,
10 | exit
11 | } from "./api/navigator"
12 | import { pageScrollTo } from "./api/scroll"
13 | import "./fromService"
14 |
15 | export {
16 | vibrateShort,
17 | vibrateLong,
18 | navigateTo,
19 | navigateBack,
20 | navigateToMiniProgram,
21 | switchTab,
22 | redirectTo,
23 | reLaunch,
24 | exit,
25 | pageScrollTo,
26 | authorize,
27 | showModal
28 | }
29 |
30 | export { JSBridge }
31 |
--------------------------------------------------------------------------------
/packages/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "evoker dev",
7 | "build": "evoker build"
8 | },
9 | "dependencies": {
10 | "evoker": "^0.12.0",
11 | "vue": "^3.2.39",
12 | "pinia": "^2.0.14",
13 | "tailwindcss": "^3.0.15"
14 | },
15 | "devDependencies": {
16 | "@evoker/cli": "^0.12.0",
17 | "@vitejs/plugin-vue": "^3.1.0",
18 | "autoprefixer": "^10.4.2",
19 | "postcss": "^8.4.5",
20 | "typescript": "^4.7.4",
21 | "vite": "^3.1.3",
22 | "vue-tsc": "^0.34.17"
23 | },
24 | "browserslist": [
25 | "iOS >= 11",
26 | "Android >= 7.0"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/packages/create-evoker/template-iOS/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '11.0'
2 |
3 | use_frameworks!
4 |
5 | target 'Launcher' do
6 |
7 | pod 'Evoker', '~> 0.13.0'
8 | pod 'Evoker/Map', '~> 0.13.0'
9 |
10 | end
11 |
12 | post_install do |installer|
13 | installer.pods_project.targets.each do |target|
14 | target.build_configurations.each do |config|
15 | config.build_settings['ENABLE_BITCODE'] = 'YES'
16 | end
17 | if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
18 | target.build_configurations.each do |config|
19 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/packages/launcher/iOS/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '11.0'
2 |
3 | use_frameworks!
4 |
5 | target 'Launcher' do
6 |
7 | pod 'Evoker', :path => "../../.."
8 | pod 'Evoker/Map', :path => "../../.."
9 |
10 | end
11 |
12 | post_install do |installer|
13 | installer.pods_project.targets.each do |target|
14 | target.build_configurations.each do |config|
15 | config.build_settings['ENABLE_BITCODE'] = 'YES'
16 | end
17 | if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
18 | target.build_configurations.each do |config|
19 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/OnNetworkStatusChange.vue:
--------------------------------------------------------------------------------
1 |
2 | 网络状态
3 | {{ networkType }}
4 |
5 |
6 |
25 |
--------------------------------------------------------------------------------
/packages/test/src/pages/WebSocket.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
29 |
--------------------------------------------------------------------------------
/packages/service/src/bridge/api/html/canvas/utils.ts:
--------------------------------------------------------------------------------
1 | const template = (id: string, script: string) => `(function() {
2 | const wrapper = document.getElementById("${id}");
3 | if (wrapper) {
4 | const canvas = wrapper.querySelector("canvas");
5 | ${script}
6 | }
7 | })()`
8 |
9 | const exec = (script: string, webViewId: number) =>
10 | globalThis.__NativeSDK.evalWebView(script, webViewId)
11 |
12 | export const execCanvasFunction = (id: string, webViewId: number, func: string) =>
13 | exec(template(id, func), webViewId)
14 |
15 | export const execCanvas2DContextFunction = (id: string, webViewId: number, func: string) =>
16 | exec(template(id, `const ctx = canvas.getContext("2d");${func}`), webViewId)
17 |
--------------------------------------------------------------------------------
/packages/example/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from "evoker"
2 | import { createPinia } from "pinia"
3 | import App from "./App.vue"
4 | import NTopic from "./components/NTopic.vue"
5 | import NCellGroup from "./components/NCellGroup.vue"
6 | import NCell from "./components/NCell.vue"
7 | import NObject from "./components/NObject.vue"
8 |
9 | import "./tailwind.css"
10 |
11 | const app = createApp(App)
12 |
13 | app.config.errorHandler = err => {
14 | console.log(err)
15 | }
16 |
17 | const pinia = createPinia()
18 | app.use(pinia)
19 |
20 | app.component("n-topic", NTopic)
21 | app.component("n-cell", NCell)
22 | app.component("n-cell-group", NCellGroup)
23 | app.component("n-object", NObject)
24 |
25 | app.mount("#app")
26 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Extension/UIScrollView+Tongceng.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+Tongceng.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 |
13 | extension UIScrollView {
14 |
15 | private static var tongcengIdKey = "WK_SCROLL_VIEW_TONGCENG_ID"
16 |
17 | var tongcengId: String? {
18 | get {
19 | objc_getAssociatedObject(self, &UIScrollView.tongcengIdKey) as! String?
20 | } set {
21 | objc_setAssociatedObject(self, &UIScrollView.tongcengIdKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Extension/NSAttributedString+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+Extension.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension NSAttributedString {
13 |
14 | var rangeOfAll: NSRange {
15 | return NSRange(location: 0, length: string.count)
16 | }
17 |
18 | func calcHeight(width: CGFloat) -> CGFloat {
19 | let rect = boundingRect(with: CGSize(width: width, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
20 | return rect.height
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/test/src/pages/Base.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
29 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Bridge/API/CanvasAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CanvasAPI.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 |
11 | enum CanvasAPI: String, CaseIterableAPI {
12 |
13 | case drawCanvas
14 |
15 | func onInvoke(appService: AppService, bridge: JSBridge, args: JSBridge.InvokeArgs) {
16 | switch self {
17 | case .drawCanvas:
18 | drawCanvas(appService: appService, bridge: bridge, args: args)
19 | }
20 | }
21 |
22 | private func drawCanvas(appService: AppService, bridge: JSBridge, args: JSBridge.InvokeArgs) {
23 |
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/packages/create-evoker/template-blank/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 | Hello world
3 |
4 | count: {{ count }}
5 |
6 |
7 |
8 |
14 |
15 |
33 |
--------------------------------------------------------------------------------
/packages/webview/src/bridge/api/scroll.ts:
--------------------------------------------------------------------------------
1 | import JSBridge from "../bridge"
2 |
3 | interface PageScrollToOptions {
4 | scrollTop?: number
5 | duration: number
6 | selector?: string
7 | offsetTop?: number
8 | }
9 |
10 | export function pageScrollTo(options: PageScrollToOptions) {
11 | const { scrollTop, selector, offsetTop, duration } = options
12 | let top = -1
13 | if (selector) {
14 | const el = document.querySelector(selector) as HTMLElement
15 | if (el) {
16 | top = el.offsetTop + el.offsetHeight + (offsetTop || 0)
17 | }
18 | } else if (scrollTop !== undefined) {
19 | top = scrollTop
20 | }
21 | if (top > -1) {
22 | JSBridge.invoke("pageScrollTo", { top, duration })
23 | }
24 | return Promise.resolve({})
25 | }
26 |
--------------------------------------------------------------------------------
/packages/create-evoker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-evoker",
3 | "version": "0.5.0",
4 | "description": "evoker creator",
5 | "author": "yizhi996",
6 | "homepage": "https://github.com/yizhi996/evoker/tree/main/packages/create-evoker",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/yizhi996/evoker.git"
10 | },
11 | "license": "MIT",
12 | "main": "index.js",
13 | "type": "module",
14 | "bin": {
15 | "create-evoker": "index.js"
16 | },
17 | "dependencies": {
18 | "execa": "^6.1.0",
19 | "inquirer": "^9.0.0",
20 | "minimist": "^1.2.6",
21 | "which-pm-runs": "^1.1.0"
22 | },
23 | "files": [
24 | "index.js",
25 | "_gitignore",
26 | "_README.md",
27 | "template-*"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Animation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | TEST
6 |
7 |
8 |
9 |
10 |
24 |
--------------------------------------------------------------------------------
/packages/bridge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@evoker/bridge",
3 | "version": "0.13.0",
4 | "type": "module",
5 | "description": "evoker js bridge",
6 | "author": "yizhi996",
7 | "homepage": "https://github.com/yizhi996/evoker/tree/main/packages/bridge",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/yizhi996/evoker.git"
11 | },
12 | "license": "MIT",
13 | "main": "dist/bridge.js",
14 | "module": "dist/bridge.js",
15 | "types": "dist/bridge.d.ts",
16 | "buildOptions": {
17 | "formats": [
18 | "es"
19 | ]
20 | },
21 | "sideEffects": false,
22 | "files": [
23 | "dist"
24 | ],
25 | "dependencies": {
26 | "@evoker/shared": "0.13.0",
27 | "vue": "^3.2.39",
28 | "@vue/shared": "^3.2.39"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/VModel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Page Component
4 |
5 |
6 |
BuiltIn Component
7 |
8 |
9 |
10 |
11 |
12 |
13 |
25 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import { build } from "vite"
3 | import { resolve } from "path"
4 | import minimist from "minimist"
5 | import { allPakcages, packageDirectory } from "./utils.js"
6 |
7 | const args = minimist(process.argv.slice(2))
8 |
9 | const target = args._[0]
10 |
11 | const targets = allPakcages().filter(p => p !== "create-evoker" && p !== "vue")
12 |
13 | if (!target) {
14 | targets.forEach(buildTarget)
15 | } else {
16 | buildTarget(target)
17 | }
18 |
19 | async function buildTarget(target) {
20 | const pkgDir = packageDirectory(target)
21 | return await build({
22 | configFile: resolve(pkgDir, "vite.config.ts"),
23 | root: pkgDir,
24 | mode: "development",
25 | build: {
26 | minify: false,
27 | watch: {}
28 | }
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Navigator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
30 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Clipboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
30 |
--------------------------------------------------------------------------------
/packages/launcher/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "launcher",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "evoker dev",
7 | "build": "evoker build"
8 | },
9 | "dependencies": {
10 | "@evoker/shared": "^0.12.0",
11 | "@vue/shared": "^3.2.39",
12 | "evoker": "^0.12.0",
13 | "pinia": "^2.0.14",
14 | "tailwindcss": "^3.0.15",
15 | "url-parse": "^1.5.10",
16 | "vue": "^3.2.39"
17 | },
18 | "devDependencies": {
19 | "@evoker/cli": "0.12.0",
20 | "@types/url-parse": "^1.4.8",
21 | "@vitejs/plugin-vue": "^3.1.0",
22 | "autoprefixer": "^10.4.2",
23 | "postcss": "^8.4.5",
24 | "typescript": "^4.7.4",
25 | "vite": "^3.1.3"
26 | },
27 | "browserslist": [
28 | "iOS >= 11",
29 | "Android >= 7.0"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/loading/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent } from "vue"
2 | import { unitToPx } from "../../utils/format"
3 |
4 | const props = {
5 | size: { type: [Number, String], default: 20 },
6 | color: { type: String, default: "#fff" }
7 | }
8 |
9 | export default defineComponent({
10 | name: "ek-loading",
11 | props,
12 | setup(props) {
13 | const size = computed(() => {
14 | return `${unitToPx(props.size)}px`
15 | })
16 |
17 | return () => (
18 |
19 |
22 |
23 | )
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/packages/create-evoker/README.md:
--------------------------------------------------------------------------------
1 | # create-evoker
2 |
3 | 快速创建 Evoker 应用脚手架。
4 |
5 | ### 创建空白脚手架
6 |
7 | ```
8 | // use npm
9 | npm create evoker my-app --template blank --platform iOS
10 |
11 | // use yarn
12 | yarn create evoker my-app --template blank --platform iOS
13 |
14 | // use pnpm
15 | pnpm create evoker my-app --template blank --platform iOS
16 | ```
17 |
18 | ### 其他模板 `--template`
19 |
20 | * example 包含所有内置组件和大部分 API 的示例
21 |
22 | ### 启动应用
23 |
24 | 1. 启动 Node 项目
25 | ```
26 | cd my-app
27 |
28 | // 安装依赖
29 | npm / yarn /pnpm install
30 |
31 | // 启动 dev server
32 | npm / yarn /pnpm run dev
33 | ```
34 |
35 | 2. 启动 iOS
36 | ```
37 | cd my-app/iOS
38 |
39 | // 安装 iOS 依赖
40 | pod install
41 | ```
42 |
43 | 3. 打开 `iOS/${projectName}.xcworkspace` 运行在指定设备或者模拟器
44 |
45 | * 如果要在真机运行,请设置 Bundle Identifier 和 签名证书
--------------------------------------------------------------------------------
/packages/service/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@evoker/service",
3 | "version": "0.13.0",
4 | "type": "module",
5 | "description": "evoker jscore runtime",
6 | "author": "yizhi996",
7 | "homepage": "https://github.com/yizhi996/evoker/tree/main/packages/service",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/yizhi996/evoker.git"
11 | },
12 | "license": "MIT",
13 | "main": "dist/service.js",
14 | "module": "dist/service.js",
15 | "types": "dist/service.d.ts",
16 | "sideEffects": false,
17 | "buildOptions": {
18 | "formats": [
19 | "es"
20 | ]
21 | },
22 | "dependencies": {
23 | "@evoker/bridge": "0.13.0",
24 | "@evoker/shared": "0.13.0",
25 | "vue": "^3.2.39",
26 | "@vue/shared": "^3.2.39"
27 | },
28 | "files": [
29 | "dist"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Resources/Image.xcassets/tab-bar-item-badge.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "filename" : "tab-bar-item-badge@3x.png",
13 | "idiom" : "universal",
14 | "resizing" : {
15 | "cap-insets" : {
16 | "bottom" : 45,
17 | "left" : 44,
18 | "right" : 45,
19 | "top" : 44
20 | },
21 | "center" : {
22 | "height" : 1,
23 | "mode" : "tile",
24 | "width" : 1
25 | },
26 | "mode" : "9-part"
27 | },
28 | "scale" : "3x"
29 | }
30 | ],
31 | "info" : {
32 | "author" : "xcode",
33 | "version" : 1
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Extension/TimeInterval+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeInterval+Extension.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 |
11 | extension TimeInterval {
12 |
13 | func secondsToHoursMinutesSeconds () -> (Int, Int, Int) {
14 | let seconds = Int(self)
15 | return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
16 | }
17 |
18 | func secondsToHoursMinutesSecondsDisplay () -> String {
19 | let (h, m, s) = secondsToHoursMinutesSeconds()
20 | var result = String(format: "%02d:%02d", m, s)
21 | if h > 0 {
22 | result = String(format: "%02d:", h) + result
23 | }
24 | return result
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/iOS/EvokerMap/Sources/MapModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapModule.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 |
11 | public class MapModule: Module {
12 |
13 | public static var name: String {
14 | return "com.evokerdev.module.map"
15 | }
16 |
17 | public static var apis: [String : API] {
18 | var result: [String : API] = [:]
19 | MapAPI.allCases.forEach { result[$0.rawValue] = $0 }
20 | return result
21 | }
22 |
23 | var mapViews: [Int: MapView] = [:]
24 |
25 | public required init(appService: AppService) {
26 |
27 | }
28 |
29 | public func onUnload(_ page: Page) {
30 | mapViews[page.pageId] = nil
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/packages/webview/src/bridge/api/canvas.ts:
--------------------------------------------------------------------------------
1 | import { isEvokerElement, nodes } from "../../dom/element"
2 |
3 | interface ExecCanvasCommandOptions {
4 | nodeId: number
5 | commands: any[]
6 | }
7 |
8 | export function execCanvasCommand(options: ExecCanvasCommandOptions) {
9 | const node = nodes.get(options.nodeId)
10 | if (node && isEvokerElement(node.el)) {
11 | node.el.__instance!.exposed!.exec(options)
12 | }
13 | return Promise.resolve({})
14 | }
15 |
16 | interface OperateCanvasOptions {
17 | nodeId: number
18 | method: string
19 | data: Record
20 | }
21 |
22 | export function operateCanvas(options: OperateCanvasOptions) {
23 | const node = nodes.get(options.nodeId)
24 | if (node && isEvokerElement(node.el)) {
25 | node.el.__instance!.exposed!.operate(options)
26 | }
27 | return Promise.resolve({})
28 | }
29 |
--------------------------------------------------------------------------------
/packages/webview/src/bridge/api/font.ts:
--------------------------------------------------------------------------------
1 | interface LoadFontFaceDesc {
2 | style?: string
3 | weight?: string
4 | variant?: string
5 | }
6 |
7 | interface LoadFontFaceOptions {
8 | family: string
9 | source: string
10 | desc: LoadFontFaceDesc
11 | }
12 |
13 | export function loadFontFace(options: LoadFontFaceOptions) {
14 | return new Promise((resolve, reject) => {
15 | const { family, source, desc } = options
16 | const font = new FontFace(family, source, desc)
17 | font
18 | .load()
19 | .then(
20 | () => {
21 | // @ts-ignore
22 | document.fonts.add(font)
23 | resolve({ status: font.status })
24 | },
25 | () => {
26 | reject(font.status)
27 | }
28 | )
29 | .catch(() => {
30 | reject(font.status)
31 | })
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/video/VideoButton/VideoButton.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, PropType, withDirectives, VNode } from "vue"
2 | import { vTap } from "../../../directive/tap"
3 | import { classNames } from "../../../utils"
4 |
5 | export default defineComponent({
6 | props: {
7 | type: String as PropType<
8 | "play" | "pause" | "mute-on" | "mute-off" | "fullscreen" | "back" | "lock" | "unlock"
9 | >
10 | },
11 | emits: ["click"],
12 | setup(props, { emit }) {
13 | const onClick = () => {
14 | emit("click")
15 | }
16 |
17 | return () => {
18 | return withDirectives(
19 | (
20 |
21 | ) as VNode,
22 | [[vTap, onClick, "", { stop: true }]]
23 | )
24 | }
25 | }
26 | })
27 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/GetSystemInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ systemInfo.brand }}
4 | {{ systemInfo.model }}
5 | {{ systemInfo.language }}
6 | {{ systemInfo.version }}
7 | {{ systemInfo.screenWidth }}
8 | {{ systemInfo.screenHeight }}
9 | {{ systemInfo.pixelRatio }}
10 |
11 |
12 |
13 |
14 |
24 |
--------------------------------------------------------------------------------
/packages/example/src/store.ts:
--------------------------------------------------------------------------------
1 | import { ref } from "vue"
2 | import { defineStore } from "pinia"
3 | import { ramdomString } from "./utils"
4 |
5 | export const useLocalStore = defineStore("local", () => {
6 | const deviceId = ref("")
7 |
8 | const KEY = "k_device_id"
9 |
10 | async function getDeviceId() {
11 | if (deviceId.value === "") {
12 | const init = () => {
13 | deviceId.value = ramdomString(12)
14 | ek.setStorage({ key: KEY, data: deviceId.value })
15 | }
16 | try {
17 | const res = await ek.getStorage({ key: KEY })
18 | if (res.data) {
19 | deviceId.value = res.data
20 | } else {
21 | init()
22 | }
23 | } catch {
24 | init()
25 | }
26 | }
27 | return deviceId.value
28 | }
29 |
30 | return {
31 | getDeviceId
32 | }
33 | })
34 |
--------------------------------------------------------------------------------
/packages/example/src/utils.ts:
--------------------------------------------------------------------------------
1 | export function formatSecond(time: number) {
2 | if (typeof time !== "number" || time < 0) {
3 | return time
4 | }
5 |
6 | const hour = Math.floor(time / 3600)
7 | time %= 3600
8 | const minute = Math.floor(time / 60)
9 | time = Math.floor(time % 60)
10 | const second = time
11 |
12 | return [hour, minute, second]
13 | .map(x => {
14 | const y = x.toString()
15 | return y[1] ? y : "0" + y
16 | })
17 | .join(":")
18 | }
19 |
20 | export function ramdomString(length: number) {
21 | let result = ""
22 | let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
23 | const charactersLength = characters.length
24 | for (let i = 0; i < length; i++) {
25 | result += characters.charAt(Math.floor(Math.random() * charactersLength))
26 | }
27 | return result
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "cSpell.enabledLanguageIds": [
4 | "markdown",
5 | "plaintext",
6 | "text",
7 | "yml"
8 | ],
9 | "[typescript]": {
10 | "editor.defaultFormatter": "esbenp.prettier-vscode"
11 | },
12 | "[javascript]": {
13 | "editor.defaultFormatter": "esbenp.prettier-vscode"
14 | },
15 | "[json]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | },
18 | "[vue]": {
19 | "editor.defaultFormatter": "esbenp.prettier-vscode"
20 | },
21 | "files.exclude": {
22 | "**/.git": true,
23 | "**/.svn": true,
24 | "**/.hg": true,
25 | "**/CVS": true,
26 | "**/.DS_Store": true,
27 | "**/Thumbs.db": true,
28 | "**/node_modules": false
29 | }
30 | }
--------------------------------------------------------------------------------
/packages/devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@evoker/devtools",
3 | "version": "0.13.0",
4 | "description": "evoker devtools",
5 | "author": "yizhi996",
6 | "homepage": "https://github.com/yizhi996/evoker/tree/main/packages/devtools",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/yizhi996/evoker.git"
10 | },
11 | "license": "MIT",
12 | "main": "dist/devtools.js",
13 | "module": "dist/devtools.js",
14 | "types": "dist/devtools.d.ts",
15 | "buildOptions": {
16 | "name": "__Devtools",
17 | "formats": [
18 | "iife"
19 | ]
20 | },
21 | "dependencies": {
22 | "@evoker/bridge": "0.13.0",
23 | "@evoker/shared": "0.13.0",
24 | "@vue/shared": "^3.2.39",
25 | "hammer-touchemulator": "^0.0.2",
26 | "ws": "^8.8.0"
27 | },
28 | "devDependencies": {
29 | "@types/ws": "^8.5.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "lib": [
14 | "esnext",
15 | "dom"
16 | ],
17 | "skipLibCheck": true,
18 | "paths": {
19 | "@evoker/*": [
20 | "../*/src"
21 | ],
22 | "evoker": [
23 | "../evoker/src"
24 | ]
25 | },
26 | },
27 | "include": [
28 | "src/**/*.ts",
29 | "src/**/*.d.ts",
30 | "src/**/*.tsx",
31 | "src/**/*.vue",
32 | "../evoker/global.d.ts"
33 | ],
34 | "references": [
35 | {
36 | "path": "./tsconfig.node.json"
37 | }
38 | ]
39 | }
--------------------------------------------------------------------------------
/packages/example/src/pages/API/IntersectionObserver.vue:
--------------------------------------------------------------------------------
1 |
2 | 小球{{ appear ? "" : "未" }}出现
3 |
4 |
8 |
向下滚动让小球出现
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Toast.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
34 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/movable-area/MovableArea.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, ref, watch } from "vue"
2 | import { useChildren } from "../../composables/useRelation"
3 | import { MOVABLE_KEY } from "./constant"
4 |
5 | export default defineComponent({
6 | name: "ek-movable-area",
7 | setup() {
8 | const container = ref()
9 |
10 | const { children, linkChildren } = useChildren(MOVABLE_KEY)
11 |
12 | linkChildren({})
13 |
14 | watch(
15 | () => [...children],
16 | children => {
17 | if (!container.value) {
18 | return
19 | }
20 | const rect = container.value.getBoundingClientRect()
21 | children.forEach(child => {
22 | child.exposed!.setAreaRect(rect)
23 | })
24 | }
25 | )
26 |
27 | return () =>
28 | }
29 | })
30 |
--------------------------------------------------------------------------------
/packages/service/src/runtime-jscore/modules/class.ts:
--------------------------------------------------------------------------------
1 | import { EvokerElement } from "../../dom/element"
2 |
3 | export interface ElementWithTransition extends EvokerElement {
4 | // _vtc = Vue Transition Classes.
5 | // Store the temporarily-added transition classes on the element
6 | // so that we can avoid overwriting them if the element's class is patched
7 | // during the transition.
8 | _vtc?: Set
9 | }
10 |
11 | export function patchClass(el: EvokerElement, value: string | null, isSVG: boolean) {
12 | const transitionClasses = (el as ElementWithTransition)._vtc
13 | if (transitionClasses) {
14 | value = (value ? [value, ...transitionClasses] : [...transitionClasses]).join(" ")
15 | }
16 | if (value == null) {
17 | el.className = ""
18 | el.removeAttribute("class")
19 | } else {
20 | el.className = value
21 | el.setAttribute("class", value)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/bridge/src/errors.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "@vue/shared"
2 |
3 | const getNameType = (name: string) => (name.includes(".") ? "property" : "argument")
4 |
5 | export const ERR_INVALID_ARG_TYPE = (name: string, types: string | string[], actual: unknown) =>
6 | `The "${name}" ${getNameType(name)} must be of type ${
7 | isString(types) ? types : types.join(" or")
8 | }. Received ${actual}`
9 |
10 | export const ERR_CANNOT_EMPTY = "cannot be empty"
11 |
12 | export const ERR_INVALID = (name: string) => `${name} invalid`
13 |
14 | export const ERR_INVALID_ARG_VALUE = (
15 | name: string,
16 | value: unknown,
17 | reason: string = "is invalid"
18 | ) => `The "${name}" ${getNameType(name)} ${reason}, Received ${value}`
19 |
20 | export const ERR_OUT_OF_RANGE = (name: string, input: unknown, range: string) =>
21 | `The value of "${name}" is out of range. It must be ${range}. Received ${input}`
22 |
--------------------------------------------------------------------------------
/packages/launcher/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "lib": [
14 | "esnext",
15 | "dom"
16 | ],
17 | "skipLibCheck": true,
18 | "paths": {
19 | "@evoker/*": [
20 | "../*/src"
21 | ],
22 | "evoker": [
23 | "../evoker/src"
24 | ]
25 | },
26 | },
27 | "include": [
28 | "src/**/*.ts",
29 | "src/**/*.d.ts",
30 | "src/**/*.tsx",
31 | "src/**/*.vue",
32 | "../evoker/global.d.ts",
33 | "../bridge/global.d.ts"
34 | ],
35 | "references": [
36 | {
37 | "path": "./tsconfig.node.json"
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/packages/webview/src/element/components/video/VideoScreenBrightness/VideoScreenBrightness.tsx:
--------------------------------------------------------------------------------
1 | import { clamp } from "@evoker/shared"
2 | import { computed, defineComponent } from "vue"
3 |
4 | export default defineComponent({
5 | props: {
6 | value: { type: Number, default: 0 }
7 | },
8 | setup(props) {
9 | const count = computed(() => {
10 | return clamp(Math.floor(props.value * 100 * 0.15), 0, 15)
11 | })
12 | return () => {
13 | const indicator = [...Array(count.value).keys()].map(i => (
14 |
15 | ))
16 | return (
17 |
18 |
亮度
19 |
20 |
{indicator}
21 |
22 | )
23 | }
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/packages/bridge/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from "@vue/shared"
2 |
3 | export function combineOptions, U = Record>(
4 | options: T,
5 | preset: U
6 | ): T & U {
7 | const res: Record = preset
8 | for (const key in options) {
9 | const value = options[key]
10 | if (value != null) {
11 | res[key] = isPlainObject(value)
12 | ? combineOptions(value, (preset as Record)[key])
13 | : value
14 | } else {
15 | res[key] = (preset as Record)[key]
16 | }
17 | }
18 | return res as T & U
19 | }
20 |
21 | export function fetchArrayBuffer(data: any, key: string) {
22 | const { __arrayBuffer__ } = data
23 | if (__arrayBuffer__) {
24 | delete data.__arrayBuffer__
25 |
26 | const arrayBuffer = globalThis.__ArrayBufferRegister.get(__arrayBuffer__)
27 | data[key] = arrayBuffer
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/loading/Loading.less:
--------------------------------------------------------------------------------
1 | .ek-loading {
2 | display: inline-block;
3 | position: relative;
4 | animation: rotate 2s linear infinite;
5 |
6 | &__circle {
7 | display: block;
8 | width: 100%;
9 | height: 100%;
10 |
11 | > circle {
12 | stroke: currentColor;
13 | stroke-width: 3;
14 | stroke-linecap: round;
15 | animation: circular 1.5s ease-in-out infinite;
16 | }
17 | }
18 | }
19 |
20 | @keyframes rotate {
21 | 0% {
22 | transform: rotate(0);
23 | }
24 |
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @keyframes circular {
31 | 0% {
32 | stroke-dasharray: 1, 200;
33 | stroke-dashoffset: 0;
34 | }
35 |
36 | 50% {
37 | stroke-dasharray: 90, 150;
38 | stroke-dashoffset: -40;
39 | }
40 |
41 | to {
42 | stroke-dasharray: 90, 150;
43 | stroke-dashoffset: -120;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/view/View.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, PropType, ref } from "vue"
2 | import { useHover } from "../../composables/useHover"
3 | import { useCSSAnimation, AnimationAction } from "../../composables/useCSSAnimation"
4 |
5 | const props = {
6 | hoverClass: { type: String, default: "none" },
7 | hoverStopPropagation: { type: Boolean, default: false },
8 | hoverStartTime: { type: Number, default: 50 },
9 | hoverStayTime: { type: Number, default: 400 },
10 | animation: { type: Array as PropType, default: () => [] }
11 | }
12 |
13 | export default defineComponent({
14 | name: "ek-view",
15 | props,
16 | setup(props) {
17 | const el = ref()
18 |
19 | const { finalHoverClass } = useHover(el, props)
20 |
21 | useCSSAnimation(el, props)
22 |
23 | return () =>
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/ScreenBrightness.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
当前屏幕亮度
4 |
{{ brightness }}
5 |
设置屏幕亮度
6 |
14 |
15 |
16 |
17 |
35 |
--------------------------------------------------------------------------------
/packages/bridge/src/pipeline.ts:
--------------------------------------------------------------------------------
1 | import { publish, subscribe } from "./bridge"
2 | import { queuePostFlushCb } from "vue"
3 |
4 | const Method = "vdSync"
5 |
6 | const queues = new Map()
7 |
8 | const jobs = new Map()
9 |
10 | function flush(pageId: number) {
11 | const messages = queues.get(pageId)
12 | publish(Method, messages, pageId)
13 | queues.delete(pageId)
14 | jobs.delete(pageId)
15 | }
16 |
17 | export function sync(message: any, pageId: number) {
18 | const messages = queues.get(pageId)
19 | if (messages) {
20 | messages.push(message)
21 | } else {
22 | queues.set(pageId, [message])
23 | }
24 |
25 | let job = jobs.get(pageId)
26 | if (!job) {
27 | job = flush.bind(null, pageId)
28 | jobs.set(pageId, job)
29 | }
30 | queuePostFlushCb(job)
31 | }
32 |
33 | export function onSync(callback: (messages: any[]) => void) {
34 | subscribe(Method, callback)
35 | }
36 |
--------------------------------------------------------------------------------
/packages/launcher/src/components/NavigationBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | {{ title }}
11 |
12 |
13 |
16 |
17 |
18 |
29 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/LoadFontFace.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ loaded ? `${fontFamily} is loaded` : `Load ${fontFamily}` }}
4 |
5 |
6 |
7 |
8 |
9 |
28 |
29 |
34 |
--------------------------------------------------------------------------------
/packages/webview/src/dom/vnode.ts:
--------------------------------------------------------------------------------
1 | import { VNode } from "vue"
2 |
3 | export interface EvokerEventListenerOptions {
4 | options?: EventListenerOptions
5 | modifiers?: string[]
6 | }
7 |
8 | export interface EvokerVNode {
9 | nodeId: number
10 | tagName: string
11 | className: string
12 | id: string
13 | attributes: Record
14 | listeners: Record
15 | textContent: string
16 | style: string
17 | data: string
18 | isSVG: boolean
19 | vnode?: VNode
20 | el?: any
21 | props?: Record
22 | }
23 |
24 | export function restoreNode(data: any[]) {
25 | const node: EvokerVNode = {
26 | nodeId: data[0],
27 | tagName: data[1],
28 | className: data[2],
29 | id: data[3],
30 | attributes: data[4],
31 | listeners: data[5],
32 | textContent: data[6],
33 | style: data[7],
34 | data: data[8],
35 | isSVG: data[9]
36 | }
37 | return node
38 | }
39 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/input-html/InputHTML.less:
--------------------------------------------------------------------------------
1 | ek-input div {
2 | position: relative;
3 | height: inherit;
4 | margin: 0;
5 | padding: 0;
6 | min-height: 100%;
7 | }
8 |
9 | .ek-input {
10 | &__html {
11 | background: transparent;
12 | border: none;
13 | display: inherit;
14 | position: relative;
15 | width: 100%;
16 | height: inherit;
17 | min-height: 100%;
18 | margin: 0;
19 | padding: 0;
20 | outline: none;
21 | overflow: inherit;
22 | text-overflow: inherit;
23 | vertical-align: middle;
24 | white-space: inherit;
25 | z-index: 2;
26 |
27 | &__placeholder {
28 | position: absolute;
29 | left: 0;
30 | top: 0;
31 | width: 100%;
32 | height: inherit;
33 | min-height: 100%;
34 | line-height: 100%;
35 | overflow: hidden;
36 | vertical-align: middle;
37 | white-space: pre;
38 | z-index: 1;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Progress.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
37 |
--------------------------------------------------------------------------------
/packages/evoker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "evoker",
3 | "version": "0.13.0",
4 | "type": "module",
5 | "description": "The Vue mini program engine",
6 | "author": "yizhi996",
7 | "homepage": "https://github.com/yizhi996/evoker",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/yizhi996/evoker.git"
11 | },
12 | "license": "MIT",
13 | "main": "dist/evoker.js",
14 | "module": "dist/evoker.js",
15 | "types": "dist/global.d.ts",
16 | "sideEffects": false,
17 | "keywords": [
18 | "evoker",
19 | "vue",
20 | "mini-program",
21 | "mini-app"
22 | ],
23 | "buildOptions": {
24 | "name": "Evoker",
25 | "formats": [
26 | "iife",
27 | "es"
28 | ]
29 | },
30 | "dependencies": {
31 | "vue": "^3.2.39",
32 | "@evoker/service": "0.13.0",
33 | "@evoker/webview": "0.13.0"
34 | },
35 | "devDependencies": {
36 | "vite": "^3.1.3"
37 | },
38 | "files": [
39 | "dist"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/packages/launcher/iOS/Launcher/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Launcher
4 | //
5 |
6 | import UIKit
7 | @main
8 |
9 | class AppDelegate: UIResponder, UIApplicationDelegate {
10 |
11 | var window: UIWindow?
12 |
13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
14 |
15 | window = UIWindow(frame: UIScreen.main.bounds)
16 | window!.makeKeyAndVisible()
17 |
18 | window!.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
19 |
20 | DispatchQueue.main.async {
21 | Launcher.shared.setupEvoker()
22 | }
23 |
24 | return true
25 | }
26 |
27 | func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
28 | return .all
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Textarea.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | isFocus: {{ focus }}
4 |
12 |
13 | Auto Height
14 |
15 |
16 | Custom Style
17 |
22 |
23 |
24 |
25 |
38 |
--------------------------------------------------------------------------------
/packages/create-evoker/template-blank/src/assets/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/create-evoker/template-iOS/Launcher/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Launcher
4 | //
5 |
6 | import UIKit
7 | @main
8 |
9 | class AppDelegate: UIResponder, UIApplicationDelegate {
10 |
11 | var window: UIWindow?
12 |
13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
14 |
15 | window = UIWindow(frame: UIScreen.main.bounds)
16 | window!.makeKeyAndVisible()
17 |
18 | window!.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
19 |
20 | DispatchQueue.main.async {
21 | Launcher.shared.setupEvoker()
22 | }
23 |
24 | return true
25 | }
26 |
27 | func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
28 | return .all
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/DownloadFile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
下载远程图片
5 |
6 |
Progress: {{ progress }}
7 |
8 |
9 |
10 |
36 |
--------------------------------------------------------------------------------
/packages/cli/src/plugins/app.ts:
--------------------------------------------------------------------------------
1 | import type { Plugin, ResolvedConfig } from "vite"
2 | import fs from "fs"
3 | import { resolve } from "path"
4 |
5 | let outputAppConfig: {
6 | [x: string]: any
7 | } = {}
8 |
9 | export { outputAppConfig }
10 |
11 | export function resetOutputAppConfig() {
12 | outputAppConfig = {}
13 | }
14 |
15 | export default function vitePluginEvokerConfig(): Plugin {
16 | let config: ResolvedConfig
17 |
18 | return {
19 | name: "vite:evoker-app",
20 |
21 | enforce: "pre",
22 |
23 | configResolved: _config => {
24 | config = _config
25 | if (!config.build.lib || !config.build.lib.entry) {
26 | throw new Error("lib entry cannot be empty.")
27 | }
28 | },
29 |
30 | transform() {
31 | this.addWatchFile(resolve("src/app.json"))
32 | },
33 |
34 | writeBundle() {
35 | const cfg = JSON.stringify(outputAppConfig)
36 | fs.writeFileSync(resolve(config.build.outDir, "app.json"), cfg, "utf-8")
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Switch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 选中
4 |
5 | 未选中
6 |
7 | 禁用
8 |
9 | 自定义颜色
10 |
11 | checkbox
12 | yes / no
13 |
14 |
15 |
16 |
17 |
34 |
--------------------------------------------------------------------------------
/packages/test/src/utils.ts:
--------------------------------------------------------------------------------
1 | interface Callback {
2 | callback: Function
3 | once: boolean
4 | }
5 |
6 | const events = new Map()
7 |
8 | export function on(name: string, callback: Function) {
9 | const list = events.get(name) || []
10 | list.push({ callback, once: false })
11 | events.set(name, list)
12 | }
13 |
14 | export function once(name: string, callback: Function) {
15 | const list = events.get(name) || []
16 | list.push({ callback, once: true })
17 | events.set(name, list)
18 | }
19 |
20 | export function off(name: string, callback: Function) {
21 | const list = events.get(name) || []
22 | const i = list.findIndex(c => c.callback === callback)
23 | if (i > -1) {
24 | list.splice(i, 1)
25 | }
26 | events.set(name, list)
27 | }
28 |
29 | export function dispatch(name: string, data: any) {
30 | const list = events.get(name) || []
31 |
32 | list.forEach(c => {
33 | c.callback(data)
34 | if (c.once) {
35 | off(name, c.callback)
36 | }
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Extension/UIColor+Theme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Extension.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIColor {
13 |
14 | class var evWhite: UIColor {
15 | return UIColor.color(.white, dark: .black)
16 | }
17 |
18 | class var evBlack: UIColor {
19 | return UIColor.color(.black, dark: .white)
20 | }
21 |
22 | class var evTextBlack: UIColor {
23 | return UIColor.color("#1d1d1f".hexColor(), dark: "#f5f5f7".hexColor())
24 | }
25 |
26 | class func color(_ light: UIColor, dark: UIColor) -> UIColor {
27 | if #available(iOS 13.0, *) {
28 | return UIColor { (traits) -> UIColor in
29 | return traits.userInterfaceStyle == .dark ? dark : light
30 | }
31 | } else {
32 | return light
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Image.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Local Image
4 |
5 |
WebP Internet Image
6 |

12 |
13 | Lazy Load Internet Image
14 |
15 |
16 |
17 |
18 |
19 |
33 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/Icon/Icon.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent, PropType } from "vue"
2 | import { unitToPx } from "../../utils/format"
3 |
4 | type IconType =
5 | | "success"
6 | | "success-no-circle"
7 | | "info"
8 | | "warn"
9 | | "waiting"
10 | | "cancel"
11 | | "download"
12 | | "search"
13 | | "clear"
14 | | "circle"
15 | | "info-circle"
16 |
17 | const props = {
18 | type: { type: String as PropType },
19 | size: { type: Number, default: 23 },
20 | color: { type: String, required: false }
21 | }
22 |
23 | export default defineComponent({
24 | name: "ek-icon",
25 | props,
26 | setup(props) {
27 | const iconSize = computed(() => {
28 | return unitToPx(props.size) + "px"
29 | })
30 |
31 | return () => (
32 |
33 |
37 |
38 | )
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Radio.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ checked }}
4 |
5 |
6 |
7 | {{
8 | fruit.name
9 | }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "dist",
5 | "sourceMap": false,
6 | "target": "es2016",
7 | "useDefineForClassFields": false,
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "allowJs": false,
11 | "strict": true,
12 | "noUnusedLocals": true,
13 | "experimentalDecorators": true,
14 | "resolveJsonModule": true,
15 | "esModuleInterop": true,
16 | "removeComments": false,
17 | "noImplicitAny": false,
18 | "jsx": "preserve",
19 | "lib": [
20 | "esnext",
21 | "dom"
22 | ],
23 | "types": [
24 | "node"
25 | ],
26 | "rootDir": ".",
27 | "paths": {
28 | "@evoker/*": [
29 | "packages/*/src"
30 | ],
31 | "evoker": [
32 | "packages/evoker/src"
33 | ]
34 | },
35 | },
36 | "include": [
37 | "packages/evoker/global.d.ts",
38 | "packages/bridge/global.d.ts",
39 | "packages/webview/global.d.ts",
40 | "packages/*/src",
41 | "packages/*/__tests__",
42 | ],
43 | }
--------------------------------------------------------------------------------
/packages/example/src/pages/API/GetLocation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ location.latitude }}, {{ location.longitude }}
4 |
5 |
6 |
7 |
8 |
38 |
--------------------------------------------------------------------------------
/packages/service/src/dom/element.ts:
--------------------------------------------------------------------------------
1 | import { EvokerPage } from "./page"
2 | import { EvokerNode } from "./node"
3 |
4 | export class EvokerElement extends EvokerNode {
5 | id = ""
6 |
7 | readonly tagName: string
8 |
9 | className = ""
10 |
11 | attributes: Record = Object.create(null)
12 |
13 | constructor(tagName: string, page: EvokerPage) {
14 | super(page)
15 | this.tagName = tagName
16 | }
17 |
18 | setAttribute(name: string, value: any) {
19 | this.attributes[name] = value
20 | this.page.onPatchProp(this, name, value)
21 | }
22 |
23 | removeAttribute(name: string): void {
24 | delete this.attributes[name]
25 | this.page.onPatchProp(this, name)
26 | }
27 |
28 | setAttributeNS(namespace: string, name: string, value: any): void {
29 | this.attributes[namespace + name] = value
30 | this.page.onPatchProp(this, name, value)
31 | }
32 |
33 | removeAttributeNS(namespace: string, name: string): void {
34 | delete this.attributes[namespace + name]
35 | this.page.onPatchProp(this, name)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/service/src/runtime-jscore/modules/attrs.ts:
--------------------------------------------------------------------------------
1 | import { ComponentInternalInstance } from "vue"
2 | import { includeBooleanAttr, isSpecialBooleanAttr } from "@vue/shared"
3 | import { EvokerElement } from "../../dom/element"
4 |
5 | export const xlinkNS = "http://www.w3.org/1999/xlink"
6 |
7 | export function patchAttr(
8 | el: EvokerElement,
9 | key: string,
10 | value: any,
11 | isSVG: boolean,
12 | instance?: ComponentInternalInstance | null
13 | ) {
14 | if (isSVG && key.startsWith("xlink:")) {
15 | if (value == null) {
16 | el.removeAttributeNS(xlinkNS, key.slice(6, key.length))
17 | } else {
18 | el.setAttributeNS(xlinkNS, key, value)
19 | }
20 | } else {
21 | // note we are only checking boolean attributes that don't have a
22 | // corresponding dom prop of the same name here.
23 | const isBoolean = isSpecialBooleanAttr(key)
24 | if (value == null || (isBoolean && !includeBooleanAttr(value))) {
25 | el.removeAttribute(key)
26 | } else {
27 | el.setAttribute(key, isBoolean ? "" : value)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/webview/src/bridge/api/navigator.ts:
--------------------------------------------------------------------------------
1 | import { invokeAppServiceMethod } from "../fromService"
2 |
3 | export function navigateTo(url?: string) {
4 | url && invokeAppServiceMethod("navigateTo", { url, relativePath: window.route })
5 | }
6 |
7 | export function redirectTo(url?: string) {
8 | url && invokeAppServiceMethod("redirectTo", { url, relativePath: window.route })
9 | }
10 |
11 | export function switchTab(url?: string) {
12 | url && invokeAppServiceMethod("switchTab", { url, relativePath: window.route })
13 | }
14 |
15 | export function reLaunch(url?: string) {
16 | url && invokeAppServiceMethod("reLaunch", { url, relativePath: window.route })
17 | }
18 |
19 | export function navigateBack(delta: number = 1) {
20 | invokeAppServiceMethod("navigateBack", { delta })
21 | }
22 |
23 | export function exit() {
24 | invokeAppServiceMethod("exit", {})
25 | }
26 |
27 | export function navigateToMiniProgram(options: { appId: string; path?: string }) {
28 | invokeAppServiceMethod("navigateToMiniProgram", {
29 | appId: options.appId,
30 | path: options.path
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/video/VideoProgress/VideoProgress.less:
--------------------------------------------------------------------------------
1 | .ek-video {
2 | &__progress {
3 | flex-grow: 1;
4 | position: relative;
5 | background-color: hsla(0, 0%, 100%, 0.2);
6 | height: 2px;
7 |
8 | &__time {
9 | color: #fff;
10 | font-size: 11px;
11 | height: 24px;
12 | line-height: 24px;
13 | width: 5ch;
14 | }
15 |
16 | &__buffer {
17 | position: absolute;
18 | background-color: hsla(0, 0%, 100%, 0.5);
19 | width: 0;
20 | height: 100%;
21 | top: 0;
22 | transition: width 0.05s ease;
23 | }
24 |
25 | &__played {
26 | position: absolute;
27 | background-color: #fff;
28 | width: 0;
29 | height: 100%;
30 | top: 0;
31 | }
32 |
33 | &__handle {
34 | position: absolute;
35 | margin-left: -14px;
36 | top: -14px;
37 | left: 0%;
38 | padding: 8px;
39 | }
40 |
41 | &__ball {
42 | background-color: #fff;
43 | border-radius: 50%;
44 | width: 12px;
45 | height: 12px;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Page/Browser/BrowserPage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BrowserPage.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | open class BrowserPage: Page {
13 |
14 | public let pageId: Int
15 |
16 | public let url: String
17 |
18 | public weak var appService: AppService?
19 |
20 | public weak var viewController: PageViewController?
21 |
22 | public var style: AppConfig.Style?
23 |
24 | public var isTabBarPage = false
25 |
26 | public var isShowTabBar = true
27 |
28 | public var tabIndex: UInt8 = 0
29 |
30 | public var isVisibled: Bool = false
31 |
32 | public required init(appService: AppService, url: String) {
33 | self.appService = appService
34 | self.url = url
35 | pageId = appService.genPageId()
36 | }
37 |
38 | open func generateViewController() -> PageViewController {
39 | return BrowserPageViewController(page: self)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 yizhi996
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/service/src/index.ts:
--------------------------------------------------------------------------------
1 | import global from "./bridge"
2 | import useApp from "./lifecycle/useApp"
3 | import usePage from "./lifecycle/usePage"
4 | import "./native"
5 | import Vue from "vue"
6 | import { extend } from "@vue/shared"
7 |
8 | export { useApp, usePage }
9 | export { createApp, getApp, getCurrentPages } from "./app"
10 | export { defineRouter } from "./router"
11 | export { global }
12 | export * from "./bridge"
13 | export type { SuccessResult, GeneralCallbackResult, AsyncReturn } from "@evoker/bridge"
14 | export { wrapperAsyncAPI, invokeFailure, invokeSuccess, invokeCallback } from "@evoker/bridge"
15 |
16 | function hijack() {
17 | return {}
18 | }
19 |
20 | hijack.prototype = Function.prototype
21 | Function.prototype.constructor = hijack as FunctionConstructor
22 | ;(Function as any) = hijack
23 |
24 | const { withModifiers } = Vue
25 | extend(Vue, {
26 | withModifiers: (fn: Function, modifiers: string[]) => {
27 | const wrapper = withModifiers(fn, modifiers) as ReturnType & {
28 | modifiers?: string[]
29 | }
30 | wrapper.modifiers = modifiers
31 | return wrapper
32 | }
33 | })
34 |
--------------------------------------------------------------------------------
/packages/example/src/components/NObject.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ item.key }}:
7 | {{ item.value }}
8 |
9 |
10 |
11 |
{{ placeholder }}
12 |
13 |
14 |
15 |
39 |
--------------------------------------------------------------------------------
/packages/shared/src/event.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from "@vue/shared"
2 | import { isNumber } from "./index"
3 |
4 | type Callback = (res: T) => void
5 |
6 | interface Event {
7 | id: number
8 | callback: Callback
9 | }
10 |
11 | let index = 0
12 |
13 | const events: Record[]> = {}
14 |
15 | export function addEvent(type: string, callback: Callback) {
16 | const id = ++index
17 | events[type] === undefined && (events[type] = [])
18 | events[type].push({ id, callback })
19 | return id
20 | }
21 |
22 | export function removeEvent(type: string, callback: number | Callback) {
23 | if (events[type]) {
24 | let idx = -1
25 | if (isNumber(callback)) {
26 | idx = events[type].findIndex(ev => ev.id === callback)
27 | } else if (isFunction(callback)) {
28 | idx = events[type].findIndex(ev => ev.callback === callback)
29 | }
30 | idx > -1 && events[type].splice(idx, 1)
31 | }
32 | }
33 |
34 | export function dispatchEvent(type: string, data?: any) {
35 | if (events[type]) {
36 | events[type].forEach(ev => {
37 | ev.callback(data)
38 | })
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/test/src/pages/System.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
47 |
--------------------------------------------------------------------------------
/packages/service/src/runtime-jscore/modules/props.ts:
--------------------------------------------------------------------------------
1 | // __UNSAFE__
2 | // Reason: potentially setting innerHTML.
3 | // This can come from explicit usage of v-html or innerHTML as a prop in render
4 |
5 | import { warn } from "vue"
6 |
7 | // functions. The user is responsible for using them with only trusted content.
8 | export function patchDOMProp(
9 | el: any,
10 | key: string,
11 | value: any,
12 | // the following args are passed only due to potential innerHTML/textContent
13 | // overriding existing VNodes, in which case the old tree must be properly
14 | // unmounted.
15 | prevChildren: any,
16 | parentComponent: any,
17 | parentSuspense: any,
18 | unmountChildren: any
19 | ) {
20 | if (key === "textContent") {
21 | if (prevChildren) {
22 | unmountChildren(prevChildren, parentComponent, parentSuspense)
23 | }
24 | el[key] = value == null ? "" : value
25 | return
26 | }
27 |
28 | if (key === "id") {
29 | el.id = value
30 | el.page.onPatchProp(el, key, value)
31 | } else {
32 | warn(
33 | `Failed setting prop "${key}" on <${el.tagName.toLowerCase()}>: ` +
34 | `value ${value} is invalid.`
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ checked }}
4 |
5 |
6 |
7 | {{
8 | fruit.name
9 | }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
41 |
--------------------------------------------------------------------------------
/packages/webview/src/element/components/slider/Slider.less:
--------------------------------------------------------------------------------
1 | ek-slider {
2 | display: block;
3 | margin: 10px 18px;
4 | padding: 0;
5 | }
6 |
7 | .ek-slider {
8 | &__wrapper {
9 | display: flex;
10 | min-height: 16px;
11 | align-items: center;
12 | }
13 |
14 | &__input {
15 | flex: 1;
16 | padding: 8px 0;
17 |
18 | &__bar {
19 | z-index: 0;
20 | -webkit-tap-highlight-color: transparent;
21 | border-radius: 5px;
22 | height: 2px;
23 | position: relative;
24 | transition: background-color 0.3s ease;
25 | }
26 |
27 | &__thumb {
28 | z-index: 2;
29 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
30 | }
31 |
32 | &__handle {
33 | z-index: 3;
34 | background-color: transparent;
35 | }
36 |
37 | &__handle,
38 | &__thumb {
39 | border-radius: 50%;
40 | position: absolute;
41 | top: 50%;
42 | }
43 |
44 | &__track {
45 | border-radius: 6px;
46 | height: 100%;
47 | transition: background-color 0.3s ease;
48 | }
49 | }
50 |
51 | &__value {
52 | color: #888;
53 | font-size: 14px;
54 | margin-left: 1em;
55 | text-align: center;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/webview/src/element/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { isArray, isObject, isString } from "@vue/shared"
2 |
3 | export function getRandomInt(min: number, max: number) {
4 | min = Math.ceil(min)
5 | max = Math.floor(max)
6 | return Math.floor(Math.random() * (max - min) + min)
7 | }
8 |
9 | export function debounce(callback: () => void, delay: number) {
10 | let timer: ReturnType
11 | return function () {
12 | if (timer) {
13 | clearTimeout(timer)
14 | }
15 | timer = setTimeout(callback, delay)
16 | }
17 | }
18 |
19 | export const enum AuthorizationStatus {
20 | authorized = 0,
21 | denied,
22 | notDetermined
23 | }
24 |
25 | export function classNames(...args: unknown[]) {
26 | const classes: string[] = []
27 |
28 | args.forEach(cls => {
29 | if (cls) {
30 | if (isString(cls)) {
31 | classes.push(cls)
32 | } else if (isArray(cls)) {
33 | const clss = classNames(cls)
34 | clss && classes.push(clss)
35 | } else if (isObject(cls)) {
36 | for (const [k, v] of Object.entries(cls)) {
37 | v && classes.push(k)
38 | }
39 | }
40 | }
41 | })
42 |
43 | return classes.join(" ")
44 | }
45 |
--------------------------------------------------------------------------------
/packages/example/src/pages/Component/Text.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ text }}
4 |
5 |
6 |
7 |
8 |
9 |
38 |
--------------------------------------------------------------------------------
/packages/test/src/global.d.ts:
--------------------------------------------------------------------------------
1 | interface CGRect {
2 | x: number
3 | y: number
4 | width: number
5 | height: number
6 | }
7 |
8 | interface UITextView {
9 | text: string
10 | }
11 |
12 | interface UIView {
13 | id: string
14 | rect: CGRect
15 | backgroundColor: string
16 | }
17 |
18 | interface UIFont {
19 | size: number
20 | weight: string
21 | }
22 |
23 | interface UILabel extends UIView {
24 | text: string
25 | textColor: string
26 | font: UIFont
27 | }
28 |
29 | interface UIButton extends UIView {
30 | title: string
31 | titleColor: string
32 | click(): void
33 | }
34 |
35 | interface UITextView extends UIView {}
36 |
37 | interface UITextField extends UIView {}
38 |
39 | interface TestUtils {
40 | containText(text: string): boolean
41 |
42 | containImage(name: string): boolean
43 |
44 | findFirstResponderInput(): UITextView | undefined
45 |
46 | findUIViewWithClass(className: string): UIView | undefined
47 |
48 | findUIButtonWithTitle(title: string): UIButton | undefined
49 |
50 | findUILabelWithText(text: string): UILabel | undefined
51 |
52 | clickTableViewCellWithTitle(title: string): void
53 | }
54 |
55 | declare global {
56 | var __TestUtils: TestUtils
57 | }
58 |
59 | export {}
60 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Setting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
38 |
--------------------------------------------------------------------------------
/iOS/Evoker/Sources/Bridge/API/Device/BatteryAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BatteryAPI.swift
3 | //
4 | // Copyright (c) Evoker. All rights reserved. (https://evokerdev.com)
5 | //
6 | // This source code is licensed under The MIT license.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | enum BatteryAPI: String, CaseIterableAPI {
13 |
14 | case getBatteryInfo
15 |
16 | func onInvoke(appService: AppService, bridge: JSBridge, args: JSBridge.InvokeArgs) {
17 | DispatchQueue.main.async {
18 | switch self {
19 | case .getBatteryInfo:
20 | getBatteryInfo(appService: appService, bridge: bridge, args: args)
21 | }
22 | }
23 | }
24 |
25 | private func getBatteryInfo(appService: AppService, bridge: JSBridge, args: JSBridge.InvokeArgs) {
26 | UIDevice.current.isBatteryMonitoringEnabled = true
27 | let level = Int(UIDevice.current.batteryLevel * 100)
28 | let isCharging = UIDevice.current.batteryState == .charging
29 | UIDevice.current.isBatteryMonitoringEnabled = false
30 | let result: [String: Any] = ["level": level, "isCharging": isCharging]
31 | bridge.invokeCallbackSuccess(args: args, result: result)
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/GetUserInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | getUserProfile 和 getUserInfo 接口需要根据自生业务在 native 端自行实现。具体请查看 EngineConfig.hooks
4 | 的 openAPI。
5 |
6 |
7 |
![]()
8 |
{{ userInfo.nickname }}
9 |
10 | getUserProfile
11 |
12 |
13 | getUserInfo
14 |
15 |
16 |
17 |
38 |
--------------------------------------------------------------------------------
/packages/example/src/pages/API/Request.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Response Header
6 |
7 | Response Data
8 |
9 |
10 |
11 |
12 |
40 |
--------------------------------------------------------------------------------