Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 05db532

Browse files
committed
feat: plugin install should offer a spinner
Fixes #3024
1 parent b940c63 commit 05db532

File tree

10 files changed

+228
-25
lines changed

10 files changed

+228
-25
lines changed

package-lock.json

Lines changed: 59 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/main/headless-support.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ export const streamTo = async () => {
3535
debug('streaming response', response)
3636
print(response)
3737
debug('streaming response2')
38+
return Promise.resolve()
3839
}
3940
}

packages/core/src/models/streamable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ import { CustomSpec } from '../webapp/views/sidecar'
2121
export type Streamable = SimpleEntity | Table | MultiTable | CustomSpec | MixedResponse
2222
export default Streamable
2323

24-
export type Stream = (response: Streamable, killLine?: boolean) => void
24+
export type Stream = (response: Streamable, killLine?: boolean) => Promise<void>
2525

2626
export type StreamableFactory = () => Promise<Stream>

packages/core/src/webapp/print.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { setStatus, Status } from './status'
2323
import { popupListen } from './listen'
2424
import { SidecarMode as Mode } from './bottom-stripe'
2525
import { isPopup } from './popup-core'
26+
import { scrollIntoView } from './scroll'
2627
import { renderPopupContent, createPopupContentContainer } from './popup'
2728

2829
import { formatTable } from './views/table'
@@ -109,13 +110,16 @@ const printTable = async (
109110
*
110111
*/
111112
export const streamTo = (tab: Tab, block: Element): Stream => {
112-
const resultDom = block.querySelector('.repl-result') as HTMLElement
113-
// so we can scroll this into view as streaming output arrives
113+
const container = block.querySelector('.repl-output') as HTMLElement
114+
const resultDom = container ? document.createElement('div') : (block.querySelector('.repl-result') as HTMLElement)
115+
if (container) {
116+
container.classList.add('repl-result-has-content')
117+
container.insertBefore(resultDom, container.childNodes[0])
118+
}
114119

115120
let previousLine: HTMLElement
116-
return (response: Streamable, killLine = false) => {
117-
// debug('stream', response)
118-
121+
return (response: Streamable, killLine = false): Promise<void> => {
122+
// debug('stream', response, killLine)
119123
resultDom.setAttribute('data-stream', 'data-stream')
120124
;(resultDom.parentNode as HTMLElement).classList.add('result-vertical')
121125

@@ -138,27 +142,31 @@ export const streamTo = (tab: Tab, block: Element): Stream => {
138142
return formatPart(_, para)
139143
})
140144
} else if (isHTML(response)) {
145+
response.classList.add('repl-result-like')
141146
previousLine = response
142147
resultDom.appendChild(previousLine)
143148
} else if (isMultiTable(response)) {
144149
response.tables.forEach(async _ => printTable(tab, _, resultDom))
145150
const br = document.createElement('br')
146151
resultDom.appendChild(br)
147152
} else if (isTable(response)) {
148-
await printTable(tab, response, resultDom)
153+
const wrapper = document.createElement('div')
154+
wrapper.classList.add('repl-result')
155+
resultDom.appendChild(wrapper)
156+
await printTable(tab, response, wrapper)
149157
} else if (isCustomSpec(response)) {
150158
showCustom(tab, response, {})
151159
} else {
152160
previousLine = document.createElement('pre')
153-
previousLine.classList.add('streaming-output')
161+
previousLine.classList.add('streaming-output', 'repl-result-like')
154162
previousLine.innerText = isMessageBearingEntity(response) ? response.message : response.toString()
155163
resultDom.appendChild(previousLine)
156164
}
157165
}
158166

159-
return formatPart(response, resultDom)
160-
161-
// scrollIntoView({ when: 0, element: spinner })
167+
return formatPart(response, resultDom).then(() => {
168+
scrollIntoView({ element: resultDom, when: 0 })
169+
})
162170
}
163171
}
164172

packages/core/web/css/top-tab-stripe.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ body.subwindow:not(.sidecar-is-minimized) .left-tab-stripe {
119119
body.not-electron .repl-input {
120120
padding-left: 0.375rem;
121121
}
122-
.repl .repl-block:not(.processing) .result-as-table .repl-result:not(:empty) {
122+
.repl .repl-block:not(.processing) .result-as-table .repl-result:not(:empty):not(.result-vertical) {
123123
padding: 0.625em 1.25em 0;
124124
}
125125
.repl-prompt-right-elements {

packages/core/web/css/ui.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ body/*:not(.sidecar-full-screen)*/ repl.sidecar-visible .repl-selection, body/*.
384384
margin: 0 !important;
385385
}
386386
.repl-result,
387+
.repl-result-like,
387388
.repl-input input,
388389
.repl-input-like {
389390
font-size: 0.875em;

plugins/plugin-manager/i18n/resources_en_US.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"list commands offered by an installed shell plugin": "list commands offered by an installed shell plugin",
44
"for advanced use: recompile plugin registry": "for advanced use: recompile plugin registry",
55
"offered commands": "offered commands",
6+
"Preparing to install": "Preparing to install {0}",
7+
"Install dependencies": "Installing dependencies",
8+
"Setup": "Setup",
69
"Install plugin": "Install plugin",
710
"install a plugin": "install a plugin",
811
"a simple example plugin": "a simple example plugin",

plugins/plugin-manager/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"main": "dist/index.js",
2323
"dependencies": {
2424
"fs-extra": "8.1.0",
25+
"ora": "4.0.2",
2526
"tmp": "0.1.0",
2627
"which": "1.3.1"
2728
},
@@ -35,6 +36,6 @@
3536
},
3637
"gitHead": "89de9f78e8a1a2bdd29d2e17d7c608ab006c6d32",
3738
"devDependencies": {
38-
"@types/which": "^1.3.2"
39+
"@types/which": "1.3.2"
3940
}
4041
}

plugins/plugin-manager/src/controller/install.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Errors from '@kui-shell/core/api/errors'
2424
import { i18n } from '@kui-shell/core/api/i18n'
2525
import Settings from '@kui-shell/core/api/settings'
2626

27+
import OraStream from '../util/ora'
2728
import locateNpm from '../util/locate-npm'
2829

2930
const strings = i18n('plugin-manager')
@@ -50,11 +51,11 @@ const usage: Errors.UsageModel = {
5051
* This is the command handler for `plugin install`
5152
*
5253
*/
53-
const doInstall = async ({ argvNoOptions, REPL }: Commands.Arguments) => {
54-
debug('command execution started')
54+
const doInstall = async (args: Commands.Arguments) => {
55+
const { argvNoOptions, REPL } = args
56+
const name = argvNoOptions[argvNoOptions.indexOf('install') + 1]
5557

56-
argvNoOptions = argvNoOptions.slice(argvNoOptions.indexOf('install') + 1)
57-
const name = argvNoOptions.shift()
58+
const spinner = await new OraStream().init(args)
5859

5960
const rootDir = Settings.userDataDir()
6061
const pluginHome = join(rootDir, 'plugins')
@@ -73,6 +74,7 @@ const doInstall = async ({ argvNoOptions, REPL }: Commands.Arguments) => {
7374
const { npm } = resolved
7475

7576
// npm init
77+
await spinner.next(strings('Setup'))
7678
await new Promise((resolve, reject) => {
7779
execFile(npm, ['init', '-y'], { cwd: pluginHome }, (error, stdout, stderr) => {
7880
if (error) {
@@ -90,8 +92,9 @@ const doInstall = async ({ argvNoOptions, REPL }: Commands.Arguments) => {
9092
})
9193

9294
// npm install
95+
await spinner.next(strings('Installing dependencies'))
9396
await new Promise((resolve, reject) => {
94-
const args = ['install', name, '--prod', '--no-package-lock']
97+
const args = ['install', name, '--prod', '--no-package-lock', '--loglevel', 'info']
9598
debug('npm install args', args)
9699
const sub = spawn(npm, args, {
97100
cwd: pluginHome
@@ -113,6 +116,7 @@ const doInstall = async ({ argvNoOptions, REPL }: Commands.Arguments) => {
113116
return reject(error)
114117
} else {
115118
debug(error)
119+
spinner.text = error
116120
}
117121
})
118122

@@ -131,8 +135,12 @@ const doInstall = async ({ argvNoOptions, REPL }: Commands.Arguments) => {
131135
})
132136
})
133137

138+
await spinner.next(strings('Compiling'), strings('Installing dependencies'))
134139
await REPL.qexec('plugin compile')
135140

141+
await spinner.next(strings('Successfully installed. Here are your new commands:'))
142+
await spinner.stop(true)
143+
136144
return REPL.qexec(`plugin commands ${name}`)
137145
}
138146

0 commit comments

Comments
 (0)