mirror of
https://github.com/simonw/datasette.git
synced 2026-09-15 04:54:17 +02:00
Add script to regenerate the datasette-modal docs screenshot
docs/generate-datasette-modal-example.sh builds a temporary demo database and plugins directory containing the createModal() example from the Modal dialogs documentation, starts a Datasette server, takes the screenshot with shot-scraper, quantizes it to an 8-bit palette PNG and stops the server again. Output is byte-identical to the committed docs/datasette-modal-example.png. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TShiUYVMmmF4zyJR6GMw34
This commit is contained in:
parent
5fd27d9347
commit
967c1298ea
2 changed files with 149 additions and 0 deletions
147
docs/generate-datasette-modal-example.sh
Executable file
147
docs/generate-datasette-modal-example.sh
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
#!/bin/bash
|
||||
# Recreate docs/datasette-modal-example.png
|
||||
#
|
||||
# Takes a screenshot of the <datasette-modal> example plugin from the
|
||||
# "Modal dialogs" section of docs/javascript_plugins.rst, running against
|
||||
# a temporary Datasette instance that this script starts and stops.
|
||||
#
|
||||
# Requirements:
|
||||
# - datasette importable by $PYTHON (e.g. "pip install -e ." in this repo)
|
||||
# - uv, for "uvx shot-scraper" and the Pillow-based PNG quantization step
|
||||
# (shot-scraper needs a Playwright browser: "uvx shot-scraper install")
|
||||
#
|
||||
# Environment variable overrides:
|
||||
# PYTHON Python command (default: python3)
|
||||
# SHOT_SCRAPER shot-scraper command (default: uvx shot-scraper)
|
||||
# PORT port for the temporary server (default: 8574)
|
||||
set -euo pipefail
|
||||
|
||||
read -r -a PYTHON_CMD <<< "${PYTHON:-python3}"
|
||||
read -r -a SHOT_SCRAPER_CMD <<< "${SHOT_SCRAPER:-uvx shot-scraper}"
|
||||
PORT="${PORT:-8574}"
|
||||
|
||||
docs_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
output="$docs_dir/datasette-modal-example.png"
|
||||
|
||||
tmp_dir=$(mktemp -d)
|
||||
server_pid=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$server_pid" ]; then
|
||||
kill "$server_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# A small demo database for the page shown behind the dialog
|
||||
"${PYTHON_CMD[@]}" - "$tmp_dir/demo.db" <<'EOF'
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
conn = sqlite3.connect(sys.argv[1])
|
||||
conn.executescript(
|
||||
"""
|
||||
create table plugins (id integer primary key, name text, description text);
|
||||
insert into plugins (name, description) values
|
||||
('datasette-cluster-map', 'Renders a map of geographic data'),
|
||||
('datasette-vega', 'Visualize data with Vega charts'),
|
||||
('datasette-graphql', 'GraphQL endpoint for Datasette');
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
EOF
|
||||
|
||||
# The example plugin code from the "Modal dialogs" section of
|
||||
# docs/javascript_plugins.rst, injected via extra_body_script()
|
||||
mkdir "$tmp_dir/plugins"
|
||||
cat > "$tmp_dir/plugins/modal_example.py" <<'EOF'
|
||||
from datasette import hookimpl
|
||||
|
||||
|
||||
@hookimpl
|
||||
def extra_body_script():
|
||||
return {
|
||||
"script": """
|
||||
document.addEventListener("datasette_init", function (event) {
|
||||
const manager = event.detail;
|
||||
const modal = manager.createModal({
|
||||
id: "my-plugin-dialog",
|
||||
className: "my-plugin-dialog",
|
||||
title: "My plugin",
|
||||
content: `
|
||||
<p style="padding: 16px 24px">Hello from a plugin!</p>
|
||||
<div class="modal-footer">
|
||||
<span class="footer-info"></span>
|
||||
<button type="button" class="btn btn-ghost my-plugin-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-primary my-plugin-save">Save</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
if (!modal) {
|
||||
return; // Browser does not support <dialog>
|
||||
}
|
||||
modal.dialog
|
||||
.querySelector(".my-plugin-cancel")
|
||||
.addEventListener("click", () => modal.requestClose("cancel"));
|
||||
// Open it later, for example from a button click:
|
||||
// modal.showModal({trigger: button});
|
||||
});
|
||||
"""
|
||||
}
|
||||
EOF
|
||||
|
||||
"${PYTHON_CMD[@]}" -m datasette "$tmp_dir/demo.db" \
|
||||
--plugins-dir "$tmp_dir/plugins" --port "$PORT" &
|
||||
server_pid=$!
|
||||
|
||||
# Wait for the server to start responding
|
||||
for _ in $(seq 1 50); do
|
||||
if curl -s -o /dev/null "http://127.0.0.1:$PORT/"; then
|
||||
break
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
# Open the modal with animations disabled, blur the auto-focused Cancel
|
||||
# button so no focus ring appears, then take a retina viewport shot
|
||||
"${SHOT_SCRAPER_CMD[@]}" shot "http://127.0.0.1:$PORT/demo/plugins" \
|
||||
--javascript '
|
||||
new Promise((resolve) => {
|
||||
const style = document.createElement("style");
|
||||
style.textContent =
|
||||
"dialog, dialog::backdrop { animation: none !important; }";
|
||||
document.head.appendChild(style);
|
||||
const modal = document
|
||||
.getElementById("my-plugin-dialog")
|
||||
.closest("datasette-modal");
|
||||
modal.showModal();
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
setTimeout(resolve, 500);
|
||||
})' \
|
||||
--width 760 --height 460 --retina \
|
||||
--output "$tmp_dir/shot.png" --silent
|
||||
|
||||
kill "$server_pid" 2>/dev/null || true
|
||||
wait "$server_pid" 2>/dev/null || true
|
||||
server_pid=""
|
||||
|
||||
# Quantize to an 8-bit palette PNG to roughly halve the file size
|
||||
cat > "$tmp_dir/quantize.py" <<'EOF'
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
img = Image.open(sys.argv[1]).convert("RGB")
|
||||
img.quantize(
|
||||
colors=256,
|
||||
method=Image.Quantize.MEDIANCUT,
|
||||
dither=Image.Dither.FLOYDSTEINBERG,
|
||||
).save(sys.argv[2], optimize=True)
|
||||
EOF
|
||||
uv run --no-project --with pillow python \
|
||||
"$tmp_dir/quantize.py" "$tmp_dir/shot.png" "$output"
|
||||
|
||||
echo "Wrote $output"
|
||||
|
|
@ -107,6 +107,8 @@ The simplest way to create a modal from a plugin is ``datasetteManager.createMod
|
|||
|
||||
Calling ``modal.showModal()`` then displays the dialog:
|
||||
|
||||
.. Regenerate this screenshot with docs/generate-datasette-modal-example.sh
|
||||
|
||||
.. image:: datasette-modal-example.png
|
||||
:alt: A modal dialog titled "My plugin" containing the text "Hello from a plugin!" and Cancel and Save buttons, shown over a dimmed and blurred Datasette table page.
|
||||
:width: 550px
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue