mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -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
|
# 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
|
2023.01.1
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ This is one of the core plugins in PyScript, which is active by default. With it
|
|||||||
|
|
||||||
## Configuration
|
## 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 |
|
| 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 |
|
| `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` |
|
| `"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
|
### Examples
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<py-config>
|
<py-config>
|
||||||
terminal = true
|
terminal = true
|
||||||
|
docked = false
|
||||||
</py-config>
|
</py-config>
|
||||||
|
|
||||||
<py-script>
|
<py-script>
|
||||||
|
|||||||
@@ -6,8 +6,25 @@ import { getLogger } from '../logger';
|
|||||||
import { type Stdio } from '../stdio';
|
import { type Stdio } from '../stdio';
|
||||||
import { InterpreterClient } from '../interpreter_client';
|
import { InterpreterClient } from '../interpreter_client';
|
||||||
|
|
||||||
|
type AppConfigStyle = AppConfig & { terminal?: boolean | 'auto'; docked?: boolean | 'docked' };
|
||||||
|
|
||||||
const logger = getLogger('py-terminal');
|
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 {
|
export class PyTerminalPlugin extends Plugin {
|
||||||
app: PyScriptApp;
|
app: PyScriptApp;
|
||||||
|
|
||||||
@@ -16,33 +33,24 @@ export class PyTerminalPlugin extends Plugin {
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(config: AppConfig & { terminal?: boolean | 'auto' }) {
|
configure(config: AppConfigStyle) {
|
||||||
// validate the terminal config and handle default values
|
// validate the terminal config and handle default values
|
||||||
const t = config.terminal;
|
validate(config, 'terminal', 'auto');
|
||||||
if (t !== undefined && t !== true && t !== false && t !== 'auto') {
|
validate(config, 'docked', 'docked');
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeLaunch(config: AppConfig & { terminal?: boolean | 'auto' }) {
|
beforeLaunch(config: AppConfigStyle) {
|
||||||
// if config.terminal is "yes" or "auto", let's add a <py-terminal> to
|
// if config.terminal is "yes" or "auto", let's add a <py-terminal> to
|
||||||
// the document, unless it's already present.
|
// the document, unless it's already present.
|
||||||
const t = config.terminal;
|
const { terminal: t, docked: d } = config;
|
||||||
if (t === true || t === 'auto') {
|
const auto = t === true || t === 'auto';
|
||||||
if (document.querySelector('py-terminal') === null) {
|
const docked = d === true || d === 'docked';
|
||||||
logger.info('No <py-terminal> found, adding one');
|
if (auto && document.querySelector('py-terminal') === null) {
|
||||||
const termElem = document.createElement('py-terminal');
|
logger.info('No <py-terminal> found, adding one');
|
||||||
if (t === 'auto') termElem.setAttribute('auto', '');
|
const termElem = document.createElement('py-terminal');
|
||||||
document.body.appendChild(termElem);
|
if (auto) termElem.setAttribute('auto', '');
|
||||||
}
|
if (docked) termElem.setAttribute('docked', '');
|
||||||
|
document.body.appendChild(termElem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +101,10 @@ function make_PyTerminal(app: PyScriptApp) {
|
|||||||
this.autoShowOnNextLine = false;
|
this.autoShowOnNextLine = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isDocked()) {
|
||||||
|
this.classList.add('py-terminal-docked');
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Registering stdio listener');
|
logger.info('Registering stdio listener');
|
||||||
app.registerStdioListener(this);
|
app.registerStdioListener(this);
|
||||||
}
|
}
|
||||||
@@ -101,9 +113,16 @@ function make_PyTerminal(app: PyScriptApp) {
|
|||||||
return this.hasAttribute('auto');
|
return this.hasAttribute('auto');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDocked() {
|
||||||
|
return this.hasAttribute('docked');
|
||||||
|
}
|
||||||
|
|
||||||
// implementation of the Stdio interface
|
// implementation of the Stdio interface
|
||||||
stdout_writeline(msg: string) {
|
stdout_writeline(msg: string) {
|
||||||
this.outElem.innerText += msg + '\n';
|
this.outElem.innerText += msg + '\n';
|
||||||
|
if (this.isDocked()) {
|
||||||
|
this.scrollTop = this.scrollHeight;
|
||||||
|
}
|
||||||
if (this.autoShowOnNextLine) {
|
if (this.autoShowOnNextLine) {
|
||||||
this.classList.remove('py-terminal-hidden');
|
this.classList.remove('py-terminal-hidden');
|
||||||
this.autoShowOnNextLine = false;
|
this.autoShowOnNextLine = false;
|
||||||
|
|||||||
@@ -330,3 +330,20 @@ textarea {
|
|||||||
.py-terminal-hidden {
|
.py-terminal-hidden {
|
||||||
display: none;
|
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")
|
term = self.page.locator("py-terminal")
|
||||||
assert term.count() == 0
|
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