├── .browserslistrc
├── .eslintrc.js
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── DOCUMENT.md
├── README-zh.md
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── assets
│ ├── BuyMeCoffeeQRCode.png
│ ├── Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg
│ ├── Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.svg
│ ├── Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917.svg
│ ├── Download_on_the_Mac_App_Store_Badge_US-UK_RGB_wht_092917.svg
│ ├── Vimkey-macOS-store-f.png
│ ├── Vimkey-macOS-store-grid.png
│ ├── Vimkey-macOS-store-search&open.png
│ ├── Vimkey-macOS-store-setting.jpg
│ ├── background-dark.jpg
│ ├── chrome-icon.svg
│ ├── chrome-web-store.png
│ ├── chrome-webstore.svg
│ ├── features-accessibility.png
│ ├── features-privacy-policy.png
│ ├── features-smoothscroll.png
│ ├── guy-working-at-home-2127164-0.svg
│ ├── logo-256.png
│ ├── logo.png
│ ├── toolbar-icon-32.png
│ ├── vimkey-f.png
│ ├── vimkey-setting.png
│ └── website-home.png
├── index.html
└── robots.txt
├── src
├── App.vue
├── log.ts
├── main.ts
├── refs.ts
├── registerServiceWorker.ts
├── router.ts
├── shadertoy-transform.ts
├── shims-vue.d.ts
└── views
│ ├── Home.vue
│ ├── Privacy.vue
│ ├── Support.vue
│ ├── shader.frag
│ └── shader.vert
├── tailwind.config.js
├── tsconfig.json
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/typescript/recommended'
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020
13 | },
14 | globals: {},
15 | rules: {
16 | 'semi': [
17 | 'error',
18 | 'never'
19 | ],
20 | 'quotes': [
21 | 'error',
22 | 'single',
23 | {
24 | 'allowTemplateLiterals': true
25 | }
26 | ],
27 | 'eqeqeq': 'error',
28 | 'no-else-return': 'error',
29 | 'object-curly-spacing': [
30 | 'error',
31 | 'always'
32 | ],
33 | 'space-infix-ops': 'error',
34 | 'no-duplicate-imports': 'error',
35 | 'no-var': 'error',
36 | 'prefer-const': 'warn',
37 | 'spaced-comment': [
38 | 'error',
39 | 'always'
40 | ],
41 | 'indent': [
42 | 'error',
43 | 4,
44 | {
45 | 'VariableDeclarator': 'first',
46 | 'SwitchCase': 1
47 | }
48 | ]
49 | },
50 | overrides: [
51 | {
52 | 'files': [
53 | '**/__tests__/*.{j,t}s?(x)',
54 | '**/tests/unit/**/*.spec.{j,t}s?(x)'
55 | ],
56 | 'env': {
57 | 'jest': true
58 | }
59 | }
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build Vimkey website
2 | on:
3 | push:
4 | branches: [ docs ]
5 | pull_request:
6 | types: [ closed ]
7 | branches: [ docs ]
8 |
9 | jobs:
10 | build-deploy:
11 | runs-on: macos-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - run: npm ci
15 | - run: npm run build
16 | - name: deploy
17 | uses: JamesIves/github-pages-deploy-action@releases/v3
18 | with:
19 | CLEAN: true
20 | BRANCH: gh-pages
21 | FOLDER: dist/
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/DOCUMENT.md:
--------------------------------------------------------------------------------
1 | # Vimkey Help
2 |
3 | When you meet some issues, make sure you have updated to latest Vimkey version.
4 |
5 | ## Custom Disable Rule not Work
6 |
7 | - Solution 1: try reset Disable settings or All Vimkey settings
8 | - Solution 2: reinstall Vimkey
9 |
10 | ## Keyboard Bindings
11 |
12 | On any website use [```?```] (shift+/) key to get Vimkey Keybindings help.
13 |
14 |
15 | ## Help [?] key display missing some keys description
16 |
17 | Reset All Vimkey settings
18 |
19 | ## Website can't compatible with Vimkey
20 |
21 | if you in any website get into some trouble you can use the [i] key to temporarily disable Vimkey or go Vimkey disable rule setting permanently disable this website.
22 |
23 | ## If you have other question or advices
24 |
25 | Contact me in the Vimkey Feedback or on GitHub create a new issue let me know.
26 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vimkey
7 |
8 |
9 | 用键盘操控浏览器,导航、滚动、切换标签、搜索等。
10 |
11 |
12 |
13 | English | 简体中文
14 |
15 |
16 |
21 |
22 |
30 |
31 |
32 | ## 功能简介
33 |
34 |
35 |
36 |
37 |
38 |
39 |
功能丰富
40 |
41 | 使用键盘点击按钮或者打开链接
42 | 自定义按键绑定,按需定义。
43 | 为 120Mhz / ProMotion 高刷屏优化的平滑滚动。
44 | 支持导入导出,多平台配置兼容。
45 | 无障碍友好
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
界面美观,使用简单。
56 |
57 | 简洁直观的用户界面。
58 | 友好的新人引导和帮助,帮你快速上手 Vimkey
59 | 流畅自然的过渡动画
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
跨平台、支持多个浏览器
70 |
71 | 支持 Safari Chrome Edge 等浏览器,Safari 支持 macOS / iPadOS / iOS
72 |
73 |
74 | iOS & iPadOS 系统版本要求 15.0+
75 |
76 | macOS 系统版本要求: macOS 11.0+
77 |
78 |
79 |
80 |
81 | ## 按键功能
82 |
83 | **导航**
84 |
85 | ```
86 | J 切换到上一个标签页
87 | K 切换到下一个标签页
88 | H 后退
89 | L 前进
90 | ```
91 |
92 | **滚动控制**
93 |
94 | ```
95 | k 向上滚动
96 | j 向下滚动
97 | h 向左滚动
98 | l 向右滚动
99 | u 快速向上滚动
100 | d 快速向下滚动
101 | ```
102 |
103 | **打开 & 搜索**
104 | ```markdown
105 | f 高亮标记当前页可点击的按钮或者链接
106 | o 打开一个链接(按shift从新窗口中打开)
107 | T 搜索当前已打开的标签页
108 | t 打开默认标签页
109 | P 在新标签中打开当前复制的链接
110 | p 在当前标签中打开当前复制的链接
111 | ```
112 |
113 | **标签内控制**
114 |
115 | ```markdown
116 | i 临时禁用 Vimkey
117 | gf 选择 iframe
118 | yt 创建一个和当前页相同的标签页
119 | r 刷新页面
120 | x 关闭页面
121 | X 恢复刚刚关闭的页面
122 | gi 选中当前页面中第一个输入框
123 | yy 复制当前页面链接
124 | ```
125 |
126 | **媒体播放**
127 |
128 | ```markdown
129 | - 降低音量
130 | = 调高音量
131 | m 静音/取消
132 | ```
133 |
134 | ## 其他
135 |
136 | ```markdown
137 | ? 按键帮助
138 | Esc 取消
139 | ```
140 |
141 | [//]: # (## 支持 & 帮助)
142 |
143 | [//]: # ()
144 | [//]: # (请访问官网支持文档:https://haojen.github.io/vimkey/#/support)
145 |
146 | ## 隐私政策
147 | 当启用 Vimkey 时,您会被告知该扩展会访问敏感信息,这个权限是运行所必要的。Vimkey **从不收集、存储或传输任何信息**,它完全在你的设备上本地运行。
148 |
149 | 在 Safari 中,你可以选择只允许一天或总是允许,你也可以选择只允许在特定的网站或每个网站。您可以在任何时候调整或撤销访问权限。
150 |
151 | ## 特别感谢
152 |
153 | Vimium and Vimari 提供了很多灵感和参考。
154 |
155 | ## 关于
156 |
157 | 我是一个独立开发者,这是我唯一的收入来源,正因如此任何形式的支持对我都十分重要(加星、分享、评论、反馈、请我喝杯咖啡等)
158 | 对我是莫大的认可与激励,让我有更多的动力继续打磨、创造出更好的产品。
159 |
160 | ## 请我喝杯饮料 🥤️
161 |
162 |
163 | Copyright © HAOZHEN MA 2022
164 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vimkey
8 |
9 |
10 | Use the Keyboard to control the browser, reducing dependence on the Mouse & Trackpad.
11 |
12 |
13 |
14 | English | 简体中文
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Features
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Powerful Feature
40 |
41 | Use the keyboard trigger click button or open a link
42 | Custom key binding.
43 | Smooth scrolling optimized for 120Mhz/ProMotion high rate screen.
44 | Support config export & import
45 | Customize filter rules to disable Vimkey.
46 | Accessibility friendly.
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
Beautiful UI, Easy to Use
57 |
58 | Simple and intuitive user interface.
59 | Friendly new guide and help to help you get started with Vimkey quickly.
60 | Smooth and natural transition animation.
61 | Support Light / Dark theme
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Cross-platform, multi-browser support
72 |
73 | Support Safari Chrome Edge. Safari (macOS / iPadOS / iOS)
74 |
75 |
76 | iOS & iPadOS system requirement: 15.0 or above
77 |
78 | macOS system requirement: macOS 11.0 or above
79 |
80 |
81 |
82 |
83 |
84 | ## Keyboard Bindings
85 |
86 | **Navigation**
87 |
88 | ```
89 | J Switch to pre tab
90 | K Switch to next tab
91 | H Go back in history
92 | L Go forward in history
93 | ```
94 |
95 | #### Scroll
96 |
97 | ```
98 | k Scroll up
99 | j Scroll down
100 | u Fast scroll up
101 | d Fast scroll down
102 | h Scroll left
103 | l Scroll right
104 | ```
105 |
106 | #### Open & Search
107 | ```markdown
108 | f To highlight current page all button or links
109 | o Open A Url From The Tab Or A New Tab (With Shift)
110 | T Search Tabs
111 | t Open A New Tab
112 | P Open The Clipboard's URL In A New Tab
113 | p Open The Clipboard's URL In The Current Tab
114 | ```
115 |
116 | #### Tab Control
117 |
118 | ```markdown
119 | i TemporarilyDisableModeDescription
120 | gf Select The Next Frame On Page
121 | yt Duplicate Current Tab
122 | r Refresh Page
123 | X Restore Page
124 | x Close Current Page
125 | gi Focus On First Input Field
126 | yy Copy The Current URL
127 | ```
128 |
129 | #### Media Control
130 |
131 | ```markdown
132 | - Reduce Sound Volume
133 | = Increase Sound Volume
134 | m Mute/Unmute
135 | ```
136 |
137 | ## Other
138 |
139 | ```
140 | ? Get Help
141 | Escape Cancel or blur from input
142 | ```
143 |
144 | Privacy Policy
145 |
146 | When enabling Vimkey, you will be warned that the extension will have access to sensitive information. This access is required for the extension to be able to interact with the website. Vimkey never collects, stores, or transmits any information. It runs entirely locally on your device
147 |
148 | in Safari, You can choose to allow just for one day or always and you can also choose to allow just on specific websites or on every website. You can revoke access at any time using Manage Extensions
149 |
150 | Thanks
151 |
152 | Vimium and Vimari provided me with a lot of inspiration and reference
153 |
154 | About
155 |
156 | As an indie developer, this is my only source of income, which is why your support means so much to me, and it's a great encourage.
157 |
158 | Star, share, comment, feedback, buy me coffee, etc.
159 |
160 | ## Buy me coffee ☕️
161 |
162 |
163 | Copyright © HAOZHEN MA 2022
164 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vimkey",
3 | "version": "2.0.1",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "register-service-worker": "^1.7.1",
13 | "vue": "^3.0.0",
14 | "vue-router": "^4.0.0-0",
15 | "vue3-markdown-it": "^1.0.9"
16 | },
17 | "devDependencies": {
18 | "@tailwindcss/postcss7-compat": "^2.0.2",
19 | "@typescript-eslint/eslint-plugin": "^4.18.0",
20 | "@typescript-eslint/parser": "^4.18.0",
21 | "@vue/cli-plugin-babel": "~4.5.0",
22 | "@vue/cli-plugin-eslint": "~4.5.0",
23 | "@vue/cli-plugin-pwa": "~4.5.0",
24 | "@vue/cli-plugin-router": "~4.5.0",
25 | "@vue/cli-plugin-typescript": "~4.5.0",
26 | "@vue/cli-service": "~4.5.0",
27 | "@vue/compiler-sfc": "^3.0.0",
28 | "@vue/eslint-config-standard": "^5.1.2",
29 | "@vue/eslint-config-typescript": "^7.0.0",
30 | "autoprefixer": "^9",
31 | "eslint": "^6.7.2",
32 | "eslint-plugin-import": "^2.20.2",
33 | "eslint-plugin-node": "^11.1.0",
34 | "eslint-plugin-promise": "^4.2.1",
35 | "eslint-plugin-standard": "^4.0.0",
36 | "eslint-plugin-vue": "^7.0.0",
37 | "postcss": "^7",
38 | "raw-loader": "^4.0.2",
39 | "sass": "^1.26.5",
40 | "sass-loader": "^8.0.2",
41 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2",
42 | "typescript": "~4.1.5",
43 | "vue-cli-plugin-tailwind": "~2.0.6"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/assets/BuyMeCoffeeQRCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/BuyMeCoffeeQRCode.png
--------------------------------------------------------------------------------
/public/assets/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg:
--------------------------------------------------------------------------------
1 |
2 | Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/public/assets/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.svg:
--------------------------------------------------------------------------------
1 |
2 | Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/public/assets/Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917.svg:
--------------------------------------------------------------------------------
1 |
2 | Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/public/assets/Download_on_the_Mac_App_Store_Badge_US-UK_RGB_wht_092917.svg:
--------------------------------------------------------------------------------
1 |
2 | Download_on_the_Mac_App_Store_Badge_US-UK_RGB_wht_092917
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/public/assets/Vimkey-macOS-store-f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-f.png
--------------------------------------------------------------------------------
/public/assets/Vimkey-macOS-store-grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-grid.png
--------------------------------------------------------------------------------
/public/assets/Vimkey-macOS-store-search&open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-search&open.png
--------------------------------------------------------------------------------
/public/assets/Vimkey-macOS-store-setting.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/Vimkey-macOS-store-setting.jpg
--------------------------------------------------------------------------------
/public/assets/background-dark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/background-dark.jpg
--------------------------------------------------------------------------------
/public/assets/chrome-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/chrome-web-store.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/chrome-web-store.png
--------------------------------------------------------------------------------
/public/assets/chrome-webstore.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
361 |
--------------------------------------------------------------------------------
/public/assets/features-accessibility.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/features-accessibility.png
--------------------------------------------------------------------------------
/public/assets/features-privacy-policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/features-privacy-policy.png
--------------------------------------------------------------------------------
/public/assets/features-smoothscroll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/features-smoothscroll.png
--------------------------------------------------------------------------------
/public/assets/guy-working-at-home-2127164-0.svg:
--------------------------------------------------------------------------------
1 | Asset 15
--------------------------------------------------------------------------------
/public/assets/logo-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/logo-256.png
--------------------------------------------------------------------------------
/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/logo.png
--------------------------------------------------------------------------------
/public/assets/toolbar-icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/toolbar-icon-32.png
--------------------------------------------------------------------------------
/public/assets/vimkey-f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/vimkey-f.png
--------------------------------------------------------------------------------
/public/assets/vimkey-setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/vimkey-setting.png
--------------------------------------------------------------------------------
/public/assets/website-home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haojen/vimkey/1341fcf4e746bdbb12194754ca8eb6946d29f94f/public/assets/website-home.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vimkey
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
HOME
42 |
PRIVACY
43 |
SUPPORT
44 |
45 |
46 |
47 |
48 |
49 | Copyright © {{ new Date().getFullYear() }}
HAOZHEN MA
50 |
51 |
52 |
53 |
54 |
69 |
--------------------------------------------------------------------------------
/src/log.ts:
--------------------------------------------------------------------------------
1 | export const SRLOG = (text: string) => `shadertoy-react: ${text}`
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import './registerServiceWorker'
4 | import router from './router'
5 |
6 | createApp(App).use(router).mount('#app')
7 |
--------------------------------------------------------------------------------
/src/refs.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 |
3 | export const currentPageState = ref<'Home' | 'Privacy' | 'Support'>('Home')
--------------------------------------------------------------------------------
/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { register } from 'register-service-worker'
4 |
5 | if (process.env.NODE_ENV === 'production') {
6 | register(`${process.env.BASE_URL}service-worker.js`, {
7 | ready () {
8 | console.log(
9 | 'App is being served from cache by a service worker.\n' +
10 | 'For more details, visit https://goo.gl/AFskqB'
11 | )
12 | },
13 | registered () {
14 | console.log('Service worker has been registered.')
15 | },
16 | cached () {
17 | console.log('Content has been cached for offline use.')
18 | },
19 | updatefound () {
20 | console.log('New content is downloading.')
21 | },
22 | updated () {
23 | console.log('New content is available; please refresh.')
24 | },
25 | offline () {
26 | console.log('No internet connection found. App is running in offline mode.')
27 | },
28 | error (error) {
29 | console.error('Error during service worker registration:', error)
30 | }
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/src/router.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router'
2 | import Home from './views/Home.vue'
3 | import { currentPageState } from '@/refs'
4 |
5 | const routes: Array = [
6 | {
7 | path: '/',
8 | name: 'Home',
9 | component: Home
10 | },
11 | // {
12 | // path: '/about',
13 | // name: 'About',
14 | // // route level code-splitting
15 | // // this generates a separate chunk (about.[hash].js) for this route
16 | // // which is lazy-loaded when the route is visited.
17 | // component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
18 | // },
19 | {
20 | path: '/support',
21 | name: 'Support',
22 | component: () => import(/* webpackChunkName: "support" */ './views/Support.vue')
23 | },
24 | {
25 | path: '/privacy',
26 | name: 'Privacy',
27 | component: () => import(/* webpackChunkName: "support" */ './views/Privacy.vue')
28 | }
29 | ]
30 |
31 | const router = createRouter({
32 | routes,
33 | history: createWebHashHistory(process.env.BASE_URL)
34 | })
35 |
36 | router.beforeEach((to) => {
37 | currentPageState.value = to.name as 'Home' | 'Privacy' | 'Support'
38 | })
39 |
40 | export default router
41 |
--------------------------------------------------------------------------------
/src/shadertoy-transform.ts:
--------------------------------------------------------------------------------
1 | import { SRLOG } from './log'
2 |
3 | const PRECISIONS = ['lowp', 'mediump', 'highp']
4 |
5 | const FS_MAIN_SHADER = `\nvoid main(void){
6 | vec4 color = vec4(0.0,0.0,0.0,1.0);
7 | mainImage( color, gl_FragCoord.xy );
8 | gl_FragColor = color;
9 | }`
10 |
11 | const BASIC_FS =
12 | // Basic shadertoy shader
13 | `void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
14 | vec2 uv = fragCoord/iResolution.xy;
15 | vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
16 | fragColor = vec4(col,1.0);
17 | }`
18 |
19 | const BASIC_VS = `
20 | attribute vec3 aVertexPosition;
21 | void main(void) {
22 | gl_Position = vec4(aVertexPosition, 1.0);
23 | }
24 | `
25 |
26 | enum Uniforms {
27 | UNIFORM_TIME = 'iTime',
28 | UNIFORM_TIMEDELTA = 'iTimeDelta',
29 | UNIFORM_DATE = 'iDate',
30 | UNIFORM_FRAME = 'iFrame',
31 | UNIFORM_MOUSE = 'iMouse',
32 | UNIFORM_RESOLUTION = 'iResolution',
33 | // UNIFORM_CHANNEL = 'iChannel',
34 | // UNIFORM_CHANNEL_RESOLUTION = 'iChannelResolution',
35 | UNIFORM_DEVICEORIENTATION = 'iDeviceOrientation', // Uniforms not built-int in shadertoy
36 | }
37 |
38 | type Uniform = {
39 | type: string,
40 | value: number | Array,
41 | };
42 |
43 | type Props = {
44 | fs: string,
45 | vs?: string,
46 | uniforms?: Array,
47 | clearColor?: Array,
48 | precision: string | 'highp' | 'mediump' | 'lowp',
49 | style?: string,
50 | // eslint-disable-next-line no-undef
51 | contextAttributes: WebGLContextAttributes,
52 | lerp?: number,
53 | devicePixelRatio: number,
54 | canvas: HTMLCanvasElement
55 | };
56 |
57 |
58 | const defaultProps = {
59 | vs: BASIC_VS,
60 | precision: 'highp',
61 | devicePixelRatio: 1,
62 | contextAttributes: {},
63 | }
64 |
65 | type Shaders = { fs: string, vs: string };
66 |
67 | function lerpVal(v0: number, v1: number, t: number) {
68 | return v0 * (1 - t) + v1 * t
69 | }
70 | function insertStringAtIndex(currentString: string, string: string, index: number) {
71 | return index > 0 ? currentString.substring(0, index) + string + currentString.substring(index, currentString.length) : string + currentString
72 | }
73 |
74 | export default class ShadertoyTransform {
75 | uniforms:{
76 | [k in string]: {
77 | type: string
78 | isNeeded: boolean
79 | value: number[]
80 | }
81 | }
82 | gl: WebGLRenderingContext | null = null;
83 | squareVerticesBuffer: WebGLBuffer | null = null;
84 | shaderProgram!: WebGLProgram | null;
85 | vertexPositionAttribute!: number;
86 | animFrameId?: number;
87 | canvas: HTMLCanvasElement;
88 | mousedown = false;
89 | // eslint-disable-next-line no-undef
90 | canvasPosition: ClientRect;
91 | timer = 0;
92 | lastMouseArr: Array = [0, 0];
93 | lastTime = 0;
94 |
95 | props: Props
96 |
97 | constructor(props: Props) {
98 |
99 | this.props = props
100 | this.canvas = props.canvas
101 | this.canvasPosition = props.canvas.getBoundingClientRect()
102 |
103 | this.uniforms = {
104 | [Uniforms.UNIFORM_TIME]: {
105 | type: 'float',
106 | isNeeded: false,
107 | value: [0],
108 | },
109 | [Uniforms.UNIFORM_TIMEDELTA]: {
110 | type: 'float',
111 | isNeeded: false,
112 | value: [0],
113 | },
114 | [Uniforms.UNIFORM_DATE]: {
115 | type: 'vec4',
116 | isNeeded: false,
117 | value: [0, 0, 0, 0],
118 | },
119 | [Uniforms.UNIFORM_MOUSE]: {
120 | type: 'vec4',
121 | isNeeded: false,
122 | value: [0, 0, 0, 0],
123 | },
124 | [Uniforms.UNIFORM_RESOLUTION]: {
125 | type: 'vec2',
126 | isNeeded: false,
127 | value: [0, 0],
128 | },
129 | [Uniforms.UNIFORM_FRAME]: {
130 | type: 'int',
131 | isNeeded: false,
132 | value: [0],
133 | },
134 | [Uniforms.UNIFORM_DEVICEORIENTATION]: {
135 | type: 'vec4',
136 | isNeeded: false,
137 | value: [0, 0, 0, 0],
138 | }
139 | }
140 | }
141 |
142 | componentDidMount = () => {
143 | this.initWebGL()
144 |
145 | const { fs, vs, clearColor = [0, 0, 0, 1] } = this.props
146 | const { gl } = this
147 |
148 | if (!gl) return
149 |
150 | gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3])
151 | gl.clearDepth(1.0)
152 | gl.enable(gl.DEPTH_TEST)
153 | gl.depthFunc(gl.LEQUAL)
154 | gl.viewport(0, 0, this.canvas.width, this.canvas.height)
155 |
156 | this.canvas.height = this.canvas.clientHeight
157 | this.canvas.width = this.canvas.clientWidth
158 |
159 | this.initShaders(this.preProcessShaders(fs || BASIC_FS, vs || BASIC_VS))
160 | this.initBuffers()
161 | this.drawScene(0)
162 | // this.addEventListeners()
163 | this.onResize()
164 | }
165 |
166 | componentWillUnmount(): void {
167 | const { gl } = this
168 |
169 | if (gl) {
170 | gl.getExtension('WEBGL_lose_context')!.loseContext()
171 |
172 | gl.useProgram(null)
173 | gl.deleteProgram(this.shaderProgram)
174 |
175 | this.shaderProgram = null
176 | }
177 |
178 | this.removeEventListeners()
179 | cancelAnimationFrame(this.animFrameId!)
180 | }
181 |
182 | initWebGL(): void {
183 | const { contextAttributes } = this.props
184 | this.gl = this.canvas.getContext('webgl', contextAttributes)
185 | this.gl!.getExtension('OES_standard_derivatives')
186 | this.gl!.getExtension('EXT_shader_texture_lod')
187 | }
188 |
189 | initBuffers(): void {
190 | const { gl } = this
191 |
192 | if (gl === null) return
193 |
194 | this.squareVerticesBuffer = gl.createBuffer()
195 |
196 | gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesBuffer)
197 |
198 | const vertices = [
199 | 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0,
200 | ]
201 |
202 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
203 | }
204 |
205 | addEventListeners(): void {
206 | const options = {
207 | passive: true,
208 | }
209 |
210 | if (this.uniforms.iMouse.isNeeded) {
211 | this.canvas.addEventListener('mousemove', this.mouseMove, options)
212 | this.canvas.addEventListener('mouseout', this.mouseUp, options)
213 | this.canvas.addEventListener('mouseup', this.mouseUp, options)
214 | this.canvas.addEventListener('mousedown', this.mouseDown, options)
215 |
216 | // this.canvas.addEventListener('touchmove', this.mouseMove, options)
217 | // this.canvas.addEventListener('touchend', this.mouseUp, options)
218 | // this.canvas.addEventListener('touchstart', this.mouseDown, options)
219 | }
220 |
221 | if (this.uniforms.iDeviceOrientation.isNeeded) {
222 | window.addEventListener(
223 | 'deviceorientation',
224 | this.onDeviceOrientationChange,
225 | options
226 | )
227 | }
228 |
229 | window.addEventListener('resize', this.onResize, options)
230 | }
231 |
232 | removeEventListeners = () => {
233 | if (this.uniforms.iMouse.isNeeded) {
234 | this.canvas.removeEventListener('mousemove', this.mouseMove)
235 | this.canvas.removeEventListener('mouseout', this.mouseUp)
236 | this.canvas.removeEventListener('mouseup', this.mouseUp)
237 | this.canvas.removeEventListener('mousedown', this.mouseDown)
238 |
239 | // this.canvas.removeEventListener('touchmove', this.mouseMove)
240 | // this.canvas.removeEventListener('touchend', this.mouseUp)
241 | // this.canvas.removeEventListener('touchstart', this.mouseDown)
242 | }
243 |
244 | if (this.uniforms.iDeviceOrientation.isNeeded) {
245 | window.removeEventListener(
246 | 'deviceorientation',
247 | this.onDeviceOrientationChange,
248 | )
249 | }
250 |
251 | window.removeEventListener('resize', this.onResize)
252 | };
253 |
254 | onDeviceOrientationChange = (evt: DeviceOrientationEvent) => {
255 | const { alpha, beta, gamma } = evt
256 | this.uniforms.iDeviceOrientation.value = [
257 | alpha || 0,
258 | beta || 0,
259 | gamma || 0,
260 | window.screen.orientation.angle,
261 | ]
262 | };
263 |
264 | mouseDown = (e: MouseEvent) => {
265 | const clientX = e.clientX // || e.changedTouches[0].clientX
266 | const clientY = e.clientY // || e.changedTouches[0].clientY
267 |
268 | const mouseX = clientX - this.canvasPosition.left - window.pageXOffset
269 | const mouseY =
270 | this.canvasPosition.height -
271 | clientY -
272 | this.canvasPosition.top -
273 | window.pageYOffset
274 |
275 | this.mousedown = true
276 | this.uniforms.iMouse.value[2] = mouseX
277 | this.uniforms.iMouse.value[3] = mouseY
278 |
279 | this.lastMouseArr[0] = mouseX
280 | this.lastMouseArr[1] = mouseY
281 | };
282 |
283 | mouseMove = (e: MouseEvent) => {
284 | this.canvasPosition = this.canvas.getBoundingClientRect()
285 | const { lerp = 1 } = this.props
286 |
287 | const clientX = e.clientX // || e.changedTouches[0].clientX
288 | const clientY = e.clientY // || e.changedTouches[0].clientY
289 |
290 | const mouseX = clientX - this.canvasPosition.left
291 | const mouseY = this.canvasPosition.height - clientY - this.canvasPosition.top
292 |
293 | if (lerp !== 1) {
294 | this.lastMouseArr[0] = mouseX
295 | this.lastMouseArr[1] = mouseY
296 | } else {
297 | this.uniforms.iMouse.value[0] = mouseX
298 | this.uniforms.iMouse.value[1] = mouseY
299 | }
300 | };
301 |
302 | mouseUp = () => {
303 | this.uniforms.iMouse.value[2] = 0
304 | this.uniforms.iMouse.value[3] = 0
305 | };
306 |
307 | onResize = () => {
308 | const { gl } = this
309 | const { devicePixelRatio = 1 } = this.props
310 |
311 | if (gl === null) return
312 |
313 | this.canvasPosition = this.canvas.getBoundingClientRect()
314 |
315 | const realToCSSPixels = devicePixelRatio // Force pixel ratio to be one to avoid expensive calculus on retina display
316 |
317 | const displayWidth = Math.floor(
318 | this.canvasPosition.width * realToCSSPixels
319 | )
320 |
321 | const displayHeight = Math.floor(
322 | this.canvasPosition.height * realToCSSPixels
323 | )
324 |
325 | gl.canvas.width = displayWidth
326 | gl.canvas.height = displayHeight
327 |
328 | if (this.uniforms.iResolution.isNeeded) {
329 | const rUniform = gl.getUniformLocation(
330 | this.shaderProgram!,
331 | Uniforms.UNIFORM_RESOLUTION
332 | )
333 | // $FlowFixMe
334 | gl.uniform2fv(rUniform, [gl.canvas.width, gl.canvas.height])
335 | }
336 | };
337 |
338 | fpsLimit = 50
339 | previousDelta = new Date().getTime()
340 | drawScene = (timestamp: number) => {
341 | this.animFrameId = requestAnimationFrame(this.drawScene)
342 |
343 | const now = new Date().getTime()
344 | const delta = now - this.previousDelta
345 |
346 | if (this.fpsLimit && delta < 1000 / this.fpsLimit) {
347 | return
348 | }
349 |
350 | const gl = this.gl!
351 | const { lerp = 1 } = this.props
352 |
353 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
354 |
355 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
356 |
357 | gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVerticesBuffer)
358 | gl.vertexAttribPointer(this.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0)
359 |
360 | this.setUniforms(timestamp)
361 |
362 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
363 |
364 | if (this.uniforms.iMouse.isNeeded && lerp !== 1) {
365 | this.uniforms.iMouse.value[0] = lerpVal(
366 | this.uniforms.iMouse.value[0],
367 | this.lastMouseArr[0],
368 | lerp
369 | )
370 | this.uniforms.iMouse.value[1] = lerpVal(
371 | this.uniforms.iMouse.value[1],
372 | this.lastMouseArr[1],
373 | lerp
374 | )
375 | }
376 |
377 | this.previousDelta = now
378 | }
379 |
380 | createShader(type: number, shaderCodeAsText: string): WebGLShader {
381 | const gl = this.gl!
382 |
383 | const shader = gl.createShader(type)!
384 |
385 | gl.shaderSource(shader, shaderCodeAsText)
386 | gl.compileShader(shader)
387 |
388 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
389 | console.warn(SRLOG('Error compiling the shader:'), shaderCodeAsText)
390 | const compilationLog = gl.getShaderInfoLog(shader)
391 | gl.deleteShader(shader)
392 | console.error(SRLOG(`Shader compiler log: ${compilationLog}`))
393 | }
394 |
395 | return shader
396 | }
397 |
398 | initShaders ({ fs, vs }: Shaders): void {
399 | const gl = this.gl!
400 | // console.log(fs, vs);
401 | const fragmentShader = this.createShader(gl.FRAGMENT_SHADER, fs)
402 | const vertexShader = this.createShader(gl.VERTEX_SHADER, vs)
403 |
404 | this.shaderProgram = gl.createProgram()
405 |
406 | if (this.shaderProgram === null) return
407 |
408 | gl.attachShader(this.shaderProgram, vertexShader)
409 | gl.attachShader(this.shaderProgram, fragmentShader)
410 | gl.linkProgram(this.shaderProgram)
411 |
412 | if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) {
413 | // $FlowFixMe
414 | console.error(
415 | SRLOG(
416 | `Unable to initialize the shader program: ${gl.getProgramInfoLog(
417 | this.shaderProgram
418 | )}`
419 | )
420 | )
421 | return
422 | }
423 |
424 | gl.useProgram(this.shaderProgram)
425 |
426 | this.vertexPositionAttribute = gl.getAttribLocation(
427 | this.shaderProgram,
428 | 'aVertexPosition'
429 | )
430 | gl.enableVertexAttribArray(this.vertexPositionAttribute)
431 | }
432 |
433 | preProcessShaders(fs: string, vs: string): { fs: string, vs: string } {
434 | const { precision, devicePixelRatio = 1 } = this.props
435 |
436 | const dprString = `#define DPR ${devicePixelRatio.toFixed(1)} \n`
437 | const isValidPrecision = PRECISIONS.includes(precision)
438 | const precisionString = `precision ${ isValidPrecision ? precision : PRECISIONS[1]} float; \n`
439 | if (!isValidPrecision)
440 | console.warn(
441 | SRLOG(`wrong precision type ${ precision }, please make sure to pass one of a valid precision lowp, mediump, highp, by default you shader precision will be set to highp.`)
442 | )
443 |
444 | let fsString = precisionString
445 | .concat(dprString)
446 | .concat(fs)
447 | .replace(/texture\(/g, 'texture2D(')
448 |
449 | const indexOfPrecisionString = fsString.lastIndexOf(precisionString)
450 |
451 | Object.keys(this.uniforms).forEach((uniform) => {
452 | if (!fs.includes(uniform)) return
453 |
454 | fsString = insertStringAtIndex( fsString,
455 | `uniform ${this.uniforms[uniform].type} ${uniform} ; \n`,
456 | indexOfPrecisionString + precisionString.length
457 | )
458 | this.uniforms[uniform].isNeeded = true
459 | })
460 |
461 | const isShadertoy = /mainImage/.test(fs)
462 | if (isShadertoy) fsString = fsString.concat(FS_MAIN_SHADER)
463 |
464 | return {
465 | fs: fsString,
466 | vs,
467 | }
468 | }
469 |
470 | setUniforms(timestamp: number): void {
471 | const gl = this.gl!
472 |
473 | if (!this.shaderProgram) return
474 |
475 | const delta = this.lastTime ? (timestamp - this.lastTime) / 1000 : 0
476 | this.lastTime = timestamp
477 |
478 | if (this.uniforms.iMouse.isNeeded) {
479 | const mouseUniform = gl.getUniformLocation(
480 | this.shaderProgram,
481 | Uniforms.UNIFORM_MOUSE
482 | )
483 | gl.uniform4fv(mouseUniform, this.uniforms.iMouse.value)
484 | }
485 |
486 | // if (
487 | // this.uniforms.iChannelResolution &&
488 | // this.uniforms.iChannelResolution.isNeeded
489 | // ) {
490 | // const channelResUniform = gl.getUniformLocation(
491 | // this.shaderProgram,
492 | // Uniforms.UNIFORM_CHANNEL_RESOLUTION
493 | // )
494 | // gl.uniform3fv(channelResUniform, this.uniforms.iChannelResolution.value)
495 | // }
496 |
497 | if (this.uniforms.iDeviceOrientation.isNeeded) {
498 | const deviceOrientationUniform = gl.getUniformLocation(
499 | this.shaderProgram,
500 | Uniforms.UNIFORM_DEVICEORIENTATION
501 | )
502 | gl.uniform4fv(
503 | deviceOrientationUniform,
504 | this.uniforms.iDeviceOrientation.value
505 | )
506 | }
507 |
508 | if (this.uniforms.iTime.isNeeded) {
509 | const timeUniform = gl.getUniformLocation(
510 | this.shaderProgram,
511 | Uniforms.UNIFORM_TIME
512 | )
513 |
514 | gl.uniform1f(timeUniform, (this.timer += delta))
515 | }
516 |
517 | if (this.uniforms.iTimeDelta.isNeeded) {
518 | const timeDeltaUniform = gl.getUniformLocation(
519 | this.shaderProgram,
520 | Uniforms.UNIFORM_TIMEDELTA[0]
521 | )
522 | gl.uniform1f(timeDeltaUniform, delta)
523 | }
524 |
525 | if (this.uniforms.iDate.isNeeded) {
526 | const d = new Date()
527 | const month = d.getMonth() + 1
528 | const day = d.getDate()
529 | const year = d.getFullYear()
530 | const time =
531 | d.getHours() * 60 * 60 +
532 | d.getMinutes() * 60 +
533 | d.getSeconds() +
534 | d.getMilliseconds() * 0.001
535 |
536 | const dateUniform = gl.getUniformLocation(
537 | this.shaderProgram,
538 | Uniforms.UNIFORM_DATE
539 | )
540 |
541 | gl.uniform4fv(dateUniform, [year, month, day, time])
542 | }
543 |
544 | if (this.uniforms.iFrame.isNeeded) {
545 | const timeDeltaUniform = gl.getUniformLocation(
546 | this.shaderProgram,
547 | Uniforms.UNIFORM_FRAME[0]
548 | )
549 | gl.uniform1i(timeDeltaUniform, this.uniforms.iFrame.value[0]++)
550 | }
551 | }
552 | }
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | declare module '*.vue' {
3 | import type { DefineComponent } from 'vue'
4 | const component: DefineComponent<{}, {}, any>
5 | export default component
6 | }
7 |
8 | interface WebGLTexture {
9 | _webglTexture: WebGLTexture
10 | }
11 |
12 | declare module '*.md'
13 | declare module '*.frag'
14 | declare module '*.vert'
15 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Vimkey
41 |
42 | Use the keyboard to control browser, navigation, scroll, open new page, trigger click button etc.
43 | reduce your reliance on the mouse & trackpad.
44 |
45 |
46 |
62 |
63 | More browser support coming soon..
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Introduce
75 |
76 |
77 |
78 |
Features
79 |
80 | Trigger button or open new link
81 | Navigation, switch tab
82 | Quick scroll, custom steps
83 | Media Control
84 | Search & Open URLs
85 |
86 |
87 |
88 |
89 |
90 |
91 |
Beautiful Interface
92 |
93 | Cool Animation.
94 | Simple and intuitive user interface.
95 | Friendly new guide to help you get started with Vimkey quickly.
96 |
97 |
98 |
99 |
100 |
101 |
102 |
Friendly Settings
103 |
104 | Custom key binding.
105 | Custom rules to disable Vimkey.
106 | Smooth scrolling optimized for 120Mhz/ProMotion high rate screen.
107 | Support export & import config.
108 | Accessibility friendly.
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
147 |
--------------------------------------------------------------------------------
/src/views/Privacy.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 | Privacy Policy
11 |
12 |
13 | Vimkey No collect any privacy information,
14 | requested permissions are only used for the corresponding features.
15 |
16 |
17 |
18 | When enabling Vimkey, you will be warned that the extension will have access to sensitive information. This access is required for the extension to be able to interact with the website. Vimkey never collects, stores, or transmits any information. It runs entirely locally on your device
19 | in Safari, You can choose to allow just for one day or always and you can also choose to allow just on specific websites or on every website. You can revoke access at any time using Manage Extensions.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
--------------------------------------------------------------------------------
/src/views/Support.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
36 |
--------------------------------------------------------------------------------
/src/views/shader.frag:
--------------------------------------------------------------------------------
1 | #define TMIN 0.1
2 | #define TMAX 200.
3 | #define RAYMARCH_TIME 128
4 | #define PRECISION 0.05
5 | #define AA 3
6 | #define PI 3.14159
7 |
8 | #define S(v,r) smoothstep( r, r+ 3./iResolution.y, v )
9 |
10 | //========SDFunctions========
11 | float sdSphere(vec3 p, vec3 o, float r){
12 | return length(p-o)-r;
13 | }
14 | //===============TRANSFORM=================
15 | mat2 rotate(float a){
16 | return mat2(cos(a),sin(a),-sin(a),cos(a));
17 | }
18 | float smUni( float d1, float d2, float k ) {
19 | float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
20 | return mix( d2, d1, h ) - k*h*(1.0-h);
21 | }
22 |
23 | //===============RENDER===================
24 | //Scene
25 | float f(vec3 p){
26 | p.zx*=rotate(iTime);
27 | p.yz*=rotate(iTime);
28 | float k = .7;
29 | float d1 = sdSphere(p,vec3(sin(iTime*.5)),1.3);//center sphere
30 | float d2 = sdSphere(p,vec3(0,2.*sin(iTime),0.),.5);
31 | float d = smUni(d1,d2,k);
32 |
33 | float d3 = sdSphere(p,vec3(sin(iTime+5.),0,2.*sin(iTime+5.)),.9);
34 | d= smUni(d,d3,k);
35 |
36 | float d4 = sdSphere(p,vec3(-cos(iTime+.3),cos(iTime+.3),cos(iTime+.3)),.8);
37 | d= smUni(d,d4,k);
38 |
39 | float d5 = sdSphere(p,vec3(3.*cos(iTime+.7),0.,.5),.3);
40 | d= smUni(d,d5,k);
41 |
42 | return d;
43 | }
44 | float rayMarch(in vec3 ro, in vec3 rd) {
45 | float t = TMIN;
46 | for(int i = 0; i < RAYMARCH_TIME ; i++) {
47 | vec3 p = ro + t * rd;
48 | float d = f(p);
49 | t += d;
50 | if(d < PRECISION || t > TMAX)
51 | break;
52 | }
53 | return t;
54 | }
55 |
56 | // https://iquilezles.org/articles/normalsSDF
57 | vec3 calcNormal(in vec3 p) {
58 | const float h = 0.0001;
59 | const vec2 k = vec2(1, -1);
60 | return normalize(k.xyy * f(p + k.xyy * h) +
61 | k.yyx * f(p + k.yyx * h) +
62 | k.yxy * f(p + k.yxy * h) +
63 | k.xxx * f(p + k.xxx * h));
64 | }
65 |
66 | mat3 setCamera(in vec3 camtar, in vec3 campos, in float camro){
67 | vec3 z = normalize(camtar-campos);
68 | vec3 cp = vec3(sin(camro),cos(camro),0.);
69 | vec3 x = normalize(cross(cp,z));
70 | vec3 y = cross(z,x);
71 | return mat3(x,y,z);
72 | }
73 | vec3 render(vec2 uv){
74 | vec3 lightPos = vec3(-5., 5.,-5);//light
75 |
76 | //SET Camera
77 | vec3 cam_tar = vec3(-1,3,2);//cam target
78 | vec3 cam_pos = cam_tar +vec3(-10,20,15);//cam position
79 |
80 |
81 | vec3 rd = vec3(uv,9.); //decide view width
82 | rd = normalize(setCamera(cam_tar,cam_pos,0.)*rd);//viewing frustum
83 |
84 | float t = rayMarch(cam_pos,rd);//raymarching
85 |
86 | vec3 color = vec3(0.067);//background
87 | if(t > TMAX) return color;
88 |
89 | vec3 p = cam_pos + t*rd;
90 | vec3 n = calcNormal(p);
91 |
92 | color = n*.5+.5;
93 | // fog
94 | color *= exp(-0.4);
95 |
96 | return color;
97 | }
98 | vec2 getuv(vec2 coord){
99 | return (2.*coord-iResolution.xy)/iResolution.y;
100 | }
101 | void mainImage( out vec4 fragColor, in vec2 fragCoord )
102 | {
103 | vec2 uv = (2.*fragCoord-iResolution.xy)/iResolution.y;
104 | vec3 color = vec3(0.);
105 | #if AA>1
106 | for(int m = 0; m < AA; m++) {
107 | for(int n = 0; n < AA; n++) {
108 | vec2 offset = 2. * (vec2(float(m), float(n)) / float(AA) - .5);
109 | vec2 uv = getuv(fragCoord + offset);
110 | #else
111 | uv = getuv(fragCoord);
112 | #endif
113 | color += render(uv);
114 | #if AA>1
115 | }
116 | }
117 | color /= float(AA*AA);
118 | #endif
119 |
120 | color = mix(color,vec3(1),0.);
121 | fragColor = vec4(color,1.0);
122 | }
123 |
--------------------------------------------------------------------------------
/src/views/shader.vert:
--------------------------------------------------------------------------------
1 | attribute vec3 aVertexPosition;
2 | void main(void) {
3 | gl_Position = vec4(aVertexPosition, 1.0);
4 | }
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: [
3 | './src/**/*.ts',
4 | './src/**/*.vue',
5 | './src/**/*.html',
6 | './public/**/*.html'
7 | ],
8 | darkMode: false, // or 'media' or 'class'
9 | theme: {
10 | extend: {},
11 | },
12 | variants: {
13 | extend: {},
14 | },
15 | plugins: [],
16 | corePlugins: {
17 | preflight: true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ]
21 | },
22 | "lib": [
23 | "esnext",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "src/**/*.ts",
31 | "src/**/*.tsx",
32 | "src/**/*.vue",
33 | "tests/**/*.ts",
34 | "tests/**/*.tsx"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: process.env.NODE_ENV === 'production'
3 | ? '/vimkey/'
4 | : '/',
5 | chainWebpack: config => {
6 | config.module.rule('md')
7 | .test(/\.md$/)
8 | .use('raw-loader')
9 | .loader('raw-loader')
10 | .end()
11 | config.module.rule('fragment')
12 | .test(/\.frag$/)
13 | .use('raw-loader')
14 | .loader('raw-loader')
15 | .end()
16 | config.module.rule('vertex')
17 | .test(/\.vert$/)
18 | .use('raw-loader')
19 | .loader('raw-loader')
20 | .end()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------