mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 10:47:35 -05:00
Docked auto py-terminal (#1284)
This commit is contained in:
committed by
GitHub
parent
716254e655
commit
e10d055453
@@ -1,5 +1,14 @@
|
||||
# Release Notes
|
||||
|
||||
2023.XX.X
|
||||
=========
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
|
||||
|
||||
|
||||
2023.01.1
|
||||
=========
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ This is one of the core plugins in PyScript, which is active by default. With it
|
||||
|
||||
## Configuration
|
||||
|
||||
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`.
|
||||
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`, together with the `docked` one.
|
||||
|
||||
For the **terminal** field, these are the values:
|
||||
|
||||
| value | description |
|
||||
|-------|-------------|
|
||||
@@ -12,11 +14,25 @@ You can control how `<py-terminal>` behaves by setting the value of the `termin
|
||||
| `true` | Automatically add a `<py-terminal>` to the page |
|
||||
| `"auto"` | This is the default. Automatically add a `<py-terminal auto>`, to the page. The terminal is initially hidden and automatically shown as soon as something writes to `stdout` and/or `stderr` |
|
||||
|
||||
For the **docked** field, these are the values:
|
||||
|
||||
| value | description |
|
||||
|-------|-------------|
|
||||
| `false` | Don't dock `<py-terminal>` to the page |
|
||||
| `true` | Automatically dock a `<py-terminal>` to the page |
|
||||
| `"docked"` | This is the default. Automatically add a `<py-terminal docked>`, to the page. The terminal, once visible, is automatically shown at the bottom of the page, covering the width of such page |
|
||||
|
||||
Please note that **docked** mode is currently used as default only when `terminal="auto"`, or *terminal* default, is used.
|
||||
|
||||
In all other cases it's up to the user decide if a terminal should be docked or not.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
terminal = true
|
||||
docked = false
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
|
||||
@@ -6,8 +6,25 @@ import { getLogger } from '../logger';
|
||||
import { type Stdio } from '../stdio';
|
||||
import { InterpreterClient } from '../interpreter_client';
|
||||
|
||||
type AppConfigStyle = AppConfig & { terminal?: boolean | 'auto'; docked?: boolean | 'docked' };
|
||||
|
||||
const logger = getLogger('py-terminal');
|
||||
|
||||
const validate = (config: AppConfigStyle, name: string, default_: string) => {
|
||||
const value = config[name] as undefined | boolean | string;
|
||||
if (value !== undefined && value !== true && value !== false && value !== default_) {
|
||||
const got = JSON.stringify(value);
|
||||
throw new UserError(
|
||||
ErrorCode.BAD_CONFIG,
|
||||
`Invalid value for config.${name}: the only accepted` +
|
||||
`values are true, false and "${default_}", got "${got}".`,
|
||||
);
|
||||
}
|
||||
if (value === undefined) {
|
||||
config[name] = default_;
|
||||
}
|
||||
};
|
||||
|
||||
export class PyTerminalPlugin extends Plugin {
|
||||
app: PyScriptApp;
|
||||
|
||||
@@ -16,35 +33,26 @@ export class PyTerminalPlugin extends Plugin {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
configure(config: AppConfig & { terminal?: boolean | 'auto' }) {
|
||||
configure(config: AppConfigStyle) {
|
||||
// validate the terminal config and handle default values
|
||||
const t = config.terminal;
|
||||
if (t !== undefined && t !== true && t !== false && t !== 'auto') {
|
||||
const got = JSON.stringify(t);
|
||||
throw new UserError(
|
||||
ErrorCode.BAD_CONFIG,
|
||||
'Invalid value for config.terminal: the only accepted' +
|
||||
`values are true, false and "auto", got "${got}".`,
|
||||
);
|
||||
}
|
||||
if (t === undefined) {
|
||||
config.terminal = 'auto'; // default value
|
||||
}
|
||||
validate(config, 'terminal', 'auto');
|
||||
validate(config, 'docked', 'docked');
|
||||
}
|
||||
|
||||
beforeLaunch(config: AppConfig & { terminal?: boolean | 'auto' }) {
|
||||
beforeLaunch(config: AppConfigStyle) {
|
||||
// if config.terminal is "yes" or "auto", let's add a <py-terminal> to
|
||||
// the document, unless it's already present.
|
||||
const t = config.terminal;
|
||||
if (t === true || t === 'auto') {
|
||||
if (document.querySelector('py-terminal') === null) {
|
||||
const { terminal: t, docked: d } = config;
|
||||
const auto = t === true || t === 'auto';
|
||||
const docked = d === true || d === 'docked';
|
||||
if (auto && document.querySelector('py-terminal') === null) {
|
||||
logger.info('No <py-terminal> found, adding one');
|
||||
const termElem = document.createElement('py-terminal');
|
||||
if (t === 'auto') termElem.setAttribute('auto', '');
|
||||
if (auto) termElem.setAttribute('auto', '');
|
||||
if (docked) termElem.setAttribute('docked', '');
|
||||
document.body.appendChild(termElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterSetup(_interpreter: InterpreterClient) {
|
||||
// the Python interpreter has been initialized and we are ready to
|
||||
@@ -93,6 +101,10 @@ function make_PyTerminal(app: PyScriptApp) {
|
||||
this.autoShowOnNextLine = false;
|
||||
}
|
||||
|
||||
if (this.isDocked()) {
|
||||
this.classList.add('py-terminal-docked');
|
||||
}
|
||||
|
||||
logger.info('Registering stdio listener');
|
||||
app.registerStdioListener(this);
|
||||
}
|
||||
@@ -101,9 +113,16 @@ function make_PyTerminal(app: PyScriptApp) {
|
||||
return this.hasAttribute('auto');
|
||||
}
|
||||
|
||||
isDocked() {
|
||||
return this.hasAttribute('docked');
|
||||
}
|
||||
|
||||
// implementation of the Stdio interface
|
||||
stdout_writeline(msg: string) {
|
||||
this.outElem.innerText += msg + '\n';
|
||||
if (this.isDocked()) {
|
||||
this.scrollTop = this.scrollHeight;
|
||||
}
|
||||
if (this.autoShowOnNextLine) {
|
||||
this.classList.remove('py-terminal-hidden');
|
||||
this.autoShowOnNextLine = false;
|
||||
|
||||
@@ -330,3 +330,20 @@ textarea {
|
||||
.py-terminal-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* avoid changing the page layout when the terminal is docked and hidden */
|
||||
html:has(py-terminal[docked]:not(py-terminal[docked].py-terminal-hidden)) {
|
||||
padding-bottom: 40vh;
|
||||
}
|
||||
|
||||
py-terminal[docked] {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
max-height: 40vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
py-terminal[docked] .py-terminal {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -131,3 +131,18 @@ class TestPyTerminal(PyScriptTest):
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
assert term.count() == 0
|
||||
|
||||
def test_config_docked(self):
|
||||
"""
|
||||
config.docked == "docked" is also the default: a <py-terminal auto docked> is
|
||||
automatically added to the page
|
||||
"""
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<button id="my-button" py-onClick="print('hello world')">Click me</button>
|
||||
"""
|
||||
)
|
||||
term = self.page.locator("py-terminal")
|
||||
self.page.locator("button").click()
|
||||
expect(term).to_be_visible()
|
||||
assert term.get_attribute("docked") == ""
|
||||
|
||||
Reference in New Issue
Block a user