ipyslides 7.3.2__tar.gz → 7.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ipyslides-7.3.2 → ipyslides-7.3.4}/PKG-INFO +1 -1
- ipyslides-7.3.4/ipyslides/__version__.py +2 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/_layout.py +8 -2
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/_widgets.py +1 -1
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/export_html.py +2 -3
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/export_template.py +15 -9
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/intro.py +2 -2
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/navigation.py +5 -5
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/settings.py +35 -25
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/slide.py +15 -30
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/styles.py +34 -51
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/widgets.py +7 -11
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/core.py +11 -18
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/utils.py +14 -1
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/xmd.py +1 -1
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides.egg-info/PKG-INFO +1 -1
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides.egg-info/SOURCES.txt +1 -8
- ipyslides-7.3.2/ipyslides/__version__.py +0 -2
- ipyslides-7.3.2/ipyslides/_base/static/interaction.css +0 -469
- ipyslides-7.3.2/ipyslides/_base/static/interaction.js +0 -1013
- ipyslides-7.3.2/ipyslides/_base/static/laser.css +0 -31
- ipyslides-7.3.2/ipyslides/_base/static/laser.js +0 -100
- ipyslides-7.3.2/ipyslides/_base/static/notes.js +0 -122
- ipyslides-7.3.2/ipyslides/pkg_nbs/ips-demo.ipynb +0 -630
- ipyslides-7.3.2/ipyslides/pkg_nbs/ips-docs.ipynb +0 -582
- {ipyslides-7.3.2 → ipyslides-7.3.4}/LICENSE +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/README.md +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/__init__.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/__init__.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/_syntax.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/base.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/icons.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/_base/notes.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/dashlab.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/formatters.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/source.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides/writer.py +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides.egg-info/dependency_links.txt +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides.egg-info/requires.txt +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/ipyslides.egg-info/top_level.txt +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/setup.cfg +0 -0
- {ipyslides-7.3.2 → ipyslides-7.3.4}/setup.py +0 -0
|
@@ -48,7 +48,9 @@ def layout_css(accent_color, aspect):
|
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
".static-widget": {
|
|
51
|
-
"
|
|
51
|
+
"^, > *, .widget-html-content": {
|
|
52
|
+
"position": "static !important", # let objects flow out
|
|
53
|
+
},
|
|
52
54
|
},
|
|
53
55
|
".Warn::after": {
|
|
54
56
|
"display": "block",
|
|
@@ -131,6 +133,11 @@ def layout_css(accent_color, aspect):
|
|
|
131
133
|
"position": "absolute !important",
|
|
132
134
|
"^, .widget-html-content": {"line-height": "0 !important"},
|
|
133
135
|
"@media print": {"position": "fixed !important",},
|
|
136
|
+
"@media screen": { # avoid messing up print layout
|
|
137
|
+
"transform": "scale(min(var(--contentScale), 1)) !important", # scale footer to match content, but not more than 1
|
|
138
|
+
"transform-origin": "left bottom !important",
|
|
139
|
+
"transition": "transform 0.4s ease-in-out",
|
|
140
|
+
},
|
|
134
141
|
}, # other properties are set internally
|
|
135
142
|
"> .Build-Btn": {"visibility": "hidden !important",}, # hide build button by default
|
|
136
143
|
"^:has(.SlideArea.Stale) > .Build-Btn": {
|
|
@@ -328,7 +335,6 @@ def layout_css(accent_color, aspect):
|
|
|
328
335
|
"line-height": "1.5 !important",
|
|
329
336
|
},
|
|
330
337
|
"^, .widget-html-content": {
|
|
331
|
-
"--text-size": "14px !important",
|
|
332
338
|
"position": "static !important", # footer content should escape
|
|
333
339
|
},
|
|
334
340
|
},
|
|
@@ -23,7 +23,7 @@ class InteractionWidget(anywidget.AnyWidget):
|
|
|
23
23
|
_colors = traitlets.Dict(jupyter_colors).tag(sync=True) # for export
|
|
24
24
|
_parts = traitlets.Dict().tag(sync=True) # parts data for each slide, for js side use
|
|
25
25
|
_main_end = traitlets.Int(default_value=0).tag(sync=True) # last main slide index
|
|
26
|
-
|
|
26
|
+
_page_css = traitlets.Unicode('').tag(sync=True) # page size and margin for print/export
|
|
27
27
|
|
|
28
28
|
msg_topy = traitlets.Unicode('').tag(sync=True)
|
|
29
29
|
msg_tojs = traitlets.Unicode('').tag(sync=True)
|
|
@@ -28,7 +28,6 @@ _script = '''<script>
|
|
|
28
28
|
let scale = scaleH > scaleW ? scaleW : scaleH;
|
|
29
29
|
if (scale) { // Only add if not null or somethings
|
|
30
30
|
document.documentElement.style.setProperty('--contentScale',scale);
|
|
31
|
-
document.documentElement.style.setProperty('--paddingBottom',Number(__PADBTM__/scale) + "px");
|
|
32
31
|
};
|
|
33
32
|
};
|
|
34
33
|
window.dispatchEvent(new window.Event('resize')); // First time programatically
|
|
@@ -111,7 +110,7 @@ class _HhtmlExporter:
|
|
|
111
110
|
theme_kws["colors"] = self.main.widgets.iw._colors
|
|
112
111
|
|
|
113
112
|
dom_classes = tuple(self.main._box._dom_classes)
|
|
114
|
-
css_classes = [c for c in dom_classes if c.startswith('Slides')]
|
|
113
|
+
css_classes = [c for c in dom_classes if c.startswith('Slides') or c.startswith('has-')] # only export relevant classes
|
|
115
114
|
if self.main.uid in dom_classes:
|
|
116
115
|
css_classes.append(self.main.uid)
|
|
117
116
|
|
|
@@ -122,7 +121,7 @@ class _HhtmlExporter:
|
|
|
122
121
|
content = content,
|
|
123
122
|
script = _script,
|
|
124
123
|
css_class = ' '.join(css_classes),
|
|
125
|
-
|
|
124
|
+
page_css = self.main.html('style', self.main.widgets.iw._page_css).value,
|
|
126
125
|
)
|
|
127
126
|
|
|
128
127
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Template for building HTML from slides
|
|
2
2
|
|
|
3
|
-
def doc_html(code_css, style_css, content, script, css_class,
|
|
3
|
+
def doc_html(code_css, style_css, content, script, css_class, page_css = ''):
|
|
4
4
|
return f'''<!DOCTYPE html>
|
|
5
5
|
<html lang="en">
|
|
6
6
|
<head>
|
|
@@ -13,8 +13,9 @@ def doc_html(code_css, style_css, content, script, css_class, padding_bottom):
|
|
|
13
13
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
|
14
14
|
|
|
15
15
|
{style_css}
|
|
16
|
-
{slides_css
|
|
16
|
+
{slides_css}
|
|
17
17
|
{code_css}
|
|
18
|
+
{page_css}
|
|
18
19
|
|
|
19
20
|
<!-- Custom stylesheet, it must be in the same directory as the html file -->
|
|
20
21
|
<link rel="stylesheet" href="overrides.css">
|
|
@@ -50,7 +51,7 @@ def doc_html(code_css, style_css, content, script, css_class, padding_bottom):
|
|
|
50
51
|
</div>
|
|
51
52
|
</div>
|
|
52
53
|
</body>
|
|
53
|
-
{script
|
|
54
|
+
{script}
|
|
54
55
|
</html>
|
|
55
56
|
'''
|
|
56
57
|
|
|
@@ -94,6 +95,9 @@ section .SlideBox {
|
|
|
94
95
|
.SlidesWrapper .SlideLogo {
|
|
95
96
|
position: fixed !important;
|
|
96
97
|
line-height: 0 !important; /* suppress bad line-height here */
|
|
98
|
+
transform: scale(min(var(--contentScale), 1)) !important; /* scale footer to match content, but not more than 1 */
|
|
99
|
+
transform-origin: left bottom !important;
|
|
100
|
+
transition: transform 0.4s ease-in-out;
|
|
97
101
|
}
|
|
98
102
|
.SlidesWrapper::-webkit-scrollbar,
|
|
99
103
|
.SlidesWrapper::-webkit-scrollbar-button,
|
|
@@ -103,7 +107,7 @@ section .SlideBox {
|
|
|
103
107
|
|
|
104
108
|
.SlidesWrapper.Scrolling .slide-footer {
|
|
105
109
|
visibility: hidden !important;
|
|
106
|
-
transition:
|
|
110
|
+
transition: visibility 200ms ease-in;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
@media print {
|
|
@@ -137,12 +141,15 @@ section .SlideBox {
|
|
|
137
141
|
margin: 0 !important;
|
|
138
142
|
padding: 0 !important;
|
|
139
143
|
page-break-inside: avoid !important;
|
|
140
|
-
page-break-
|
|
144
|
+
page-break-before: always !important;
|
|
145
|
+
break-before: page !important;
|
|
146
|
+
break-inside: avoid !important;
|
|
141
147
|
overflow: hidden !important; /* otherwise it throws text to next page */
|
|
142
|
-
padding-bottom:
|
|
148
|
+
padding-bottom: var(--pad-footer, 23px) !important; /* for avoiding overflow to bottom,same as under SlideArea */
|
|
143
149
|
}
|
|
144
|
-
section:
|
|
145
|
-
page-break-
|
|
150
|
+
section:first-of-type {
|
|
151
|
+
page-break-before: avoid !important;
|
|
152
|
+
break-before: avoid !important;
|
|
146
153
|
}
|
|
147
154
|
section .SlideBox {
|
|
148
155
|
margin-bottom: 0 !important;
|
|
@@ -153,7 +160,6 @@ section .SlideBox {
|
|
|
153
160
|
position: static !important; /*override from document as printing absolute is issue */
|
|
154
161
|
overflow: hidden !important;
|
|
155
162
|
page-break-inside: avoid !important;
|
|
156
|
-
--paddingBottom: __PADBTM__px; /* fixed for printing, padding set in style_css */
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
</style>
|
|
@@ -123,7 +123,7 @@ Use `Save as PDF` option instead of Print PDF in browser to make links work in o
|
|
|
123
123
|
- PDF printing may not work as expected in all browsers/IDEs and tested only in JupyterLab.
|
|
124
124
|
- In case of issues with frames not displaying properly using `Ctrl + P`, export to HTML and then print.
|
|
125
125
|
- Make sure your presentation is clean for print. "Inline Notes" are only meant for personal reference or sharing slides with notes.
|
|
126
|
-
-
|
|
126
|
+
- If print shows blank/gray pages, adjust printer setting settings such as margin and it would resolve the issue. If not, export to HTML and then print.
|
|
127
127
|
|
|
128
128
|
**Printing from Exported HTML File**<br>
|
|
129
129
|
You can also get PDF from exported HTML file. Use `Save as PDF` and enable background graphics when printing to keep links working.
|
|
@@ -133,7 +133,7 @@ slides, use [alert! slides.alt /]. You can paste screenshots from system tool
|
|
|
133
133
|
into Clips GUI in side panel. On Linux, you need [alert! xclip /] or [alert! wl-paste /] installed.
|
|
134
134
|
|
|
135
135
|
::: note-tip
|
|
136
|
-
- You might want to
|
|
136
|
+
- You might want to flow content for export purpose. Use checkbox in settings panel to enable it.
|
|
137
137
|
- Print slides with notes by enabling `Inline Notes` option in settings panel before print/export. Notes appear at top to grab immediate attetion of the speaker.
|
|
138
138
|
- Use `Merge Parts` option in settings panel to show all parts of a page/slide as one page/slide.
|
|
139
139
|
"""
|
|
@@ -3,6 +3,8 @@ Author Notes: Classes in this module should only be instantiated in Slides class
|
|
|
3
3
|
and then provided to other classes via composition, not inheritance.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from ..utils import update_class
|
|
7
|
+
|
|
6
8
|
class Navigation:
|
|
7
9
|
def __init__(self, _instanceSlides):
|
|
8
10
|
"Instnace should be inside `Slides` class."
|
|
@@ -16,17 +18,15 @@ class Navigation:
|
|
|
16
18
|
self.btn_next.on_click(self._shift_right)
|
|
17
19
|
|
|
18
20
|
def _shift_right(self,change):
|
|
19
|
-
self.widgets.slidebox
|
|
20
|
-
self.widgets.slidebox.remove_class('AnimPrev') # content animation flag
|
|
21
|
+
update_class(self.widgets.slidebox, 'Prev AnimPrev', False) # remove backwards animation safely
|
|
21
22
|
if change and not self.slides._current.next_frame():
|
|
22
23
|
if self.wprogress.value < self.wprogress.max:
|
|
23
24
|
self.wprogress.value = self.wprogress.value + 1 # Forwards
|
|
24
25
|
|
|
25
26
|
def _shift_left(self,change):
|
|
26
|
-
self.widgets.slidebox.remove_class('Prev') # remove backwards animation safely, need trigger before adding
|
|
27
|
+
self.widgets.slidebox.remove_class('Prev') # remove backwards animation safely, need trigger before adding, but avoid resetting AnimPrev here
|
|
27
28
|
if change and not self.slides._current.prev_frame():
|
|
28
|
-
self.widgets.slidebox
|
|
29
|
-
self.widgets.slidebox.add_class('AnimPrev') # content animation flag, need to stay persistent to avoid trigger removal too early
|
|
29
|
+
update_class(self.widgets.slidebox, 'Prev AnimPrev', True)
|
|
30
30
|
if self.wprogress.value > 0:
|
|
31
31
|
self.wprogress.value = self.wprogress.value - 1 # Backwards
|
|
32
32
|
|
|
@@ -4,6 +4,7 @@ and then provided to other classes via composition, not inheritance.
|
|
|
4
4
|
"""
|
|
5
5
|
import json, re
|
|
6
6
|
import traitlets
|
|
7
|
+
import textwrap
|
|
7
8
|
|
|
8
9
|
from traitlets import HasTraits, Int, Unicode, Bool, Float, TraitError
|
|
9
10
|
from pathlib import Path
|
|
@@ -11,7 +12,7 @@ from inspect import Signature, Parameter
|
|
|
11
12
|
from ipywidgets.widgets.trait_types import InstanceDict
|
|
12
13
|
|
|
13
14
|
from ..formatters import code_css, htmlize
|
|
14
|
-
from ..utils import html, today, _clipbox_children, _resolve_img, get_clips_dir, set_dir
|
|
15
|
+
from ..utils import html, today, update_class, _clipbox_children, _resolve_img, get_clips_dir, set_dir
|
|
15
16
|
from . import styles, _layout
|
|
16
17
|
from ..dashlab import disabled
|
|
17
18
|
|
|
@@ -126,21 +127,23 @@ class Footer(ConfigTraits):
|
|
|
126
127
|
date = Unicode("today", allow_none=True, help="Set date string or use 'today' for current date.")
|
|
127
128
|
controls = Bool(True, help="Show/hide controls like next/prev buttons and clickers in PDF/export mode.")
|
|
128
129
|
progress = Bool(True, help="Show/hide progress bar at bottom of slides.")
|
|
129
|
-
|
|
130
|
+
|
|
130
131
|
def _apply_change(self,change):
|
|
131
132
|
# change is None when called as footer(...), so we update all things together
|
|
132
133
|
self._update_footer() # html content updated
|
|
133
134
|
wgts = self.main._widgets
|
|
134
135
|
wgts._progbar.layout.display = "" if self.progress else "none" # show/hide progress bar
|
|
135
|
-
wgts.iw._fpad = 23 if any([self.text, self.date, self.section, self.numbering]) else 16 # 16 is same around all sides unless footer needs more
|
|
136
136
|
wgts.controls.layout.visibility = "visible" if self.controls else "hidden"
|
|
137
|
-
wgts.iw.msg_tojs = 'RESCALE' # sets padding etc
|
|
138
137
|
|
|
139
138
|
if not self.controls:
|
|
140
139
|
self.main._slides.notify("Navigation controls hidden. But keyboard and edge click is still working!")
|
|
141
140
|
|
|
142
141
|
for slide in self.main._slides:
|
|
143
142
|
self._set_on(slide) # update footer per slide for print/export
|
|
143
|
+
|
|
144
|
+
hastext = any([self.text, self.date, self.section, self.numbering])
|
|
145
|
+
update_class(self.main._widgets.mainbox, 'has-footer-text', hastext)
|
|
146
|
+
wgts.iw.msg_tojs = 'RESCALE' # keep class update first so scale uses latest footer padding
|
|
144
147
|
|
|
145
148
|
def _running_section_text(self, slide):
|
|
146
149
|
"Get running section title via Slides._sectionindex and return stripped text."
|
|
@@ -192,12 +195,9 @@ class Layout(ConfigTraits):
|
|
|
192
195
|
centered = Bool(True)
|
|
193
196
|
scroll = Bool(True)
|
|
194
197
|
width = Int(100)
|
|
195
|
-
aspect = Float(16/9
|
|
198
|
+
aspect = Float(16/9, help="Aspect ratio of slides, width/height.")
|
|
196
199
|
ncol_refs = Int(2)
|
|
197
200
|
theme = Unicode('Jupyter')
|
|
198
|
-
|
|
199
|
-
_reflow = False # internal use only
|
|
200
|
-
_inotes = False # internal use only
|
|
201
201
|
|
|
202
202
|
@traitlets.validate('width')
|
|
203
203
|
def _limit_width(self, proposal):
|
|
@@ -206,6 +206,7 @@ class Layout(ConfigTraits):
|
|
|
206
206
|
return proposal["value"]
|
|
207
207
|
|
|
208
208
|
def _apply_change(self, change):
|
|
209
|
+
self._set_page_css() # set page size for print/export
|
|
209
210
|
if change and change['owner'] == "theme":
|
|
210
211
|
return self.main._update_theme({'owner':'layout'}) # otherwise colors don't apply to icons
|
|
211
212
|
self.main._update_size(change = None) # will reset theme and send RESCALE message
|
|
@@ -217,12 +218,27 @@ class Layout(ConfigTraits):
|
|
|
217
218
|
raise ValueError(f"Theme value expect on the followings: {themes!r}")
|
|
218
219
|
self.main._widgets.theme.value = proposal["value"] # needs a robust update
|
|
219
220
|
return proposal["value"]
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def _height(self) -> str:
|
|
224
|
+
"Get height of slides in mm based on aspect and width (210mm)."
|
|
225
|
+
return f"{round(210 / self.aspect, 2)}mm"
|
|
226
|
+
|
|
227
|
+
def _set_page_css(self):
|
|
228
|
+
"Set page size and margin for print/export. Must be done once per view to avoid clashes."
|
|
229
|
+
self.main._widgets.iw._page_css = textwrap.dedent(f'''
|
|
230
|
+
@page {{
|
|
231
|
+
margin: 0 !important;
|
|
232
|
+
size: 210mm {self._height} !important; /* 1pt ~ 0.35mm, so no more than one decimal required */
|
|
233
|
+
}}
|
|
234
|
+
''')
|
|
220
235
|
|
|
221
236
|
@fix_sig
|
|
222
237
|
class Toggle(ConfigTraits):
|
|
223
238
|
"Toggle ON/OFF checks in settings panel."
|
|
224
|
-
|
|
239
|
+
flow = Bool(False)
|
|
225
240
|
notes = Bool(False)
|
|
241
|
+
inotes = Bool(False)
|
|
226
242
|
toast = Bool(True)
|
|
227
243
|
focus = Bool(True, help="Focus on slide area when a slide is built or rebuilt.")
|
|
228
244
|
rebuild= Bool(True, help="Auto rebuild markdown slides when an included notebook-scoped variable is updated.")
|
|
@@ -233,7 +249,9 @@ class Toggle(ConfigTraits):
|
|
|
233
249
|
if widget := getattr(self.main._widgets.checks, trait, None):
|
|
234
250
|
traitlets.link((self,trait),(widget, "value"))
|
|
235
251
|
|
|
236
|
-
def _apply_change(self, change):
|
|
252
|
+
def _apply_change(self, change):
|
|
253
|
+
update_class(self.main._widgets.mainbox, 'has-flow-content', self.flow) # content flow on/off
|
|
254
|
+
update_class(self.main._widgets.mainbox, 'has-inline-notes', self.inotes) # inline notes on/off
|
|
237
255
|
|
|
238
256
|
class AutoUnicode(Unicode):
|
|
239
257
|
def validate(self, obj, value):
|
|
@@ -256,7 +274,7 @@ class Logo(ConfigTraits):
|
|
|
256
274
|
@traitlets.validate('src')
|
|
257
275
|
def _fix_path(self, proposal):
|
|
258
276
|
if isinstance(proposal["value"], Path):
|
|
259
|
-
return str(
|
|
277
|
+
return str(proposal["value"])
|
|
260
278
|
return proposal["value"]
|
|
261
279
|
|
|
262
280
|
def _set_props(self, **kwargs):
|
|
@@ -305,9 +323,7 @@ class Settings:
|
|
|
305
323
|
|
|
306
324
|
self._widgets.sliders.fontsize.observe(lambda c: self.fonts(size=c.new), names=["value"])
|
|
307
325
|
self._widgets.theme.observe(self._update_theme, names=["value"])
|
|
308
|
-
self._widgets.checks.
|
|
309
|
-
self._widgets.checks.inotes.observe(self._update_theme, names=["value"]) # set inline notes through layout._inotes
|
|
310
|
-
self._widgets.checks.merge.observe(self._set_merge_class, names=["value"])
|
|
326
|
+
self._widgets.checks.merge.observe(self._set_merge_ctx, names=["value"])
|
|
311
327
|
self._widgets.buttons.print.on_click(self._print_pdf)
|
|
312
328
|
self._wslider.observe(self._update_size, names=["value"])
|
|
313
329
|
self._update_theme({'owner':'layout'}) # Trigger Theme with aspect changed as well
|
|
@@ -334,12 +350,10 @@ class Settings:
|
|
|
334
350
|
raise AttributeError(f"Can't reset attribute {name!r} on {self!r}")
|
|
335
351
|
self.__dict__[name] = value
|
|
336
352
|
|
|
337
|
-
def
|
|
338
|
-
if self.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
self._widgets.mainbox.remove_class('SlidesMerged')
|
|
342
|
-
|
|
353
|
+
def _set_merge_ctx(self, change=None):
|
|
354
|
+
if curr := self._slides._current:
|
|
355
|
+
curr.update_display() # force update to reset frames
|
|
356
|
+
|
|
343
357
|
def _print_pdf(self, btn):
|
|
344
358
|
if self._slides._next_pending:
|
|
345
359
|
return self._slides.notify(
|
|
@@ -368,7 +382,7 @@ class Settings:
|
|
|
368
382
|
if len(self._slides) < 5 or slide.number == 0:
|
|
369
383
|
return '' # no clicks for few slides or title page
|
|
370
384
|
|
|
371
|
-
items = [getattr(item,'_sec_id','') for item in self._slides if item.index <= self._slides._lms_idx] # only before supplemnetal
|
|
385
|
+
items = [getattr(item,'_sec_id','') for item in self._slides if (item.index or 0) <= self._slides._lms_idx] # only before supplemnetal
|
|
372
386
|
imax = len(items) - 1
|
|
373
387
|
items = [items[int(round(i,0))] for i in [0, imax/4,imax/2, 3*imax/4, imax]]
|
|
374
388
|
labels = '●●●●●'
|
|
@@ -400,10 +414,6 @@ class Settings:
|
|
|
400
414
|
_layout.layout_css(self._colors['accent'],self.layout.aspect)
|
|
401
415
|
).value + _layout.loading_style # loading style should be always there
|
|
402
416
|
|
|
403
|
-
# before applying style_css theme, set reflow and inotes
|
|
404
|
-
self.layout._reflow = self._widgets.checks.reflow.value
|
|
405
|
-
self.layout._inotes = self._widgets.checks.inotes.value
|
|
406
|
-
|
|
407
417
|
theme_css = styles.style_css(**self._theme_kws)
|
|
408
418
|
self._widgets.htmls.theme.value = html("style", theme_css).value
|
|
409
419
|
if self._widgets.checks.notes.value:
|
|
@@ -11,7 +11,7 @@ from ipywidgets import HTML as ipwHTML, VBox
|
|
|
11
11
|
from . import styles
|
|
12
12
|
from ._layout import background_css, get_unique_css_class, loading_skeleton
|
|
13
13
|
from .styles import collapse_node, hide_node
|
|
14
|
-
from ..utils import XTML, html, _resolve_img, _styled_css, _build_css
|
|
14
|
+
from ..utils import XTML, html, update_class, _resolve_img, _styled_css, _build_css
|
|
15
15
|
from ..xmd import capture_content
|
|
16
16
|
from ..formatters import _Output, widget_from_data, slidebound
|
|
17
17
|
|
|
@@ -135,7 +135,7 @@ class Slide:
|
|
|
135
135
|
self._has_widgets = False # Update in _build_slide function
|
|
136
136
|
self._has_vars = () # Update in _slide function for markdown slides only
|
|
137
137
|
self._toc_args = () # empty by default
|
|
138
|
-
self.
|
|
138
|
+
self._update_class(add=f"n{self.number}")
|
|
139
139
|
self._fcss = ipwHTML(layout={"margin": "0","padding": "0","heigh": "0"}) # frame separator CSS
|
|
140
140
|
self._bg_ikws = {} # rebuild always re-derives background mapping from content
|
|
141
141
|
|
|
@@ -206,7 +206,7 @@ class Slide:
|
|
|
206
206
|
# Clean up delimiters: trailing, empty, adjacent PAUSE delimiters
|
|
207
207
|
self._contents = self._cleanup_delimiters(outputs)
|
|
208
208
|
self._contents.extend(self._handle_refs()) # add at end if any
|
|
209
|
-
self.
|
|
209
|
+
self._update_class(remove = 'Out-Sync') # Now synced
|
|
210
210
|
self.update_display()
|
|
211
211
|
|
|
212
212
|
if self._app.widgets.checks.focus.value: # User preference
|
|
@@ -254,8 +254,8 @@ class Slide:
|
|
|
254
254
|
self._widget.clear_output(wait = True) # Clear, but don't go there
|
|
255
255
|
# Need to know how many contents before user provide content
|
|
256
256
|
with capture_content() as cap:
|
|
257
|
+
html('span', '', id = self._sec_id, css_class='Slide-UID').display() # to register section id in DOM
|
|
257
258
|
display(VBox([
|
|
258
|
-
html('span', '', id = self._sec_id, css_class='Slide-UID').as_widget(), # span to not occupy space, need to remove from frames later.
|
|
259
259
|
self._bglayer, # background image layer
|
|
260
260
|
self._ftrhtml, # dynamic footer layer
|
|
261
261
|
self._fcss, # frame separator CSS
|
|
@@ -311,13 +311,9 @@ class Slide:
|
|
|
311
311
|
if s._section:
|
|
312
312
|
items.append({"c":"next", "s":s})
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
first_toc_slide = next((s for s in self._app[:] if s._toc_args), None)
|
|
315
315
|
for s in self._app[:]:
|
|
316
|
-
|
|
317
|
-
s._widget.add_class('FirstTOC')
|
|
318
|
-
first_toc = False
|
|
319
|
-
else:
|
|
320
|
-
s._widget.remove_class('FirstTOC')
|
|
316
|
+
update_class(s._widget, 'main-toc', s is first_toc_slide) # first TOC slide is special
|
|
321
317
|
|
|
322
318
|
items = [XTML(textwrap.dedent('''
|
|
323
319
|
<li class="toc-item {c}">
|
|
@@ -344,7 +340,7 @@ class Slide:
|
|
|
344
340
|
|
|
345
341
|
self._frame_idxs = tuple(frames) if len(frames) > 1 else ()
|
|
346
342
|
if self._frame_idxs:
|
|
347
|
-
self.
|
|
343
|
+
self._update_class(add='HasFrames') # make sure class is added
|
|
348
344
|
self._reveal_frames() # quick preview of how built frames will appear
|
|
349
345
|
elif hasattr(self, '_frame_idxs'): # from previous run may be
|
|
350
346
|
del self._frame_idxs
|
|
@@ -365,7 +361,8 @@ class Slide:
|
|
|
365
361
|
|
|
366
362
|
# Next PAUSE after columns is ignored if fragments were already built from the COLUMNS output.
|
|
367
363
|
if meta.get("DELIM", "") == "PAUSE":
|
|
368
|
-
|
|
364
|
+
prev_index = index - 1 - offset # actual index in contents list
|
|
365
|
+
meta_prev = ensure_dict(contents[prev_index].metadata) if prev_index >= 0 else {}
|
|
369
366
|
if "COLUMNS" in meta_prev and get_frags(meta_prev):
|
|
370
367
|
continue
|
|
371
368
|
|
|
@@ -397,18 +394,12 @@ class Slide:
|
|
|
397
394
|
|
|
398
395
|
def _update_view(self, which):
|
|
399
396
|
self._set_progress()
|
|
400
|
-
|
|
401
|
-
'remove_class' if which == 'next' else 'add_class'
|
|
402
|
-
)('AnimPrev') # set content animation direction between parts
|
|
403
|
-
|
|
397
|
+
update_class(self._app.widgets.slidebox, 'AnimPrev', which == 'prev') # set content animation direction between parts
|
|
404
398
|
frame = self._fidxs[self.indexf] if self._fidxs else {}
|
|
405
399
|
self._update_transition_objs(animation=False) # avoid animations between frames
|
|
406
400
|
|
|
407
401
|
if self.index == self._app.wprogress.max: # This is last slide
|
|
408
|
-
|
|
409
|
-
self._app._box.add_class("InView-Last")
|
|
410
|
-
else:
|
|
411
|
-
self._app._box.remove_class("InView-Last")
|
|
402
|
+
update_class(self._app._box, "InView-Last", self.indexf + 1 == self.nf) # only if last frame
|
|
412
403
|
|
|
413
404
|
self._app._send_nav_msg(
|
|
414
405
|
which == 'next',
|
|
@@ -689,19 +680,13 @@ class Slide:
|
|
|
689
680
|
if not self._app.this: # Otherwise it has side effects
|
|
690
681
|
self._app.navigate_to(self.index)
|
|
691
682
|
|
|
692
|
-
def
|
|
683
|
+
def _update_class(self, add=None, remove=None):
|
|
693
684
|
"Set CSS classes on this slide separated by space. classes are remove first and add after it."
|
|
694
685
|
if remove is not None: # remove first to enable toggle
|
|
695
|
-
|
|
696
|
-
raise TypeError("CSS classes should be a string with each class separated by space")
|
|
697
|
-
for c in remove.split():
|
|
698
|
-
self._widget.remove_class(c)
|
|
686
|
+
update_class(self._widget, remove, False)
|
|
699
687
|
|
|
700
|
-
if add is not None:
|
|
701
|
-
|
|
702
|
-
raise TypeError("CSS classes should be a string with each class separated by space")
|
|
703
|
-
for c in add.split():
|
|
704
|
-
self._widget.add_class(c)
|
|
688
|
+
if add is not None: # add after remove to enable toggle
|
|
689
|
+
update_class(self._widget, add, True)
|
|
705
690
|
|
|
706
691
|
@property
|
|
707
692
|
def _css_class(self):
|