Expand replaces Widen: fill the window in both directions

From the development repository at 3f1da52.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ackandClaude Opus 4.8 committed 2026-10-11 19:43:17 +02:00
1 parent 51e66db9a1
commit cc0b8dbd31
9 files changed
+102 -72

No files matched your search

+2 -2
View File
@@ -78,8 +78,8 @@
data-help="Show the whole page in the window. Pressed by the host, it does so on every screen. Ctrl (Command on a Mac) with the scroll wheel, or with a pen stroke up or down, zooms about the pointer.">Fit</button>
<button id="zoom_sheet" type="button"
data-help="Show the page at the scale a new page has: one A4 sheet filling the window. A shrunk page is then seen at its size on the sheet. Pressed by the host, it does so on every screen.">A4</button>
<button id="zoom_widen" type="button" hidden
data-help="Make the page wider, for everyone, until it fills the window at this zoom. Nothing on it moves.">Widen</button>
<button id="zoom_expand" type="button" hidden
data-help="Make the page fill the window, for everyone, at this zoom, in both directions. Nothing on it moves.">Expand</button>
<button id="zoom_shrink" type="button" hidden
data-help="Make the page, for everyone, just large enough for what is on it, with the margin around. Nothing on it moves.">Shrink</button>
<label class="toggle" data-help="Let the page grow when your writing reaches an edge. Off, your writing stops at the edge. Images always get the room they need.">
+12 -8
View File
@@ -1027,12 +1027,16 @@ async function enterSession(invitationText, name, notebook) {
renderer.sheetScale();
session.sendViewCommand('sheet');
});
// "Widen" makes the sheet itself wider, until it fills the window at
// the present zoom; nothing on it moves (Andy, 2026-10-08).
element('zoom_widen').addEventListener('click', () => {
const room = renderer.available().width / renderer.cssPixelsPerUnit;
const widened = session.widenPage(room);
if (widened) log(`Page widened to ${session.page.baseWidth / 100} cm`);
// "Expand" makes the sheet itself fill the window at the present zoom,
// in both directions; nothing on it moves (Andy, 2026-10-08 for the
// width, both ways 2026-10-11).
element('zoom_expand').addEventListener('click', () => {
const room = renderer.available();
const expanded = session.expandPage(room.width / renderer.cssPixelsPerUnit,
room.height / renderer.cssPixelsPerUnit);
if (expanded) {
log(`Page expanded to ${session.page.baseWidth / 100} by ${session.page.baseHeight / 100} cm`);
}
});
element('zoom_shrink').addEventListener('click', () => {
if (session.shrinkPage()) {
@@ -1062,7 +1066,7 @@ async function enterSession(invitationText, name, notebook) {
const hideTool = /** @type {HTMLElement} */ (document.querySelector('[data-tool=hide]'));
hideTool.hidden = !session.mayMakePages();
if (hideTool.hidden && tool === 'hide') /** @type {HTMLElement} */ (document.querySelector('[data-tool=pen]')).click();
element('zoom_widen').hidden = !session.mayMakePages();
element('zoom_expand').hidden = !session.mayMakePages();
element('zoom_shrink').hidden = !session.mayMakePages();
}
showHostTools();
@@ -1413,7 +1417,7 @@ function showParticipants(session, renderer) {
// A checkbox with its label to the left, like Extend (Andy, 2026-10-09).
const allowLine = document.createElement('label');
allowLine.className = 'allow_line';
allowLine.setAttribute('data-help', 'Make this participant a co-host: they may then start pages, put images on the page and arrange them, load notebook pages, widen, shrink and hide, as you can.');
allowLine.setAttribute('data-help', 'Make this participant a co-host: they may then start pages, put images on the page and arrange them, load notebook pages, expand, shrink and hide, as you can.');
const allow = document.createElement('input');
allow.type = 'checkbox';
allow.className = 'allow';
+30 -21
View File
@@ -479,28 +479,37 @@ export class Session {
}
/**
* Make the sheet wider, for more room to write (Andy, 2026-10-08:
* "Widen" enlarges the page rather than zooming). Everyone's page
* widens; it is a change to the page, so it needs the right to make
* pages, and it ends the chance to take back an image.
* @param {number} width The page's new width, if wider than now.
* @returns {boolean} Whether the page widened.
* Make the sheet fill the window, for more room to write (Andy,
* 2026-10-08 as "Widen", which only grew the width; both ways since
* 2026-10-11, since there is no reason width alone suits anyone).
* Everyone's page grows; it is a change to the page, so it needs the
* right to make pages, and it ends the chance to take back an image.
* @param {number} width The page's new width, if wider than now.
* @param {number} height The page's new height, if taller than now.
* @returns {boolean} Whether the page grew.
*/
widenPage(width) {
expandPage(width, height) {
const page = this.page;
const added = Math.round(width) - page.width;
if (!this.mayMakePages() || page.number === 0 || added <= 0) return false;
// Equally on both sides of the PAGE as it is, writing included (the
// sheet may lie inside a page that writing has grown; measured from
// the sheet, the page came out short, Andy, 2026-10-08): what is on
// the page stays where it is seen. The same whole number of units on
// each side, since the page is centred on the screen: an odd growth
// split 1199 and 1200 moved the page's centre half a unit, a fraction
// of a pixel that showed as a one-pixel move on Olion (2026-10-09).
const half = Math.floor(added / 2);
if (half === 0) return false;
page.baseLeft = page.left - half;
page.baseWidth = page.width + 2 * half;
if (!this.mayMakePages() || page.number === 0) return false;
// Equally on both sides, and top and bottom, of the PAGE as it is,
// writing included (the sheet may lie inside a page that writing has
// grown; measured from the sheet, the page came out short, Andy,
// 2026-10-08): what is on the page stays where it is seen. The same
// whole number of units on each side, since the page is centred on
// the screen: an odd growth split 1199 and 1200 moved the centre half
// a unit, a pixel's fraction that showed as a one-pixel move on Olion
// (2026-10-09). A dimension already filling the window does not grow.
const addLeft = Math.floor((Math.round(width) - page.width) / 2);
const addTop = Math.floor((Math.round(height) - page.height) / 2);
if (addLeft <= 0 && addTop <= 0) return false;
if (addLeft > 0) {
page.baseLeft = page.left - addLeft;
page.baseWidth = page.width + 2 * addLeft;
}
if (addTop > 0) {
page.baseTop = page.top - addTop;
page.baseHeight = page.height + 2 * addTop;
}
page.imagesRevision += 1;
this.send(0, { type: 'page', page: page.descriptor() });
this.extentChanged(true);
@@ -510,7 +519,7 @@ export class Session {
/**
* Make the sheet the smallest that holds everything on the page with
* the margin around it ("Shrink", Andy, 2026-10-08). A change to the
* page like "Widen".
* page like "Expand".
* @returns {boolean} Whether the sheet changed.
*/
shrinkPage() {
+7 -1
View File
@@ -260,6 +260,11 @@ h2 {
flex: 1;
min-width: 0;
overflow: auto;
/* Centre the page in the desk both ways, so that growing the sheet
("Expand") equally on every side leaves what is on it where it is
seen. margin:auto on an overflowing flex child computes to 0, so a
page larger than the desk still scrolls to every edge. */
display: flex;
padding: 0.5rem;
background: var(--desk);
}
@@ -287,7 +292,8 @@ h2 {
.page {
position: relative;
margin: 0 auto;
flex: none;
margin: auto;
background: white;
box-shadow: 0 1px 4px rgb(0 0 0 / 0.25);
touch-action: none;
+5 -5
View File
@@ -153,11 +153,11 @@ two gestures change the view, and none of them changes the page:
* When the host presses **Fit** or **A4**, every participant's view
follows, each in their own window. Your own Fit and A4 change only
your screen.
* **Widen**, which only the host and co-hosts have, makes the page
itself wider, for everyone, until it fills the
window at the present zoom. The sheet grows equally on both sides,
so nothing on the page moves and nothing is rescaled; there is simply
more room at either side.
* **Expand**, which only the host and co-hosts have, makes the page
itself fill the window, for everyone, at the present zoom, in both
directions. The sheet grows equally on every side, so nothing on the
page moves and nothing is rescaled; there is simply more room around
it.
* **Shrink**, also for the host and co-hosts, makes the page, for everyone, just large enough for what is on it,
with the usual margin around. Nothing on it moves either.
* **Extend** is a box, ticked to begin with. Ticked, the sheet grows
+5 -5
View File
@@ -225,10 +225,10 @@ is the "write your answer here" box of a worksheet: draw it on the scan
where the answer belongs, and the student types into their own box or
beside yours. Only its owner can type in a box, as with any text.
**Widen**, among the view buttons, makes the page wider for everyone,
until it fills your window at your present zoom: the quickest way to
more room for writing beside an exercise. The sheet grows equally on
both sides, so nothing on the page moves. The page keeps the new width
**Expand**, among the view buttons, makes the page fill your window for
everyone, at your present zoom, in both directions: the quickest way to
more room for writing around an exercise. The sheet grows equally on
every side, so nothing on the page moves. The page keeps the new size
in everyone's notebook. **Shrink** is its opposite: the page becomes
just large enough for everything on it, images and writing, with the
margin of 5 mm around, which trims a page down to its exercise before you keep
@@ -266,7 +266,7 @@ notebook in the toolbar first.
In the **Participants** list of the Session window, each guest's name
has a box **Co-host** beside it. Ticking it makes that guest a
co-host: they may then start new pages, put images on the page and
arrange them, load pages from their notebook, widen, shrink and hide,
arrange them, load pages from their notebook, expand, shrink and hide,
exactly as you can, and the buttons for it appear on their toolbar.
Unticking it takes it back; otherwise it lasts until the session
ends. What stays yours alone: making co-hosts, and bringing everyone's
+2 -1
View File
@@ -113,7 +113,8 @@ from the host (or from an allowed guest, for pages and images).
## Pages
Page coordinates are in units of 0.1 mm. A blank page is an A4 sheet
in landscape, 2970 by 2100 units. "Widen" makes the sheet wider, and
in landscape, 2970 by 2100 units. "Expand" makes the sheet fill the
window in both directions, and
"Shrink" makes it the bounding box of everything on the page plus the
greater of the two margins, exactly, on every side; both are changes to the page like adding
an image, carried in the page descriptor (its `left` and `top` edges and
+1 -1
View File
@@ -68,7 +68,7 @@ rather than in the browser's storage.
## The screen in one paragraph
The toolbar holds the pen, text, eraser and hide tools, colours and
line widths, the view commands (Fit, A4, Widen, Shrink, Extend), the
line widths, the view commands (Fit, A4, Expand, Shrink, Extend), the
host's page buttons (New, Image, Notebooks), your notebook, Keep, Export
and Session. Rest the pointer on any button for a second for its
explanation. Each participant writes on a layer of their own that
+38 -28
View File
@@ -1418,23 +1418,26 @@ class EndToEndTest(unittest.TestCase):
self.assertEqual(placed["placed"]["y"], upward["origin"][1] + 50)
self.assertEqual(placed["placed"]["x"], upward["origin"][0] + (upward["page"]["width"] - 300) // 2)
# Widen on a page that writing has grown on one side: the page
# comes out as wide as the window, measured from the page's own
# edges, not the sheet's (Andy, 2026-10-08).
# Expand on a page that writing has grown on one side: the page
# comes out as wide and as tall as the window, measured from the
# page's own edges, not the sheet's (Andy, 2026-10-08, 2026-10-11).
firefox.run("""
document.getElementById('desk').dispatchEvent(new WheelEvent('wheel',
{deltaY: 100, ctrlKey: true, clientX: 300, clientY: 300, bubbles: true, cancelable: true}));
""")
firefox.run("document.getElementById('zoom_widen').click();")
widened = self.wait_until(lambda s: s["page"]["width"] > grown["page"]["width"], "the page to widen")
firefox.run("document.getElementById('zoom_expand').click();")
self.wait_until(lambda s: s["page"]["width"] > grown["page"]["width"]
and s["page"]["height"] > grown["page"]["height"], "the page to expand")
measured = firefox.run("""
const r = window.kokkoja.renderer;
const desk = document.getElementById('desk');
const style = getComputedStyle(desk);
const room = desk.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
return {page: document.getElementById('page').getBoundingClientRect().width, room};
const page = document.getElementById('page').getBoundingClientRect();
return {width: page.width, height: page.height,
room: desk.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight),
roomHeight: desk.offsetHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom)};
""")
self.assertAlmostEqual(measured["page"], measured["room"], delta=1)
self.assertAlmostEqual(measured["width"], measured["room"], delta=1)
self.assertAlmostEqual(measured["height"], measured["roomHeight"], delta=1)
# The writer's tab is closed for the next test; the host's is cleared.
firefox.switch_to(guest_tab)
@@ -1654,8 +1657,8 @@ class EndToEndTest(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(firefox.run("return window.kokkoja.renderer.cssPixelsPerUnit;"), host_scale)
# Widen makes the sheet itself wider, for everyone, until it fills
# the window at this zoom; the scale and what is in view stay.
# Expand makes the sheet fill the window, for everyone, at this
# zoom, in both directions; the scale and what is in view stay.
firefox.run("""
document.getElementById('desk').dispatchEvent(new WheelEvent('wheel',
{deltaY: 100, ctrlKey: true, clientX: 300, clientY: 300, bubbles: true, cancelable: true}));
@@ -1666,29 +1669,36 @@ class EndToEndTest(unittest.TestCase):
return [Math.round(b.left), Math.round(b.top), Math.round(b.width)];
""")
box_before = image_box()
firefox.run("document.getElementById('zoom_widen').click();")
widened = zoom()
# The sheet grew equally on both sides: nothing on it moved on the screen.
firefox.run("document.getElementById('zoom_expand').click();")
expanded = zoom()
# The sheet grew equally on every side: nothing on it moved on screen.
self.assertEqual(image_box(), box_before)
self.assertLess(firefox.run("return window.kokkoja.session.page.baseLeft;"), 0)
desk_width = firefox.run("""
self.assertLess(firefox.run("return window.kokkoja.session.page.baseTop;"), 0)
room = firefox.run("""
const desk = document.getElementById('desk');
const style = getComputedStyle(desk);
return desk.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
return {width: desk.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight),
height: desk.offsetHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom)};
""")
self.assertAlmostEqual(widened["width"], desk_width, delta=1)
self.assertEqual(widened["zoom"], smaller["zoom"])
page_box = firefox.run("""
const b = document.getElementById('page').getBoundingClientRect();
return {width: b.width, height: b.height};
""")
self.assertAlmostEqual(page_box["width"], room["width"], delta=1)
self.assertAlmostEqual(page_box["height"], room["height"], delta=1)
self.assertEqual(expanded["zoom"], smaller["zoom"])
wide_page = self.state()["page"]
self.assertGreater(wide_page["width"], teacher["page"]["width"])
self.assertEqual(wide_page["height"], teacher["page"]["height"])
self.assertGreater(wide_page["height"], teacher["page"]["height"])
firefox.switch_to(student_tab)
self.wait_until(lambda s: s["page"] == wide_page, "the wider page to reach the student")
self.assertTrue(firefox.run("return document.getElementById('zoom_widen').hidden;"))
self.wait_until(lambda s: s["page"] == wide_page, "the expanded page to reach the student")
self.assertTrue(firefox.run("return document.getElementById('zoom_expand').hidden;"))
self.assertEqual(firefox.run("return window.kokkoja.session.page.baseLeft;"),
-((wide_page["width"] - teacher["page"]["width"]) // 2))
# The page file carries the sheet's left edge and reads back with it.
widened_file = self.current_page("Student notebook", {}, number=3)
self.assertRegex(widened_file, r'data-base="\d+ 2100 -\d+ 0"')
# The page file carries the sheet's edges and reads back with them.
expanded_file = self.current_page("Student notebook", {}, number=3)
self.assertRegex(expanded_file, r'data-base="\d+ \d+ -\d+ -\d+"')
firefox.switch_to(teacher_tab)
# Shrink makes the sheet just large enough for what is on it, with
@@ -1725,15 +1735,15 @@ class EndToEndTest(unittest.TestCase):
firefox.run("document.getElementById('zoom_sheet').click();")
sheet_scale = firefox.run("return window.kokkoja.renderer.cssPixelsPerUnit;")
self.assertLess(sheet_scale, fitted_scale)
self.assertAlmostEqual(sheet_scale, min(desk_width / 2970, firefox.run("""
self.assertAlmostEqual(sheet_scale, min(room["width"] / 2970, firefox.run("""
const desk = document.getElementById('desk');
const style = getComputedStyle(desk);
return desk.offsetHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);
""") / 2100), places=4)
# Widened again, from the shrunk sheet, for the room the rest needs.
# Expanded again, from the shrunk sheet, for the room the rest needs.
firefox.run("document.getElementById('zoom_fit').click();")
firefox.run("document.getElementById('zoom_widen').click();")
self.wait_until(lambda s: s["page"]["width"] > shrunk_page["page"]["width"], "the page widened again")
firefox.run("document.getElementById('zoom_expand').click();")
self.wait_until(lambda s: s["page"]["width"] > shrunk_page["page"]["width"], "the page expanded again")
firefox.run("document.getElementById('zoom_fit').click();")
teacher = self.state()