vividly 0.0.1__py3-none-any.whl
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.
- vividly/__init__.py +1 -0
- vividly/cli.py +344 -0
- vividly/constants.py +25 -0
- vividly/resources/__init__.py +1 -0
- vividly/resources/starter-project/.github/copilot-instructions.md +10 -0
- vividly/resources/starter-project/.github/instructions/vividly.instructions.md +15 -0
- vividly/resources/starter-project/.github/skills/vividly-presentations/SKILL.md +17 -0
- vividly/resources/starter-project/site/index.html +20 -0
- vividly/resources/starter-project/site/slides/annotated-markdown/document.md +50 -0
- vividly/resources/starter-project/site/slides/contact-sheet/slide.css +45 -0
- vividly/resources/starter-project/site/slides/contact-sheet/slide.js +44 -0
- vividly/resources/starter-project/site/slides/diagram-build/slide.css +58 -0
- vividly/resources/starter-project/site/slides/diagram-build/slide.html +24 -0
- vividly/resources/starter-project/site/slides/diagram-build/slide.js +6 -0
- vividly/resources/starter-project/site/slides/markdown-controls/document.md +16 -0
- vividly/resources/starter-project/site/slides/markdown-controls/slide.css +43 -0
- vividly/resources/starter-project/site/slides/markdown-controls/slide.js +36 -0
- vividly/resources/starter-project/site/slides/photo-tour/data/photos.yaml +18 -0
- vividly/resources/starter-project/site/slides/photo-tour/slide.css +61 -0
- vividly/resources/starter-project/site/slides/photo-tour/slide.js +48 -0
- vividly/resources/starter-project/site/slides/presentation.yaml +85 -0
- vividly/resources/starter-project/site/slides/title-card/assets/hero.svg +20 -0
- vividly/resources/starter-project/site/slides/title-card/slide.css +35 -0
- vividly/resources/starter-project/site/slides/title-card/slide.html +7 -0
- vividly/resources/starter-project/site/slides/title-card/slide.js +9 -0
- vividly/resources/starter-project/site/thumbnails/images/contact-sheet.png +0 -0
- vividly/resources/starter-project/site/thumbnails/images/full-bleed-image.png +0 -0
- vividly/resources/starter-project/site/thumbnails/images/image-fit-modes.png +0 -0
- vividly/resources/starter-project/site/thumbnails/images/photo-tour.png +0 -0
- vividly/resources/starter-project/site/thumbnails/index.json +37 -0
- vividly/resources/starter-project/site/thumbnails/mechanics/annotated-markdown.png +0 -0
- vividly/resources/starter-project/site/thumbnails/mechanics/diagram-build.png +0 -0
- vividly/resources/starter-project/site/thumbnails/mechanics/markdown-controls.png +0 -0
- vividly/resources/starter-project/site/thumbnails/opening/title-card.png +0 -0
- vividly/resources/starter-project/site/vividly/assets/demo/aurora.svg +17 -0
- vividly/resources/starter-project/site/vividly/assets/demo/canyon.svg +12 -0
- vividly/resources/starter-project/site/vividly/assets/demo/dunes.svg +11 -0
- vividly/resources/starter-project/site/vividly/assets/demo/glacier.svg +12 -0
- vividly/resources/starter-project/site/vividly/assets/demo/harbor.svg +16 -0
- vividly/resources/starter-project/site/vividly/assets/demo/orchard.svg +14 -0
- vividly/resources/starter-project/site/vividly/css/base.css +627 -0
- vividly/resources/starter-project/site/vividly/css/controls.css +377 -0
- vividly/resources/starter-project/site/vividly/css/kit.css +40 -0
- vividly/resources/starter-project/site/vividly/fonts/README.md +3 -0
- vividly/resources/starter-project/site/vividly/runtime/bus.js +33 -0
- vividly/resources/starter-project/site/vividly/runtime/context.js +49 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/annotated-markdown.js +209 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/code.js +145 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/image.js +89 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/index.js +9 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/markdown.js +21 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/render.js +34 -0
- vividly/resources/starter-project/site/vividly/runtime/controls/source-control.js +102 -0
- vividly/resources/starter-project/site/vividly/runtime/manifest.js +57 -0
- vividly/resources/starter-project/site/vividly/runtime/presentation.js +863 -0
- vividly/resources/starter-project/site/vividly/runtime/slide.js +35 -0
- vividly/resources/starter-project/site/vividly/runtime/state.js +37 -0
- vividly/resources/starter-project/site/vividly/runtime/styles.js +53 -0
- vividly/resources/starter-project/site/vividly/slides/annotated-markdown.js +49 -0
- vividly/resources/starter-project/site/vividly/slides/image.js +40 -0
- vividly/resources/starter-project/site/vividly/themes/dark.css +37 -0
- vividly/resources/starter-project/site/vividly/vendor/highlight-github-dark.css +10 -0
- vividly/resources/starter-project/site/vividly/vendor/highlight.min.js +1213 -0
- vividly/resources/starter-project/site/vividly/vendor/js-yaml.min.js +2 -0
- vividly/resources/starter-project/site/vividly/vendor/marked.min.js +2456 -0
- vividly/resources/starter-project/site/vividly/version.txt +1 -0
- vividly/resources/starter-project/vividly/docs/background/overview.md +31 -0
- vividly/resources/starter-project/vividly/docs/background/presentation-orchestration.md +20 -0
- vividly/resources/starter-project/vividly/docs/presentation-orchestration-spec.md +674 -0
- vividly/resources/starter-project/vividly/docs/user/cli.md +61 -0
- vividly/thumbnails.py +467 -0
- vividly-0.0.1.dist-info/METADATA +153 -0
- vividly-0.0.1.dist-info/RECORD +77 -0
- vividly-0.0.1.dist-info/WHEEL +5 -0
- vividly-0.0.1.dist-info/entry_points.txt +2 -0
- vividly-0.0.1.dist-info/licenses/LICENSE +21 -0
- vividly-0.0.1.dist-info/top_level.txt +1 -0
vividly/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Vividly presentation builder and runner."""
|
vividly/cli.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Command-line interface for Vividly."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import functools
|
|
5
|
+
import hashlib
|
|
6
|
+
import http.server
|
|
7
|
+
import json
|
|
8
|
+
import shutil
|
|
9
|
+
from collections.abc import Callable, Generator
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from importlib import resources
|
|
12
|
+
from importlib.metadata import version
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TypedDict, cast
|
|
15
|
+
|
|
16
|
+
from .constants import (
|
|
17
|
+
BASELINE_NAME,
|
|
18
|
+
DEFAULT_PORT,
|
|
19
|
+
DEFAULT_SITE_ROOT,
|
|
20
|
+
EXISTING_FILE_LIST_LIMIT,
|
|
21
|
+
FRAMEWORK_VERSION_PATH,
|
|
22
|
+
IGNORED_NAMES,
|
|
23
|
+
MANAGED_ROLE,
|
|
24
|
+
SEED_PATH_PREFIXES,
|
|
25
|
+
SEED_ROLE,
|
|
26
|
+
SITE_INDEX_NAME,
|
|
27
|
+
STARTER_PROJECT_NAME,
|
|
28
|
+
STARTER_PROJECT_PACKAGE,
|
|
29
|
+
)
|
|
30
|
+
from .thumbnails import ThumbnailError, generate_thumbnails
|
|
31
|
+
|
|
32
|
+
type CommandHandler = Callable[[argparse.Namespace], None]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FileRecord(TypedDict):
|
|
36
|
+
"""Hash and upgrade policy for one file Vividly installed."""
|
|
37
|
+
|
|
38
|
+
sha256: str
|
|
39
|
+
role: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Baseline(TypedDict):
|
|
43
|
+
"""The set of files Vividly installed, as installed."""
|
|
44
|
+
|
|
45
|
+
framework_version: str
|
|
46
|
+
tool_version: str
|
|
47
|
+
files: dict[str, FileRecord]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def unimplemented_command(arguments: argparse.Namespace) -> None:
|
|
51
|
+
"""Report that a command is not implemented."""
|
|
52
|
+
command = cast(str, arguments.command)
|
|
53
|
+
print(f"vividly {command} is not implemented.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@contextmanager
|
|
57
|
+
def starter_project() -> Generator[Path, None, None]:
|
|
58
|
+
"""Yield a filesystem path to the packaged starter project."""
|
|
59
|
+
resource = resources.files(STARTER_PROJECT_PACKAGE).joinpath(STARTER_PROJECT_NAME)
|
|
60
|
+
with resources.as_file(resource) as path:
|
|
61
|
+
yield path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def framework_version(payload_root: Path) -> str:
|
|
65
|
+
"""Read the version the framework payload declares for itself."""
|
|
66
|
+
return (payload_root / FRAMEWORK_VERSION_PATH).read_text(encoding="utf-8").strip()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def tool_version() -> str:
|
|
70
|
+
"""Return the version of the installed vividly package."""
|
|
71
|
+
return version("vividly")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def version_banner() -> str:
|
|
75
|
+
"""Describe the tool and the framework version it can install."""
|
|
76
|
+
with starter_project() as starter:
|
|
77
|
+
return f"vividly {tool_version()} (bundled framework {framework_version(starter)})"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def hash_file(path: Path) -> str:
|
|
81
|
+
"""Return the SHA-256 digest of a file, with text line endings normalized to LF."""
|
|
82
|
+
data = path.read_bytes()
|
|
83
|
+
if b"\0" not in data:
|
|
84
|
+
data = data.replace(b"\r\n", b"\n")
|
|
85
|
+
return hashlib.sha256(data).hexdigest()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def file_role(relative_path: str) -> str:
|
|
89
|
+
"""Classify a shipped file as author content that update leaves alone, or as ours to upgrade."""
|
|
90
|
+
if relative_path.startswith(SEED_PATH_PREFIXES):
|
|
91
|
+
return SEED_ROLE
|
|
92
|
+
return MANAGED_ROLE
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def payload_files(payload_root: Path) -> list[Path]:
|
|
96
|
+
"""List the files a copy of the payload installs, sorted by relative path."""
|
|
97
|
+
files = [
|
|
98
|
+
path
|
|
99
|
+
for path in payload_root.rglob("*")
|
|
100
|
+
if path.is_file() and IGNORED_NAMES.isdisjoint(path.relative_to(payload_root).parts)
|
|
101
|
+
]
|
|
102
|
+
return sorted(files, key=lambda path: path.relative_to(payload_root).as_posix())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_baseline(payload_root: Path) -> Baseline:
|
|
106
|
+
"""Describe every file the payload installs, so update can detect later edits."""
|
|
107
|
+
files: dict[str, FileRecord] = {}
|
|
108
|
+
for path in payload_files(payload_root):
|
|
109
|
+
relative = path.relative_to(payload_root).as_posix()
|
|
110
|
+
files[relative] = {"sha256": hash_file(path), "role": file_role(relative)}
|
|
111
|
+
return {
|
|
112
|
+
"framework_version": framework_version(payload_root),
|
|
113
|
+
"tool_version": tool_version(),
|
|
114
|
+
"files": files,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def write_baseline(project_root: Path, baseline: Baseline) -> Path:
|
|
119
|
+
"""Write the baseline to the project root."""
|
|
120
|
+
baseline_path = project_root / BASELINE_NAME
|
|
121
|
+
baseline_path.write_text(json.dumps(baseline, indent=2) + "\n", encoding="utf-8")
|
|
122
|
+
return baseline_path
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def confirm(prompt: str) -> bool:
|
|
126
|
+
"""Ask the user to approve an action."""
|
|
127
|
+
try:
|
|
128
|
+
answer = input(f"{prompt} [y/N]: ")
|
|
129
|
+
except EOFError:
|
|
130
|
+
return False
|
|
131
|
+
return answer.strip().lower() in {"y", "yes"}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def existing_files(root: Path) -> list[str]:
|
|
135
|
+
"""List the files already in a directory, as paths relative to it."""
|
|
136
|
+
if not root.is_dir():
|
|
137
|
+
return []
|
|
138
|
+
return sorted(
|
|
139
|
+
path.relative_to(root).as_posix() for path in root.rglob("*") if path.is_file()
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def init_command(arguments: argparse.Namespace) -> None:
|
|
144
|
+
"""Create a new presentation from the packaged starter project."""
|
|
145
|
+
presentation_root = cast(Path, arguments.presentation_root).expanduser().resolve()
|
|
146
|
+
assume_yes = cast(bool, arguments.assume_yes)
|
|
147
|
+
|
|
148
|
+
if presentation_root.exists() and not presentation_root.is_dir():
|
|
149
|
+
raise SystemExit(f"vividly init: {presentation_root} is not a directory")
|
|
150
|
+
|
|
151
|
+
if (presentation_root / BASELINE_NAME).is_file():
|
|
152
|
+
raise SystemExit(
|
|
153
|
+
f"vividly init: {presentation_root} already holds a Vividly presentation "
|
|
154
|
+
f"({BASELINE_NAME}).\n"
|
|
155
|
+
"Run vividly update to upgrade it, or choose an empty directory."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
existing = existing_files(presentation_root)
|
|
159
|
+
if existing:
|
|
160
|
+
shown = existing[:EXISTING_FILE_LIST_LIMIT]
|
|
161
|
+
listing = "\n".join(f" {name}" for name in shown)
|
|
162
|
+
remainder = len(existing) - len(shown)
|
|
163
|
+
if remainder:
|
|
164
|
+
listing += f"\n ...and {remainder} more"
|
|
165
|
+
plural = "" if len(existing) == 1 else "s"
|
|
166
|
+
raise SystemExit(
|
|
167
|
+
f"vividly init: {presentation_root} already contains {len(existing)} file{plural}:\n"
|
|
168
|
+
f"{listing}\n"
|
|
169
|
+
"Choose an empty directory, or remove these files before initializing.\n"
|
|
170
|
+
"If these are Vividly files from an older layout, run vividly update instead."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
with starter_project() as starter:
|
|
174
|
+
print(f"Vividly will create a new presentation in {presentation_root}")
|
|
175
|
+
print("It copies the starter project, which adds:")
|
|
176
|
+
for entry in sorted(starter.iterdir(), key=lambda path: path.name):
|
|
177
|
+
suffix = "/" if entry.is_dir() else ""
|
|
178
|
+
print(f" {entry.name}{suffix}")
|
|
179
|
+
print(f"and records the installed files in {BASELINE_NAME}.")
|
|
180
|
+
|
|
181
|
+
if not assume_yes and not confirm("Proceed?"):
|
|
182
|
+
print("vividly init: cancelled. Nothing was written.")
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
shutil.copytree(
|
|
186
|
+
starter,
|
|
187
|
+
presentation_root,
|
|
188
|
+
dirs_exist_ok=True,
|
|
189
|
+
ignore=shutil.ignore_patterns(*IGNORED_NAMES),
|
|
190
|
+
)
|
|
191
|
+
baseline = build_baseline(starter)
|
|
192
|
+
|
|
193
|
+
baseline_path = write_baseline(presentation_root, baseline)
|
|
194
|
+
print(f"Created presentation in {presentation_root}")
|
|
195
|
+
print(f"Recorded {len(baseline['files'])} files in {baseline_path}")
|
|
196
|
+
print('\nUse "vividly run" to edit your presentation.')
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def run_command(arguments: argparse.Namespace) -> None:
|
|
200
|
+
"""Run a Vividly presentation locally."""
|
|
201
|
+
port = cast(int, arguments.port)
|
|
202
|
+
presentation_root = cast(Path, arguments.presentation_root).expanduser().resolve()
|
|
203
|
+
site_root = presentation_root / DEFAULT_SITE_ROOT
|
|
204
|
+
if not (site_root / SITE_INDEX_NAME).is_file():
|
|
205
|
+
raise SystemExit(
|
|
206
|
+
f"vividly run: no presentation site found at {site_root}\n"
|
|
207
|
+
"Run this command from a Vividly presentation root or pass its path."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
handler = functools.partial(
|
|
211
|
+
http.server.SimpleHTTPRequestHandler,
|
|
212
|
+
directory=str(site_root),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
with http.server.ThreadingHTTPServer(("", port), handler) as server:
|
|
216
|
+
print(f"Serving {site_root} at http://localhost:{server.server_port}")
|
|
217
|
+
try:
|
|
218
|
+
server.serve_forever()
|
|
219
|
+
except KeyboardInterrupt:
|
|
220
|
+
print("\nServer stopped.")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def thumbnails_command(arguments: argparse.Namespace) -> None:
|
|
224
|
+
"""Generate all thumbnails or a selected subset."""
|
|
225
|
+
presentation_root = cast(Path, arguments.presentation_root)
|
|
226
|
+
addresses = cast(list[str] | None, arguments.slides)
|
|
227
|
+
|
|
228
|
+
def report_progress(position: int, total: int, address: str) -> None:
|
|
229
|
+
print(f"Capturing {position}/{total}: {address}", flush=True)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
results = generate_thumbnails(
|
|
233
|
+
presentation_root,
|
|
234
|
+
addresses,
|
|
235
|
+
on_progress=report_progress,
|
|
236
|
+
)
|
|
237
|
+
except ThumbnailError as error:
|
|
238
|
+
raise SystemExit(f"vividly thumbnails: {error}") from error
|
|
239
|
+
for result in results:
|
|
240
|
+
print(f"Captured {result.address} at step {result.step}: {result.path}")
|
|
241
|
+
count = len(results)
|
|
242
|
+
noun = "thumbnail" if count == 1 else "thumbnails"
|
|
243
|
+
print(f"Generated {count} {noun}.")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
COMMANDS: tuple[tuple[str, str, CommandHandler], ...] = (
|
|
247
|
+
("init", "Initialize a new presentation", init_command),
|
|
248
|
+
("update", "Update the Vividly runtime", unimplemented_command),
|
|
249
|
+
("run", "Run a presentation locally", run_command),
|
|
250
|
+
("install-browser", "Install Chromium for thumbnail generation", unimplemented_command),
|
|
251
|
+
("thumbnails", "Regenerate committed slide thumbnails", thumbnails_command),
|
|
252
|
+
("publish", "Publish a presentation", unimplemented_command),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def port_number(value: str) -> int:
|
|
257
|
+
"""Parse a valid TCP port number."""
|
|
258
|
+
port = int(value)
|
|
259
|
+
if not 1 <= port <= 65535:
|
|
260
|
+
raise argparse.ArgumentTypeError("port must be between 1 and 65535")
|
|
261
|
+
return port
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
265
|
+
"""Create the command-line argument parser."""
|
|
266
|
+
parser = argparse.ArgumentParser(
|
|
267
|
+
prog="vividly",
|
|
268
|
+
description="Build, run, and publish Vividly presentations.",
|
|
269
|
+
)
|
|
270
|
+
parser.add_argument(
|
|
271
|
+
"--version",
|
|
272
|
+
action="version",
|
|
273
|
+
version=version_banner(),
|
|
274
|
+
help="show the CLI version and the runtime version it installs",
|
|
275
|
+
)
|
|
276
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
277
|
+
|
|
278
|
+
for name, help_text, handler in COMMANDS:
|
|
279
|
+
command_parser = subparsers.add_parser(name, help=help_text)
|
|
280
|
+
command_parser.set_defaults(handler=handler)
|
|
281
|
+
if name == "init":
|
|
282
|
+
command_parser.add_argument(
|
|
283
|
+
"presentation_root",
|
|
284
|
+
type=Path,
|
|
285
|
+
nargs="?",
|
|
286
|
+
default=Path.cwd(),
|
|
287
|
+
metavar="PATH",
|
|
288
|
+
help="empty directory for the new presentation (default: current directory)",
|
|
289
|
+
)
|
|
290
|
+
command_parser.add_argument(
|
|
291
|
+
"-y",
|
|
292
|
+
"--yes",
|
|
293
|
+
dest="assume_yes",
|
|
294
|
+
action="store_true",
|
|
295
|
+
help="skip the confirmation prompt",
|
|
296
|
+
)
|
|
297
|
+
if name == "run":
|
|
298
|
+
command_parser.add_argument(
|
|
299
|
+
"presentation_root",
|
|
300
|
+
type=Path,
|
|
301
|
+
nargs="?",
|
|
302
|
+
default=Path.cwd(),
|
|
303
|
+
metavar="PATH",
|
|
304
|
+
help="presentation root containing site/ (default: current directory)",
|
|
305
|
+
)
|
|
306
|
+
command_parser.add_argument(
|
|
307
|
+
"--port",
|
|
308
|
+
type=port_number,
|
|
309
|
+
default=DEFAULT_PORT,
|
|
310
|
+
help=f"port for the local server (default: {DEFAULT_PORT})",
|
|
311
|
+
)
|
|
312
|
+
if name == "thumbnails":
|
|
313
|
+
command_parser.add_argument(
|
|
314
|
+
"presentation_root",
|
|
315
|
+
type=Path,
|
|
316
|
+
nargs="?",
|
|
317
|
+
default=Path.cwd(),
|
|
318
|
+
metavar="PATH",
|
|
319
|
+
help="presentation root containing site/ (default: current directory)",
|
|
320
|
+
)
|
|
321
|
+
command_parser.add_argument(
|
|
322
|
+
"--slide",
|
|
323
|
+
action="append",
|
|
324
|
+
dest="slides",
|
|
325
|
+
metavar="ADDRESS",
|
|
326
|
+
help="complete manifest address to capture (repeatable; default: all slides)",
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return parser
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def main() -> None:
|
|
333
|
+
"""Run the Vividly command-line interface."""
|
|
334
|
+
parser = create_parser()
|
|
335
|
+
arguments = parser.parse_args()
|
|
336
|
+
if arguments.command is None:
|
|
337
|
+
parser.print_help()
|
|
338
|
+
return
|
|
339
|
+
handler = cast(CommandHandler, arguments.handler)
|
|
340
|
+
handler(arguments)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if __name__ == "__main__":
|
|
344
|
+
main()
|
vividly/constants.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Shared defaults for Vividly."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
DEFAULT_SITE_ROOT = Path("site")
|
|
6
|
+
DEFAULT_PORT = 8080
|
|
7
|
+
|
|
8
|
+
SITE_INDEX_NAME = "index.html"
|
|
9
|
+
|
|
10
|
+
# The framework declares its own version, so it can later ship separately from the tool.
|
|
11
|
+
FRAMEWORK_VERSION_PATH = Path("site/vividly/version.txt")
|
|
12
|
+
|
|
13
|
+
# Records what Vividly shipped, so update can tell its own files from the author's.
|
|
14
|
+
BASELINE_NAME = "vividly-baseline.json"
|
|
15
|
+
|
|
16
|
+
EXISTING_FILE_LIST_LIMIT = 10
|
|
17
|
+
|
|
18
|
+
MANAGED_ROLE = "managed"
|
|
19
|
+
SEED_ROLE = "seed"
|
|
20
|
+
SEED_PATH_PREFIXES = ("site/slides/",)
|
|
21
|
+
|
|
22
|
+
IGNORED_NAMES = frozenset({".DS_Store", "Thumbs.db", "__pycache__"})
|
|
23
|
+
|
|
24
|
+
STARTER_PROJECT_PACKAGE = "vividly.resources"
|
|
25
|
+
STARTER_PROJECT_NAME = "starter-project"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged resources for creating Vividly presentation projects."""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Vividly Presentation Project
|
|
2
|
+
|
|
3
|
+
This repository contains a Vividly presentation. Keep the deployable presentation inside `site/`.
|
|
4
|
+
|
|
5
|
+
- Read `vividly/docs/` before changing runtime behavior or presentation structure.
|
|
6
|
+
- Treat `site/vividly/` as installed runtime infrastructure. Do not edit it while authoring slides.
|
|
7
|
+
- Define presentation order and configuration in `site/slides/presentation.yaml`.
|
|
8
|
+
- Put each slide in its own directory under `site/slides/` and keep slide-specific scripts, styles, data, assets, and vendored dependencies there.
|
|
9
|
+
- Put presentation-wide assets, fonts, themes, and vendored dependencies under `site/vividly/`.
|
|
10
|
+
- Preserve browser-native ES modules and the no-build-step deployment model.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Use when creating or modifying Vividly slides, presentation manifests, themes, assets, or other files under the hosted site."
|
|
3
|
+
applyTo:
|
|
4
|
+
- "site/slides/**"
|
|
5
|
+
- "site/vividly/themes/**"
|
|
6
|
+
---
|
|
7
|
+
# Vividly Authoring Instructions
|
|
8
|
+
|
|
9
|
+
- Read `vividly/docs/presentation-orchestration-spec.md` for the runtime contract.
|
|
10
|
+
- Keep every slide independently authorable in its own `site/slides/<slide-id>/` directory.
|
|
11
|
+
- Use browser-native ES modules; do not introduce a compilation or bundling step.
|
|
12
|
+
- Resolve slide-local templates, styles, data, assets, and vendored modules relative to the slide module.
|
|
13
|
+
- Register slides in `site/slides/presentation.yaml`; do not hard-code presentation order in the runtime.
|
|
14
|
+
- Put reusable presentation styling in `site/vividly/themes/` rather than leaking slide CSS globally.
|
|
15
|
+
- Do not modify `site/vividly/runtime/` during ordinary presentation authoring.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vividly-presentations
|
|
3
|
+
description: "Create and revise Vividly web presentations. Use when adding slides, arranging the presentation manifest, authoring slide steps, or working with presentation assets and themes."
|
|
4
|
+
argument-hint: "Describe the presentation or slide change"
|
|
5
|
+
---
|
|
6
|
+
# Vividly Presentations
|
|
7
|
+
|
|
8
|
+
## Procedure
|
|
9
|
+
|
|
10
|
+
1. Read `vividly/docs/presentation-orchestration-spec.md` and inspect `site/slides/presentation.yaml`.
|
|
11
|
+
2. Inspect neighboring slides for established visual and structural conventions.
|
|
12
|
+
3. Create or update one self-contained directory under `site/slides/` for each affected slide.
|
|
13
|
+
4. Keep slide-local markup, CSS, JavaScript, data, assets, and dependencies inside that directory.
|
|
14
|
+
5. Update `site/slides/presentation.yaml` when slides or presentation structure change.
|
|
15
|
+
6. Run `vividly run`, open the reported URL, and verify navigation, builds, layout, and assets in the browser.
|
|
16
|
+
|
|
17
|
+
Do not edit `site/vividly/runtime/` merely to accommodate one slide. Runtime changes require an explicit presentation-system task.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Vividly</title>
|
|
7
|
+
|
|
8
|
+
<link rel="stylesheet" href="vividly/css/base.css">
|
|
9
|
+
<link rel="stylesheet" href="vividly/css/kit.css">
|
|
10
|
+
<link rel="stylesheet" href="vividly/css/controls.css">
|
|
11
|
+
|
|
12
|
+
<script src="vividly/vendor/js-yaml.min.js"></script>
|
|
13
|
+
<script src="vividly/vendor/marked.min.js"></script>
|
|
14
|
+
<script src="vividly/vendor/highlight.min.js"></script>
|
|
15
|
+
<script type="module" src="vividly/runtime/presentation.js"></script>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<vividly-presentation manifest="./slides/presentation.yaml"></vividly-presentation>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<vividly-document-header title="Annotated Markdown">
|
|
2
|
+
A document reader whose annotations form the slide's step sequence.
|
|
3
|
+
</vividly-document-header>
|
|
4
|
+
|
|
5
|
+
# Controls and slides
|
|
6
|
+
|
|
7
|
+
Vividly controls render regions of a slide. They expose state as properties and events,
|
|
8
|
+
but they do not know about the presentation's step axis.
|
|
9
|
+
|
|
10
|
+
<vividly-annotation title="The control boundary">
|
|
11
|
+
The viewer can appear beside other content or more than once on a slide. That makes it
|
|
12
|
+
a control rather than a slide module.
|
|
13
|
+
</vividly-annotation>
|
|
14
|
+
|
|
15
|
+
## A thin adapter
|
|
16
|
+
|
|
17
|
+
The reusable slide module loads this document, counts its annotations, and maps each
|
|
18
|
+
step to `activeAnnotation`.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
setStep(step) {
|
|
22
|
+
this.viewer.revealAnnotation(step);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
<vividly-annotation title="Absolute steps">
|
|
27
|
+
Selecting annotation 1 always establishes the same state. The viewer does not need to
|
|
28
|
+
know whether the presenter moved forward, backward, or followed a deep link.
|
|
29
|
+
</vividly-annotation>
|
|
30
|
+
|
|
31
|
+
## Independent navigation
|
|
32
|
+
|
|
33
|
+
Select an annotation from the left pane or use the presentation arrow keys. Both paths
|
|
34
|
+
end at the same slide step and URL.
|
|
35
|
+
|
|
36
|
+
<vividly-annotation title="One source of truth">
|
|
37
|
+
Pointer interaction emits an event. The adapter asks the orchestrator to navigate, and
|
|
38
|
+
the orchestrator writes the resulting step back to the control.
|
|
39
|
+
</vividly-annotation>
|
|
40
|
+
|
|
41
|
+
## Hidden mounting
|
|
42
|
+
|
|
43
|
+
The runtime mounts nearby slides while their layers are hidden. Annotation selection is
|
|
44
|
+
safe at that point; scrolling waits until the current task finishes and layout is
|
|
45
|
+
available.
|
|
46
|
+
|
|
47
|
+
<vividly-annotation title="Deferred scrolling">
|
|
48
|
+
The deferred operation is cancellable and does not depend on animation frames, which
|
|
49
|
+
may stop while a browser tab is in the background.
|
|
50
|
+
</vividly-annotation>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
:scope {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-rows: auto 1fr;
|
|
4
|
+
gap: var(--spacing-lg);
|
|
5
|
+
padding: var(--spacing-xl);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.sheet-heading {
|
|
9
|
+
margin: 0;
|
|
10
|
+
font-family: var(--font-display);
|
|
11
|
+
font-size: var(--font-size-xl);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.sheet-grid {
|
|
15
|
+
display: grid;
|
|
16
|
+
grid-template-columns: repeat(3, 1fr);
|
|
17
|
+
gap: var(--spacing-lg);
|
|
18
|
+
align-content: start;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* --on comes from .v-at and is 1 only on this tile's step. */
|
|
22
|
+
.sheet-tile {
|
|
23
|
+
margin: 0;
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
opacity: calc(0.4 + 0.6 * var(--on));
|
|
26
|
+
transform: scale(calc(0.96 + 0.04 * var(--on)));
|
|
27
|
+
outline: 2px solid rgb(79 195 247 / var(--on));
|
|
28
|
+
outline-offset: 4px;
|
|
29
|
+
border-radius: var(--radius-md);
|
|
30
|
+
transition: opacity 300ms ease, transform 300ms ease, outline-color 300ms ease;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sheet-tile img {
|
|
34
|
+
display: block;
|
|
35
|
+
width: 100%;
|
|
36
|
+
aspect-ratio: 8 / 5;
|
|
37
|
+
object-fit: cover;
|
|
38
|
+
border-radius: var(--radius-md);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sheet-tile figcaption {
|
|
42
|
+
margin-top: var(--spacing-xs);
|
|
43
|
+
font-size: var(--font-size-sm);
|
|
44
|
+
color: var(--text-muted);
|
|
45
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Image slide: a grid where the current step highlights one tile. Highlight is
|
|
2
|
+
// pure CSS via the kit's .v-at primitive; JS only builds the grid.
|
|
3
|
+
|
|
4
|
+
import { Slide } from '../../vividly/runtime/slide.js';
|
|
5
|
+
|
|
6
|
+
export const meta = {
|
|
7
|
+
title: 'Contact Sheet',
|
|
8
|
+
styles: ['./slide.css'],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const IMAGES = [
|
|
12
|
+
{ src: 'aurora', label: 'Aurora' },
|
|
13
|
+
{ src: 'canyon', label: 'Canyon' },
|
|
14
|
+
{ src: 'harbor', label: 'Harbor' },
|
|
15
|
+
{ src: 'dunes', label: 'Dunes' },
|
|
16
|
+
{ src: 'glacier', label: 'Glacier' },
|
|
17
|
+
{ src: 'orchard', label: 'Orchard' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default class ContactSheet extends Slide {
|
|
21
|
+
async preload() {
|
|
22
|
+
return { steps: IMAGES.length };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
mount(root) {
|
|
26
|
+
root.innerHTML = `
|
|
27
|
+
<h2 class="sheet-heading">Contact Sheet</h2>
|
|
28
|
+
<div class="sheet-grid">
|
|
29
|
+
${IMAGES.map(
|
|
30
|
+
(img, i) => `
|
|
31
|
+
<figure class="sheet-tile v-at" style="--at: ${i}" data-index="${i}">
|
|
32
|
+
<img src="${this.ctx.asset(`../../vividly/assets/demo/${img.src}.svg`)}" alt="${img.label}">
|
|
33
|
+
<figcaption>${img.label}</figcaption>
|
|
34
|
+
</figure>`
|
|
35
|
+
).join('')}
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
root.addEventListener('click', (e) => {
|
|
40
|
+
const tile = e.target.closest('.sheet-tile');
|
|
41
|
+
if (tile) this.ctx.navigate(this.ctx.address, Number(tile.dataset.index));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
:scope {
|
|
2
|
+
display: grid;
|
|
3
|
+
align-content: center;
|
|
4
|
+
gap: var(--spacing-xl);
|
|
5
|
+
padding: var(--spacing-xl);
|
|
6
|
+
max-width: 62rem;
|
|
7
|
+
margin-inline: auto;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
h2 {
|
|
11
|
+
margin: 0;
|
|
12
|
+
font-family: var(--font-display);
|
|
13
|
+
font-size: var(--font-size-xl);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.pipeline {
|
|
17
|
+
list-style: none;
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
display: grid;
|
|
21
|
+
gap: var(--spacing-md);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.stage {
|
|
25
|
+
--reveal-rise: 20px;
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: auto 1fr;
|
|
28
|
+
grid-template-areas: 'num title' 'num caption';
|
|
29
|
+
column-gap: var(--spacing-md);
|
|
30
|
+
align-items: center;
|
|
31
|
+
padding: var(--spacing-md) var(--spacing-lg);
|
|
32
|
+
background: var(--bg-elevated);
|
|
33
|
+
border-left: 3px solid var(--accent);
|
|
34
|
+
border-radius: var(--radius-md);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.stage-num {
|
|
38
|
+
grid-area: num;
|
|
39
|
+
display: grid;
|
|
40
|
+
place-items: center;
|
|
41
|
+
width: 2rem;
|
|
42
|
+
height: 2rem;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
background: var(--accent);
|
|
45
|
+
color: var(--text-inverse);
|
|
46
|
+
font-variant-numeric: tabular-nums;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.stage strong {
|
|
51
|
+
grid-area: title;
|
|
52
|
+
font-family: var(--font-mono);
|
|
53
|
+
font-size: var(--font-size-md);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.stage .v-caption {
|
|
57
|
+
grid-area: caption;
|
|
58
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<h2>How a slide gets on screen</h2>
|
|
2
|
+
|
|
3
|
+
<ol class="pipeline">
|
|
4
|
+
<li class="stage v-reveal" style="--at: 0">
|
|
5
|
+
<span class="stage-num">1</span>
|
|
6
|
+
<strong>import()</strong>
|
|
7
|
+
<span class="v-caption">The module loads. Styles are scoped and adopted.</span>
|
|
8
|
+
</li>
|
|
9
|
+
<li class="stage v-reveal" style="--at: 1">
|
|
10
|
+
<span class="stage-num">2</span>
|
|
11
|
+
<strong>preload()</strong>
|
|
12
|
+
<span class="v-caption">Data is fetched. The slide declares its step count.</span>
|
|
13
|
+
</li>
|
|
14
|
+
<li class="stage v-reveal" style="--at: 2">
|
|
15
|
+
<span class="stage-num">3</span>
|
|
16
|
+
<strong>mount()</strong>
|
|
17
|
+
<span class="v-caption">DOM is built while hidden, so transitions can cross-fade.</span>
|
|
18
|
+
</li>
|
|
19
|
+
<li class="stage v-reveal" style="--at: 3">
|
|
20
|
+
<span class="stage-num">4</span>
|
|
21
|
+
<strong>enter() → setStep()</strong>
|
|
22
|
+
<span class="v-caption">The runtime writes data-step and --step. CSS takes over.</span>
|
|
23
|
+
</li>
|
|
24
|
+
</ol>
|