8000 Docked auto py-terminal (#1284) · ocdex/pyscript@e10d055 · GitHub
[go: up one dir, main page]

Skip to content

Commit e10d055

Browse files
Docked auto py-terminal (pyscript#1284)
1 parent 716254e commit e10d055

File tree

5 files changed

+99
-23
lines changed

5 files changed

+99
-23
lines changed

docs/changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Release Notes
22

3+
2023.XX.X
4+
=========
5+
6+
Features
7+
--------
8+
9+
- 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.
10+
11+
312
2023.01.1
413
=========
514

docs/reference/plugins/py-terminal.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,35 @@ This is one of the core plugins in PyScript, which is active by default. With it
44

55
## Configuration
66

7-
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`.
7+
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`, together with the `docked` one.
8+
9+
For the **terminal** field, these are the values:
810

911
| value | description |
1012
|-------|-------------|
1113
| `false` | Don't add `<py-terminal>` to the page |
1214
| `true` | Automatically add a `<py-terminal>` to the page |
1315
| `"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` |
1416

17+
For the **docked** field, these are the values:
18+
19+
| value | description |
20+
|-------|-------------|
21+
| `false` | Don't dock `<py-terminal>` to the page |
22+
| `true` | Automatically dock a `<py-terminal>` to the page |
23+
| `"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 |
24+
25+
Please note that **docked** mode is currently used as default only when `terminal="auto"`, or *terminal* default, is used.
26+
27+
In all other cases it's up to the user decide if a terminal should be docked or not.
28+
29+
1530
### Examples
1631

1732
```html
1833
<py-config>
1934
terminal = true
35+
docked = false
2036
</py-config>
2137

2238
<py-script>

pyscriptjs/src/plugins/pyterminal.ts

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,25 @@ import { getLogger } from '../logger';
66
import { type Stdio } from '../stdio';
77
import { InterpreterClient } from '../interpreter_client';
88

9+
type AppConfigStyle = AppConfig & { terminal?: boolean | 'auto'; docked?: boolean | 'docked' };
10+
911
const logger = getLogger('py-terminal');
1012

13+
const validate = (config: AppConfigStyle, name: string, default_: string) => {
14+
const value = config[name] as undefined | boolean | string;
15+
if (value !== undefined && value !== true && value !== false && value !== default_) {
16+
const got = JSON.stringify(value);
17+
throw new UserError(
18+
ErrorCode.BAD_CONFIG,
19+
`Invalid value for config.${name}: the only accepted` +
20+
`values are true, false and "${default_}", got "${got}".`,
21+
);
22+
}
23+
if (value === undefined) {
24+
config[name] = default_;
25+
}
26+
};
27+
1128
export class PyTerminalPlugin extends Plugin {
1229
app: PyScriptApp;
1330

@@ -16,33 +33,24 @@ export class PyTerminalPlugin extends Plugin {
1633
this.app = app;
1734
}
1835

19-
configure(config: AppConfig & { terminal?: boolean | 'auto' }) {
36+
configure(config: AppConfigStyle) {
2037
// validate the terminal config and handle default values
21-
const t = config.terminal;
22-
if (t !== undefined && t !== true && t !== false && t !== 'auto') {
23-
const got = JSON.stringify(t);
24-
throw new UserError(
25-
ErrorCode.BAD_CONFIG,
26-
'Invalid value for config.terminal: the only accepted' +
27-
`values are true, false and "auto", got "${got}".`,
28-
);
29-
}
30-
if (t === undefined) {
31-
config.terminal = 'auto'; // default value
32-
}
38+
validate(config, 'terminal', 'auto');
39+
validate(config, 'docked', 'docked');
3340
}
3441

35-
beforeLaunch(config: AppConfig & { terminal?: boolean | 'auto' }) {
42+
beforeLaunch(config: AppConfigStyle) {
3643
// if config.terminal is "yes" or "auto", let's add a <py-terminal> to
3744
// the document, unless it's already present.
38-
const t = config.terminal;
39-
if (t === true || t === 'auto') {
40-
if (document.querySelector('py-terminal') === null) {
41-
logger.info('No <py-terminal> found, adding one');
42-
const termElem = document.createElement('py-terminal');
43-
if (t === 'auto') termElem.setAttribute('auto', '');
44-
document.body.appendChild(termElem);
45-
}
45+
const { terminal: t, docked: d } = config;
46+
const auto = t === true || t === 'auto';
47+
const docked = d === true || d === 'docked';
48+
if (auto && document.querySelector('py-terminal') === null) {
49+
logger.info('No <py-terminal> found, adding one');
50+
const termElem = document.createElement('py-terminal');
51+
if (auto) termElem.setAttribute('auto', '');
52+
if (docked) termElem.setAttribute('docked', '');
53+
document.body.appendChild(termElem);
4654
}
4755
}
4856

@@ -93,6 +101,10 @@ function make_PyTerminal(app: PyScriptApp) {
93101
this.autoShowOnNextLine = false;
94102
}
95103

104+
if (this.isDocked()) {
105+
this.classList.add('py-terminal-docked');
106+
}
107+
96108
logger.info('Registering stdio listener');
97109
app.registerStdioListener(this);
98110
}
@@ -101,9 +113,16 @@ function make_PyTerminal(app: PyScriptApp) {
101113
return this.hasAttribute('auto');
102114
}
103115

116+
isDocked() {
117+
return this.hasAttribute('docked');
118+
}
119+
104120
// implementation of the Stdio interface
105121
stdout_writeline(msg: string) {
106122
this.outElem.innerText += msg + '\n';
123+
if (this.isDocked()) {
124+
this.scrollTop = this.scrollHeight;
125+
}
107126
if (this.autoShowOnNextLine) {
108127
this.classList.remove('py-terminal-hidden');
109128
this.autoShowOnNextLine = false;

pyscriptjs/src/styles/pyscript_base.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,20 @@ textarea {
330330
.py-terminal-hidden {
331331
display: none;
332332
}
333+
334+
/* avoid changing the page layout when the terminal is docked and hidden */
335+
html:has(py-terminal[docked]:not(py-terminal[docked].py-terminal-hidden)) {
336+
padding-bottom: 40vh;
337+
}
338+
339+
py-terminal[docked] {
340+
position: fixed;
341+
bottom: 0;
342+
width: 100vw;
343+
max-height: 40vh;
344+
overflow: auto;
345+
}
346+
347+
py-terminal[docked] .py-terminal {
348+
margin: 0;
349+
}

pyscriptjs/tests/integration/test_py_terminal.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,18 @@ def test_config_false(self):
131131
)
132132
term = self.page.locator("py-terminal")
133133
assert term.count() == 0
134+
135+
def test_config_docked(self):
136+
"""
137+
config.docked == "docked" is also the default: a <py-terminal auto docked> is
138+
automatically added to the page
139+
"""
140+
self.pyscript_run(
141+
"""
142+
<button id="my-button" py-onClick="print('hello world')">Click me</button>
143+
"""
144+
)
145+
term = self.page.locator("py-terminal")
146+
self.page.locator("button").click()
147+
expect(term).to_be_visible()
148+
assert term.get_attribute("docked") == ""

0 commit comments

Comments
 (0)
0