picosh 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +77 -17
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -6,6 +6,8 @@ const os = require('os');
|
|
|
6
6
|
const https = require('https');
|
|
7
7
|
const http = require('http');
|
|
8
8
|
|
|
9
|
+
// ─── image paste ────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
9
11
|
const SAVE_DIR = path.join(os.tmpdir(), 'picosh');
|
|
10
12
|
const SAVE_PATH = path.join(SAVE_DIR, 'clip_latest.png');
|
|
11
13
|
|
|
@@ -21,34 +23,98 @@ function hasImage(clipboard) {
|
|
|
21
23
|
|
|
22
24
|
function saveBitmap(clipboard) {
|
|
23
25
|
fs.mkdirSync(SAVE_DIR, {recursive: true});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return Promise.resolve(filepath);
|
|
26
|
+
fs.writeFileSync(SAVE_PATH, clipboard.readImage().toPNG());
|
|
27
|
+
return Promise.resolve(SAVE_PATH);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function downloadImage(url) {
|
|
30
31
|
return new Promise((resolve) => {
|
|
31
32
|
fs.mkdirSync(SAVE_DIR, {recursive: true});
|
|
32
|
-
const
|
|
33
|
-
const file = fs.createWriteStream(filepath);
|
|
33
|
+
const file = fs.createWriteStream(SAVE_PATH);
|
|
34
34
|
const client = url.startsWith('https') ? https : http;
|
|
35
|
-
|
|
36
35
|
client.get(url, (res) => {
|
|
37
36
|
res.pipe(file);
|
|
38
|
-
file.on('finish', () => file.close(() => resolve(
|
|
37
|
+
file.on('finish', () => file.close(() => resolve(SAVE_PATH)));
|
|
39
38
|
}).on('error', () => {
|
|
40
|
-
fs.unlink(
|
|
39
|
+
fs.unlink(SAVE_PATH, () => {});
|
|
41
40
|
resolve(null);
|
|
42
41
|
});
|
|
43
42
|
});
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
// ─── AI waiting indicator ───────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const ANSI_RE = /\x1b\[[0-9;]*[mGKHF]/g;
|
|
48
|
+
const PROMPT_RE = />\s*$|❯\s*$/;
|
|
49
|
+
const timers = {};
|
|
50
|
+
const waitingState = {};
|
|
51
|
+
|
|
52
|
+
function setWaiting(uid, waiting) {
|
|
53
|
+
waitingState[uid] = waiting;
|
|
54
|
+
if (typeof window !== 'undefined') {
|
|
55
|
+
window.dispatchEvent(new CustomEvent('picosh-ai-waiting', {detail: {uid, waiting}}));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.middleware = () => (next) => (action) => {
|
|
60
|
+
if (action.type === 'SESSION_ADD_DATA') {
|
|
61
|
+
const {uid, data} = action;
|
|
62
|
+
const clean = data.replace(ANSI_RE, '');
|
|
63
|
+
|
|
64
|
+
clearTimeout(timers[uid]);
|
|
65
|
+
setWaiting(uid, false);
|
|
66
|
+
|
|
67
|
+
timers[uid] = setTimeout(() => {
|
|
68
|
+
if (PROMPT_RE.test(clean)) setWaiting(uid, true);
|
|
69
|
+
}, 500);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action.type === 'SESSION_PTY_DATA') {
|
|
73
|
+
clearTimeout(timers[action.uid]);
|
|
74
|
+
setWaiting(action.uid, false);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return next(action);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
exports.decorateTab = (Tab, {React}) => {
|
|
81
|
+
return function PicoshTab(props) {
|
|
82
|
+
const [waiting, setWaitingState] = React.useState(!!waitingState[props.uid]);
|
|
83
|
+
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
function onWaiting(e) {
|
|
86
|
+
if (e.detail.uid === props.uid) setWaitingState(e.detail.waiting);
|
|
87
|
+
}
|
|
88
|
+
window.addEventListener('picosh-ai-waiting', onWaiting);
|
|
89
|
+
return () => window.removeEventListener('picosh-ai-waiting', onWaiting);
|
|
90
|
+
}, [props.uid]);
|
|
91
|
+
|
|
92
|
+
return React.createElement(
|
|
93
|
+
'div',
|
|
94
|
+
{style: {position: 'relative', display: 'contents'}},
|
|
95
|
+
waiting && React.createElement('style', {key: 's'}, `
|
|
96
|
+
@keyframes picosh-glow {
|
|
97
|
+
0%, 100% { box-shadow: 0 0 6px 2px #4a9eff; }
|
|
98
|
+
50% { box-shadow: 0 0 12px 4px #4a9eff; }
|
|
99
|
+
}
|
|
100
|
+
`),
|
|
101
|
+
React.createElement(Tab, Object.assign({}, props, {
|
|
102
|
+
style: Object.assign({}, props.style, waiting ? {
|
|
103
|
+
animation: 'picosh-glow 1.5s ease-in-out infinite',
|
|
104
|
+
borderRadius: '4px',
|
|
105
|
+
} : {}),
|
|
106
|
+
})),
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ─── image paste (term) ──────────────────────────────────────────────────────
|
|
112
|
+
|
|
46
113
|
exports.decorateTerm = (Term, {React}) => {
|
|
47
114
|
return class extends React.Component {
|
|
48
115
|
componentDidMount() {
|
|
49
116
|
this._onKeyDown = (e) => {
|
|
50
117
|
if (!((e.ctrlKey || e.metaKey) && e.key === 'v')) return;
|
|
51
|
-
|
|
52
118
|
try {
|
|
53
119
|
const {clipboard} = require('electron');
|
|
54
120
|
const found = hasImage(clipboard);
|
|
@@ -57,18 +123,12 @@ exports.decorateTerm = (Term, {React}) => {
|
|
|
57
123
|
e.preventDefault();
|
|
58
124
|
e.stopPropagation();
|
|
59
125
|
|
|
60
|
-
const save = found.type === 'bitmap'
|
|
61
|
-
? saveBitmap(clipboard)
|
|
62
|
-
: downloadImage(found.src);
|
|
63
|
-
|
|
126
|
+
const save = found.type === 'bitmap' ? saveBitmap(clipboard) : downloadImage(found.src);
|
|
64
127
|
save.then((filepath) => {
|
|
65
|
-
if (filepath && this.props.onData)
|
|
66
|
-
this.props.onData(filepath);
|
|
67
|
-
}
|
|
128
|
+
if (filepath && this.props.onData) this.props.onData(filepath);
|
|
68
129
|
});
|
|
69
130
|
} catch (_) {}
|
|
70
131
|
};
|
|
71
|
-
|
|
72
132
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
73
133
|
}
|
|
74
134
|
|