Skip to content

Commit 95f07aa

Browse files
committed
add output.page()
1 parent 6b4bd0c commit 95f07aa

File tree

9 files changed

+175
-7
lines changed

9 files changed

+175
-7
lines changed

docs/spec.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,23 @@ The ``spec`` fields of ``download`` commands:
395395
* name: str, File name when downloading
396396
* content: str, File content in base64 encoding.
397397

398+
open_page
399+
^^^^^^^^^^^^^^^
400+
Open new page
401+
402+
The ``spec`` fields of ``new_page`` commands:
403+
404+
* page_id: str, page id to be created
405+
406+
close_page
407+
^^^^^^^^^^^^^^^
408+
Close a page
409+
410+
The ``spec`` fields of ``close_page`` commands:
411+
412+
* page_id: str, page id to be closed
413+
414+
398415
Event
399416
------------
400417

pywebio/io_ctrl.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,11 @@ def inner(*args, **kwargs):
214214

215215
def send_msg(cmd, spec=None, task_id=None):
216216
msg = dict(command=cmd, spec=spec, task_id=task_id or get_current_task_id())
217-
get_current_session().send_task_command(msg)
217+
s = get_current_session()
218+
page = s.get_page_id()
219+
if page is not None:
220+
msg['page'] = page
221+
s.send_task_command(msg)
218222

219223

220224
def single_input_kwargs(single_input_return):

pywebio/output.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
| | `popup`:sup:`*†` | Show popup |
7373
| +---------------------------+------------------------------------------------------------+
7474
| | `close_popup` | Close the current popup window. |
75+
| +---------------------------+------------------------------------------------------------+
76+
| | `page` | Open a new page. |
7577
+--------------------+---------------------------+------------------------------------------------------------+
7678
| Layout and Style | `put_row`:sup:`*†` | Use row layout to output content |
7779
| +---------------------------+------------------------------------------------------------+
@@ -231,7 +233,7 @@
231233
'put_table', 'put_buttons', 'put_image', 'put_file', 'PopupSize', 'popup', 'put_button',
232234
'close_popup', 'put_widget', 'put_collapse', 'put_link', 'put_scrollable', 'style', 'put_column',
233235
'put_row', 'put_grid', 'span', 'put_processbar', 'set_processbar', 'put_loading',
234-
'output', 'toast', 'get_scope', 'put_info', 'put_error', 'put_warning', 'put_success']
236+
'output', 'toast', 'get_scope', 'put_info', 'put_error', 'put_warning', 'put_success', 'page']
235237

236238

237239
# popup size
@@ -1809,3 +1811,70 @@ async def coro_wrapper(*args, **kwargs):
18091811
return coro_wrapper
18101812
else:
18111813
return wrapper
1814+
1815+
1816+
def page(func=None):
1817+
"""
1818+
Open a page. Can be used as context manager and decorator.
1819+
1820+
:Usage:
1821+
1822+
::
1823+
1824+
with page() as scope_name:
1825+
input()
1826+
put_xxx()
1827+
1828+
@page() # or @page
1829+
def content():
1830+
input()
1831+
put_xxx()
1832+
"""
1833+
1834+
if func is None:
1835+
return page_()
1836+
return page_()(func)
1837+
1838+
1839+
class page_:
1840+
page_id: str
1841+
1842+
def __enter__(self):
1843+
self.page_id = random_str(10)
1844+
send_msg('open_page', dict(page_id=self.page_id))
1845+
get_current_session().push_page(self.page_id)
1846+
1847+
def __exit__(self, exc_type, exc_val, exc_tb):
1848+
"""
1849+
If this method returns True, it means that the context manager can handle the exception,
1850+
so that the with statement terminates the propagation of the exception
1851+
"""
1852+
get_current_session().pop_page()
1853+
send_msg('close_page', dict(page_id=self.page_id))
1854+
1855+
# todo: catch Page Close Exception
1856+
return False # Propagate Exception
1857+
1858+
def __call__(self, func):
1859+
"""decorator implement"""
1860+
1861+
@wraps(func)
1862+
def wrapper(*args, **kwargs):
1863+
self.__enter__()
1864+
try:
1865+
return func(*args, **kwargs)
1866+
finally:
1867+
self.__exit__(None, None, None)
1868+
1869+
@wraps(func)
1870+
async def coro_wrapper(*args, **kwargs):
1871+
self.__enter__()
1872+
try:
1873+
return await func(*args, **kwargs)
1874+
finally:
1875+
self.__exit__(None, None, None)
1876+
1877+
if iscoroutinefunction(func):
1878+
return coro_wrapper
1879+
else:
1880+
return wrapper

pywebio/session/base.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def __init__(self, session_info):
6262
self.internal_save = dict(info=session_info) # some session related info, just for internal used
6363
self.save = {} # underlying implement of `pywebio.session.data`
6464
self.scope_stack = defaultdict(lambda: ['ROOT']) # task_id -> scope栈
65+
self.page_stack = defaultdict(lambda: []) # task_id -> page id
6566

6667
self.deferred_functions = [] # 会话结束时运行的函数
6768
self._closed = False
@@ -94,6 +95,26 @@ def push_scope(self, name):
9495
task_id = type(self).get_current_task_id()
9596
self.scope_stack[task_id].append(name)
9697

98+
def get_page_id(self):
99+
task_id = type(self).get_current_task_id()
100+
if task_id not in self.page_stack:
101+
return None
102+
try:
103+
return self.page_stack[task_id][-1]
104+
except IndexError:
105+
return None
106+
107+
def pop_page(self):
108+
task_id = type(self).get_current_task_id()
109+
try:
110+
return self.page_stack[task_id].pop()
111+
except IndexError:
112+
raise ValueError("Internal Error: No page to exit") from None
113+
114+
def push_page(self, page_id):
115+
task_id = type(self).get_current_task_id()
116+
self.page_stack[task_id].append(page_id)
117+
97118
def send_task_command(self, command):
98119
raise NotImplementedError
99120

webiojs/src/handlers/base.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Command, Session} from "../session";
2+
import {DeliverMessage} from "../models/page";
23

34

45
export interface CommandHandler {
@@ -35,10 +36,13 @@ export class CommandDispatcher {
3536
}
3637

3738
dispatch_message(msg: Command): boolean {
38-
if (msg.command in this.command2handler) {
39+
if (msg.page !== undefined && msg.page) {
40+
DeliverMessage(msg);
41+
} else if (msg.command in this.command2handler) {
3942
this.command2handler[msg.command].handle_message(msg);
40-
return true;
43+
} else {
44+
return false
4145
}
42-
return false
46+
return true;
4347
}
4448
}

webiojs/src/handlers/page.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {Command} from "../session";
2+
import {CommandHandler} from "./base";
3+
import {ClosePage, OpenPage} from "../models/page";
4+
5+
export class PageHandler implements CommandHandler {
6+
accept_command: string[] = ['open_page', 'close_page'];
7+
8+
constructor() {
9+
}
10+
11+
handle_message(msg: Command) {
12+
if (msg.command === 'open_page') {
13+
OpenPage(msg.spec.page_id);
14+
} else if (msg.command === 'close_page') {
15+
ClosePage(msg.spec.page_id);
16+
}
17+
}
18+
}

webiojs/src/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {ToastHandler} from "./handlers/toast";
1111
import {EnvSettingHandler} from "./handlers/env";
1212
import {PinHandler} from "./handlers/pin";
1313
import {customMessage} from "./i18n"
14+
import {PageHandler} from "./handlers/page";
1415

1516
// 获取后端API的绝对地址
1617
function backend_absaddr(addr: string) {
@@ -40,9 +41,10 @@ function set_up_session(webio_session: Session, output_container_elem: JQuery, i
4041
let download_ctrl = new DownloadHandler();
4142
let toast_ctrl = new ToastHandler();
4243
let env_ctrl = new EnvSettingHandler();
44+
let page_ctrl = new PageHandler();
4345

4446
let dispatcher = new CommandDispatcher(output_ctrl, input_ctrl, popup_ctrl, session_ctrl,
45-
script_ctrl, download_ctrl, toast_ctrl, env_ctrl, pin_ctrl);
47+
script_ctrl, download_ctrl, toast_ctrl, env_ctrl, pin_ctrl, page_ctrl);
4648

4749
webio_session.on_server_message((msg: Command) => {
4850
try {

webiojs/src/models/page.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// page id to page window reference
2+
import {Command, SubPageSession} from "../session";
3+
import {LazyPromise} from "../utils";
4+
5+
let subpages: { [page_id: string]: Window } = {};
6+
7+
8+
export function OpenPage(page_id: string) {
9+
if (page_id in subpages)
10+
throw `Can't open page, the page id "${page_id}" is duplicated`;
11+
subpages[page_id] = window.open(window.location.href);
12+
13+
// the `_pywebio_page` will be resolved in new opened page in `SubPageSession.start_session()`
14+
// @ts-ignore
15+
subpages[page_id]._pywebio_page = new LazyPromise()
16+
}
17+
18+
export function ClosePage(page_id: string) {
19+
if (!(page_id in subpages))
20+
throw `Can't close page, the page (id "${page_id}") is not found`;
21+
subpages[page_id].close()
22+
}
23+
24+
export function DeliverMessage(msg: Command) {
25+
if (!(msg.page in subpages))
26+
throw `Can't deliver message, the page (id "${msg.page}") is not found`;
27+
// @ts-ignore
28+
subpages[msg.page]._pywebio_page.promise.then((page: SubPageSession) => {
29+
msg.page = undefined;
30+
page.server_message(msg);
31+
});
32+
}

webiojs/src/session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {t} from "./i18n";
55
export interface Command {
66
command: string
77
task_id: string
8+
page: string,
89
spec: any
910
}
1011

@@ -89,7 +90,7 @@ export class SubPageSession implements Session {
8990
safe_poprun_callbacks(this._session_create_callbacks, 'session_create_callback');
9091

9192
// @ts-ignore
92-
window._pywebio_page.promise.resolve(this);
93+
window._pywebio_page.resolve(this);
9394
};
9495

9596
// called by opener, transfer command to this session

0 commit comments

Comments
 (0)