@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.
- package/.github/workflows/main.yml +21 -0
- package/package.json +27 -0
- package/service.js +110 -0
- package/view/index.html +191 -0
|
@@ -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
|
+
});
|
package/view/index.html
ADDED
|
@@ -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>
|