@aicupa/plugin-balance 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: Npm Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+
8
+ jobs:
9
+ Npm-Publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v2.3.4
14
+
15
+ - name: Install Deps
16
+ run: yarn install
17
+
18
+ - name: publish with latest tag
19
+ uses: JS-DevTools/npm-publish@v1
20
+ with:
21
+ token: ${{ secrets.NPM_AUTH_TOKEN }}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@aicupa/plugin-balance",
3
+ "version": "1.0.2",
4
+ "description": "Show time balance ratio between temporary and current tasks",
5
+ "main": "./service",
6
+ "view": "./view",
7
+ "pluginContributes": {
8
+ "contextMenus": [
9
+ {
10
+ "title": "Copy Node ID",
11
+ "title_zh": "复制节点ID",
12
+ "command": "copyNodeId"
13
+ }
14
+ ],
15
+ "views": {
16
+ "head": true
17
+ }
18
+ },
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org/",
21
+ "access": "public"
22
+ },
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@aicupa/api": "^1.0.1"
26
+ }
27
+ }
package/service.js ADDED
@@ -0,0 +1,110 @@
1
+ const { createPlugin } = require("@aicupa/api");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const fs = require("fs");
5
+
6
+ const configPath = path.join(
7
+ os.homedir(),
8
+ ".todoListNative",
9
+ "plugin-balance.json",
10
+ );
11
+
12
+ function loadConfig() {
13
+ try {
14
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
15
+ } catch (_) {
16
+ return {};
17
+ }
18
+ }
19
+
20
+ function saveConfig(data) {
21
+ try {
22
+ fs.writeFileSync(configPath, JSON.stringify(data));
23
+ } catch (_) {}
24
+ }
25
+
26
+ module.exports = createPlugin((api) => {
27
+ const saved = loadConfig();
28
+ let tempNodeId = saved.tempId || null;
29
+ let currentNodeId = saved.currentId || null;
30
+
31
+ function collectDoneTime(node) {
32
+ let total = 0;
33
+ if (!node) return total;
34
+ const stack = [node];
35
+ while (stack.length) {
36
+ const n = stack.pop();
37
+ if (
38
+ n.todo?.done &&
39
+ n.todo?.start &&
40
+ n.todo?.end &&
41
+ n.todo.end > n.todo.start
42
+ ) {
43
+ total += n.todo.end - n.todo.start;
44
+ }
45
+ if (Array.isArray(n.children)) {
46
+ stack.push(...n.children);
47
+ }
48
+ }
49
+ return total;
50
+ }
51
+
52
+ function findNodeById(tree, id) {
53
+ const stack = [...tree];
54
+ while (stack.length) {
55
+ const n = stack.pop();
56
+ if (String(n.key) === String(id)) return n;
57
+ if (Array.isArray(n.children)) {
58
+ stack.push(...n.children);
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+
64
+ return {
65
+ copyNodeId({ node }) {
66
+ if (node?.key != null) {
67
+ api.clipboard.writeText(String(node.key));
68
+ return { ok: true };
69
+ }
70
+ return { ok: false, error: "No node key" };
71
+ },
72
+
73
+ setConfig({ tempId, currentId }) {
74
+ tempNodeId = tempId;
75
+ currentNodeId = currentId;
76
+ saveConfig({ tempId, currentId });
77
+ return { ok: true };
78
+ },
79
+
80
+ getConfig() {
81
+ return {
82
+ ok: true,
83
+ result: { tempId: tempNodeId, currentId: currentNodeId },
84
+ };
85
+ },
86
+
87
+ calculate({ tree }) {
88
+ if (!Array.isArray(tree) || !tempNodeId || !currentNodeId) {
89
+ return {
90
+ ok: true,
91
+ result: { percent: 0, tempTime: 0, currentTime: 0 },
92
+ };
93
+ }
94
+
95
+ const tempNode = findNodeById(tree, tempNodeId);
96
+ const currentNode = findNodeById(tree, currentNodeId);
97
+ const tempTime = collectDoneTime(tempNode);
98
+ const currentTime = collectDoneTime(currentNode);
99
+ const percent =
100
+ currentTime > 0
101
+ ? Math.round((tempTime / (currentTime + tempTime)) * 100)
102
+ : 0;
103
+
104
+ return {
105
+ ok: true,
106
+ result: { percent, tempTime, currentTime },
107
+ };
108
+ },
109
+ };
110
+ });
@@ -0,0 +1,191 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <style>
7
+ * {
8
+ margin: 0;
9
+ padding: 0;
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
15
+ background: transparent;
16
+ color: inherit;
17
+ font-size: 12px;
18
+ }
19
+
20
+ .balance-container {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 8px;
24
+ padding: 2px 0;
25
+ }
26
+
27
+ .balance-inputs {
28
+ display: flex;
29
+ gap: 4px;
30
+ align-items: center;
31
+ flex-shrink: 0;
32
+ }
33
+
34
+ .balance-input {
35
+ width: 60px;
36
+ height: 20px;
37
+ border: 1px solid rgba(128, 128, 128, 0.3);
38
+ border-radius: 3px;
39
+ background: transparent;
40
+ color: inherit;
41
+ font-size: 11px;
42
+ padding: 0 4px;
43
+ outline: none;
44
+ text-align: center;
45
+ }
46
+
47
+ .balance-input:focus {
48
+ border-color: rgba(64, 128, 255, 0.5);
49
+ }
50
+
51
+ .balance-input::placeholder {
52
+ opacity: 0.4;
53
+ font-size: 10px;
54
+ }
55
+
56
+ .balance-label {
57
+ opacity: 0.5;
58
+ font-size: 10px;
59
+ white-space: nowrap;
60
+ }
61
+
62
+ .balance-bar-wrap {
63
+ flex: 1;
64
+ min-width: 0;
65
+ position: relative;
66
+ }
67
+
68
+ .balance-bar-track {
69
+ height: 4px;
70
+ border-radius: 2px;
71
+ background: rgba(128, 128, 128, 0.15);
72
+ overflow: hidden;
73
+ }
74
+
75
+ .balance-bar-fill {
76
+ height: 100%;
77
+ border-radius: 3px;
78
+ transition: width 0.3s ease, background 0.3s ease;
79
+ }
80
+
81
+ .balance-ratio {
82
+ font-size: 11px;
83
+ opacity: 0.7;
84
+ flex-shrink: 0;
85
+ min-width: 36px;
86
+ text-align: right;
87
+ }
88
+ </style>
89
+ </head>
90
+
91
+ <body>
92
+ <div class="balance-container">
93
+ <div class="balance-inputs">
94
+ <input class="balance-input" id="tempId" placeholder="Temp ID" />
95
+ <span class="balance-label">/</span>
96
+ <input class="balance-input" id="currentId" placeholder="Task ID" />
97
+ </div>
98
+ <div class="balance-bar-wrap">
99
+ <div class="balance-bar-track">
100
+ <div class="balance-bar-fill" id="barFill" style="width:0%"></div>
101
+ </div>
102
+ </div>
103
+ <span class="balance-ratio" id="ratio">0%</span>
104
+ </div>
105
+ <script>
106
+ (function () {
107
+ let callId = 0
108
+ const pending = {}
109
+
110
+ function callPlugin(method, params) {
111
+ return new Promise((resolve) => {
112
+ const id = ++callId
113
+ pending[id] = resolve
114
+ window.parent.postMessage({ type: 'plugin-call', id, method, params }, '*')
115
+ })
116
+ }
117
+
118
+ window.addEventListener('message', (e) => {
119
+ const msg = e.data
120
+ if (msg?.type === 'plugin-result' && pending[msg.id]) {
121
+ pending[msg.id](msg.result || msg.error)
122
+ delete pending[msg.id]
123
+ }
124
+ if (msg?.type === 'plugin-init') {
125
+ if (msg.theme) {
126
+ document.body.style.color = msg.theme.color
127
+ var inputs = document.querySelectorAll('.balance-input')
128
+ for (var i = 0; i < inputs.length; i++) {
129
+ inputs[i].style.color = msg.theme.color
130
+ }
131
+ }
132
+ loadConfig()
133
+ }
134
+ if (msg?.type === 'plugin-tree-update') {
135
+ recalculate(msg.tree)
136
+ }
137
+ })
138
+
139
+ const tempInput = document.getElementById('tempId')
140
+ const currentInput = document.getElementById('currentId')
141
+ const barFill = document.getElementById('barFill')
142
+ const ratioEl = document.getElementById('ratio')
143
+ let debounceTimer = null
144
+
145
+ function onInputChange() {
146
+ clearTimeout(debounceTimer)
147
+ debounceTimer = setTimeout(async () => {
148
+ const tempId = tempInput.value.trim()
149
+ const currentId = currentInput.value.trim()
150
+ await callPlugin('setConfig', { tempId: tempId || null, currentId: currentId || null })
151
+ requestUpdate()
152
+ }, 500)
153
+ }
154
+
155
+ tempInput.addEventListener('input', onInputChange)
156
+ currentInput.addEventListener('input', onInputChange)
157
+
158
+ function requestUpdate() {
159
+ window.parent.postMessage({ type: 'plugin-request-tree' }, '*')
160
+ }
161
+
162
+ async function recalculate(tree) {
163
+ try {
164
+ const res = await callPlugin('calculate', { tree })
165
+ if (res?.ok !== false) {
166
+ const inner = res?.result || res
167
+ const r = inner?.result || inner
168
+ const pct = Math.min(r.percent || 0, 100)
169
+ barFill.style.width = pct + '%'
170
+ var h = Math.round(120 * (1 - pct / 100))
171
+ barFill.style.background = 'hsl(' + h + ', 55%, 65%)'
172
+ ratioEl.textContent = pct + '%'
173
+ }
174
+ } catch (_) { }
175
+ }
176
+
177
+ async function loadConfig() {
178
+ try {
179
+ const res = await callPlugin('getConfig', {})
180
+ const inner = res?.result || res
181
+ const r = inner?.result || inner
182
+ if (r?.tempId) tempInput.value = r.tempId
183
+ if (r?.currentId) currentInput.value = r.currentId
184
+ requestUpdate()
185
+ } catch (_) { }
186
+ }
187
+ })()
188
+ </script>
189
+ </body>
190
+
191
+ </html>