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.
Files changed (2) hide show
  1. package/index.js +77 -17
  2. 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
- const filepath = SAVE_PATH;
25
- fs.writeFileSync(filepath, clipboard.readImage().toPNG());
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 filepath = SAVE_PATH;
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(filepath)));
37
+ file.on('finish', () => file.close(() => resolve(SAVE_PATH)));
39
38
  }).on('error', () => {
40
- fs.unlink(filepath, () => {});
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picosh",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
4
4
  "description": "Hyper plugin: paste clipboard images as file paths",
5
5
  "main": "index.js",
6
6
  "license": "MIT",