nucli 0.3.0__tar.gz → 0.5.0__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.
- {nucli-0.3.0 → nucli-0.5.0}/PKG-INFO +4 -4
- {nucli-0.3.0 → nucli-0.5.0}/README.md +2 -2
- {nucli-0.3.0 → nucli-0.5.0}/pyproject.toml +7 -8
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/__init__.py +1 -1
- nucli-0.5.0/src/nucli/__main__.py +7 -0
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/_meta.py +2 -2
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/commands/demo.py +1 -1
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/commands/doctor.py +1 -1
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/demos/counter.py +23 -22
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/demos/movies.py +145 -142
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/demos/sampled.py +23 -22
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/main.py +4 -4
- nucli-0.3.0/src/nu/_cli/__main__.py +0 -7
- {nucli-0.3.0 → nucli-0.5.0}/.gitignore +0 -0
- {nucli-0.3.0 → nucli-0.5.0}/LICENSE.md +0 -0
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/commands/__init__.py +0 -0
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/commands/telemetry.py +0 -0
- {nucli-0.3.0/src/nu/_cli → nucli-0.5.0/src/nucli}/demos/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nucli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: The `nu` command line: doctor, demos, telemetry. Ships alongside the nucore kernel and the nustd fabrics.
|
|
5
5
|
Project-URL: Repository, https://github.com/nustackdev/nu
|
|
6
6
|
Author-email: Gor Arakelyan <gorarkln@gmail.com>
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Requires-Dist: click>=8.0
|
|
19
|
-
Requires-Dist: nustd>=0.
|
|
19
|
+
Requires-Dist: nustd>=0.5.0
|
|
20
20
|
Requires-Dist: rich-click>=1.6
|
|
21
21
|
Requires-Dist: rich>=10.7
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
@@ -34,10 +34,10 @@ nu demo movies # run a packaged demo app
|
|
|
34
34
|
nu telemetry # see / change telemetry settings
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
It
|
|
37
|
+
It imports as `nucli`, installs the `nu` command, and depends on
|
|
38
38
|
[`nustd`](https://pypi.org/project/nustd/) (which pulls the
|
|
39
39
|
[`nucore`](https://pypi.org/project/nucore/) kernel) because the
|
|
40
|
-
packaged demos run on `
|
|
40
|
+
packaged demos run on `nustd.ui` and `nustd.kv` at runtime.
|
|
41
41
|
|
|
42
42
|
You normally do not install this directly - `pip install "nustd[all]" nucli`
|
|
43
43
|
brings it along. Released in lockstep with the kernel. Docs at
|
|
@@ -11,10 +11,10 @@ nu demo movies # run a packaged demo app
|
|
|
11
11
|
nu telemetry # see / change telemetry settings
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
It
|
|
14
|
+
It imports as `nucli`, installs the `nu` command, and depends on
|
|
15
15
|
[`nustd`](https://pypi.org/project/nustd/) (which pulls the
|
|
16
16
|
[`nucore`](https://pypi.org/project/nucore/) kernel) because the
|
|
17
|
-
packaged demos run on `
|
|
17
|
+
packaged demos run on `nustd.ui` and `nustd.kv` at runtime.
|
|
18
18
|
|
|
19
19
|
You normally do not install this directly - `pip install "nustd[all]" nucli`
|
|
20
20
|
brings it along. Released in lockstep with the kernel. Docs at
|
|
@@ -4,18 +4,18 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nucli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "The `nu` command line: doctor, demos, telemetry. Ships alongside the nucore kernel and the nustd fabrics."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
11
11
|
authors = [{ name = "Gor Arakelyan", email = "gorarkln@gmail.com" }]
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
# Released in lockstep with the kernel. Carries no `nu/__init__.py` -- it drops
|
|
14
|
-
# `
|
|
14
|
+
# `nucli` into the `nu.` namespace the kernel owns. Depends on `nustd` rather
|
|
15
15
|
# than the bare kernel because the packaged demos (`nu demo movies`) run on
|
|
16
|
-
# `
|
|
16
|
+
# `nustd.ui` and `nustd.kv` at runtime.
|
|
17
17
|
dependencies = [
|
|
18
|
-
"nustd>=0.
|
|
18
|
+
"nustd>=0.5.0",
|
|
19
19
|
"click>=8.0",
|
|
20
20
|
"rich>=10.7",
|
|
21
21
|
"rich-click>=1.6",
|
|
@@ -52,9 +52,8 @@ classifiers = [
|
|
|
52
52
|
Repository = "https://github.com/nustackdev/nu"
|
|
53
53
|
|
|
54
54
|
[project.scripts]
|
|
55
|
-
nu = "
|
|
55
|
+
nu = "nucli:main"
|
|
56
56
|
|
|
57
57
|
[tool.hatch.build.targets.wheel]
|
|
58
|
-
packages = ["src/
|
|
59
|
-
#
|
|
60
|
-
# and the installs merge into one `site-packages/nu/`.
|
|
58
|
+
packages = ["src/nucli"]
|
|
59
|
+
# The import name is `nucli`; the command it installs is `nu`.
|
|
@@ -15,8 +15,8 @@ def nu_version() -> str:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def demos_root() -> Path:
|
|
18
|
-
"""Locate packaged demo scripts (ships as
|
|
19
|
-
return Path(str(resources.files("
|
|
18
|
+
"""Locate packaged demo scripts (ships as nucli.demos)."""
|
|
19
|
+
return Path(str(resources.files("nucli").joinpath("demos")))
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def demos() -> dict[str, Path]:
|
|
@@ -10,8 +10,8 @@ from rich.console import Console
|
|
|
10
10
|
from rich.table import Table
|
|
11
11
|
from rich.text import Text
|
|
12
12
|
|
|
13
|
-
from nu._cli._meta import nu_version
|
|
14
13
|
from nu._config.branding import BLUE, PURPLE
|
|
14
|
+
from nucli._meta import nu_version
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
# Fabric extras (as declared in packages/nustd/pyproject.toml) and the import
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import nu
|
|
6
|
+
import nustd
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
_DB = Path.home() / ".nu" / "demos" / "counter"
|
|
@@ -12,30 +13,30 @@ _DB.parent.mkdir(parents=True, exist_ok=True)
|
|
|
12
13
|
class Counter(nu.Shape):
|
|
13
14
|
"""Persistent counter state."""
|
|
14
15
|
|
|
15
|
-
value:
|
|
16
|
+
value: nustd.kv.IntRef
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class Links(
|
|
19
|
-
docs =
|
|
19
|
+
class Links(nustd.ui.Row):
|
|
20
|
+
docs = nustd.ui.LinkRef.slot(
|
|
20
21
|
label="Read the docs", href="https://nustack.dev/docs", target="_blank"
|
|
21
22
|
)
|
|
22
|
-
github =
|
|
23
|
+
github = nustd.ui.LinkRef.slot(
|
|
23
24
|
label="Star on GitHub", href="https://github.com/nustackdev/nu", target="_blank"
|
|
24
25
|
)
|
|
25
|
-
examples =
|
|
26
|
+
examples = nustd.ui.LinkRef.slot(
|
|
26
27
|
label="Browse more demos",
|
|
27
28
|
href="https://github.com/nustackdev/nu/tree/main/examples",
|
|
28
29
|
target="_blank",
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
class Dashboard(
|
|
33
|
+
class Dashboard(nustd.ui.Page):
|
|
33
34
|
"""Live counter page with description and source."""
|
|
34
35
|
|
|
35
|
-
heading =
|
|
36
|
-
count =
|
|
37
|
-
about_heading =
|
|
38
|
-
about =
|
|
36
|
+
heading = nustd.ui.HeadingRef.slot(label="Persistent counter, live")
|
|
37
|
+
count = nustd.ui.StatRef.slot(label="Count")
|
|
38
|
+
about_heading = nustd.ui.HeadingRef.slot(label="How it works")
|
|
39
|
+
about = nustd.ui.MarkdownRef.slot(
|
|
39
40
|
value=(
|
|
40
41
|
"- Stores counter value in rocksdb.\n"
|
|
41
42
|
"- App increments the counter once a second.\n"
|
|
@@ -44,38 +45,38 @@ class Dashboard(nu.ui.Page):
|
|
|
44
45
|
"- Same Interactions used to orchestrate storage and UI update.\n"
|
|
45
46
|
),
|
|
46
47
|
)
|
|
47
|
-
links_heading =
|
|
48
|
-
links_intro =
|
|
48
|
+
links_heading = nustd.ui.HeadingRef.slot(label="Try Nu yourself")
|
|
49
|
+
links_intro = nustd.ui.TextRef.slot(
|
|
49
50
|
value="Persistent state, live browser, no glue. See how far the primitive goes.",
|
|
50
51
|
)
|
|
51
52
|
links = Links.slot(gap=4, align="center", wrap=True)
|
|
52
|
-
source_heading =
|
|
53
|
-
source_intro =
|
|
53
|
+
source_heading = nustd.ui.HeadingRef.slot(label="Source")
|
|
54
|
+
source_intro = nustd.ui.TextRef.slot(
|
|
54
55
|
value="The whole app, one file. Storage, UI, and the wires between them.",
|
|
55
56
|
)
|
|
56
|
-
source =
|
|
57
|
+
source = nustd.ui.CodeBlockRef.slot(
|
|
57
58
|
code=Path(__file__).read_text(),
|
|
58
59
|
language="python",
|
|
59
60
|
)
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
class App(
|
|
63
|
+
class App(nustd.ui.Index):
|
|
63
64
|
"""UI index with one page."""
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
home = Dashboard.slot("/")
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
app = nu.With(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
nustd.kv.rocksdb_navigator(str(_DB)),
|
|
71
|
+
nustd.ui.server(
|
|
72
|
+
nustd.kv.auto_flow_atomic(
|
|
72
73
|
nu.ReactForever(
|
|
73
74
|
Counter.value.on_change(),
|
|
74
|
-
|
|
75
|
+
App.home.count.set_value(nu.str(Counter.value)),
|
|
75
76
|
),
|
|
76
77
|
),
|
|
77
78
|
),
|
|
78
|
-
body=
|
|
79
|
+
body=nustd.kv.auto_flow_atomic(
|
|
79
80
|
nu.IfDo(Counter.value.missing(), Counter.value.set(0))
|
|
80
81
|
>> nu.ForeverDo(
|
|
81
82
|
Counter.value.inc() >> nu.Delay(1.0),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import nu
|
|
6
|
+
import nustd
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
_DB = Path.home() / ".nu" / "demos" / "movies"
|
|
@@ -23,16 +24,16 @@ _GENRES_WITH_ANY = [{"value": "", "label": "Any genre"}, *_GENRES]
|
|
|
23
24
|
# ---- UI ---------------------------------------------------------------------
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class TitleField(
|
|
27
|
-
input =
|
|
27
|
+
class TitleField(nustd.ui.Field):
|
|
28
|
+
input = nustd.ui.InputRef.slot(placeholder="e.g. Arrival")
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
class TextAreaField(
|
|
31
|
-
input =
|
|
31
|
+
class TextAreaField(nustd.ui.Field):
|
|
32
|
+
input = nustd.ui.TextAreaRef.slot(placeholder="Quick thoughts...", rows=2)
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
class YearField(
|
|
35
|
-
input =
|
|
35
|
+
class YearField(nustd.ui.Field):
|
|
36
|
+
input = nustd.ui.NumberInputRef.slot(
|
|
36
37
|
min=1900.0,
|
|
37
38
|
max=2100.0,
|
|
38
39
|
step=1.0,
|
|
@@ -40,8 +41,8 @@ class YearField(nu.ui.Field):
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
class RatingField(
|
|
44
|
-
input =
|
|
44
|
+
class RatingField(nustd.ui.Field):
|
|
45
|
+
input = nustd.ui.NumberInputRef.slot(
|
|
45
46
|
min=1.0,
|
|
46
47
|
max=10.0,
|
|
47
48
|
step=0.5,
|
|
@@ -49,96 +50,96 @@ class RatingField(nu.ui.Field):
|
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
class GenreField(
|
|
53
|
-
input =
|
|
53
|
+
class GenreField(nustd.ui.Field):
|
|
54
|
+
input = nustd.ui.SelectRef.slot(options=_GENRES, selected="drama")
|
|
54
55
|
|
|
55
56
|
|
|
56
|
-
class SwitchField(
|
|
57
|
-
input =
|
|
57
|
+
class SwitchField(nustd.ui.Field):
|
|
58
|
+
input = nustd.ui.SwitchRef.slot(default=True)
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
class DetailsFieldset(
|
|
61
|
+
class DetailsFieldset(nustd.ui.Fieldset):
|
|
61
62
|
title = TitleField.slot(label="Title", help="Movie title", required=True)
|
|
62
63
|
year = YearField.slot(label="Year")
|
|
63
64
|
genre = GenreField.slot(label="Genre")
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
class ScoreFieldset(
|
|
67
|
+
class ScoreFieldset(nustd.ui.Fieldset):
|
|
67
68
|
rating = RatingField.slot(label="Rating", help="How much you liked it")
|
|
68
69
|
watched = SwitchField.slot(label="Watched?", help="Off means still on the pile")
|
|
69
70
|
notes = TextAreaField.slot(label="Notes")
|
|
70
71
|
|
|
71
72
|
|
|
72
|
-
class AddMovieForm(
|
|
73
|
+
class AddMovieForm(nustd.ui.Form):
|
|
73
74
|
details = DetailsFieldset.slot(legend="Movie", gap="md")
|
|
74
75
|
score = ScoreFieldset.slot(legend="Your take", gap="md")
|
|
75
|
-
submit =
|
|
76
|
-
feedback =
|
|
76
|
+
submit = nustd.ui.ButtonRef.slot(label="Log it", variant="primary")
|
|
77
|
+
feedback = nustd.ui.AlertRef.slot(variant="ok", dismissible=True)
|
|
77
78
|
|
|
78
79
|
|
|
79
|
-
class MinRatingField(
|
|
80
|
-
input =
|
|
80
|
+
class MinRatingField(nustd.ui.Field):
|
|
81
|
+
input = nustd.ui.NumberInputRef.slot(min=1.0, max=10.0, step=0.5, default=1.0)
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
class FilterGenreField(
|
|
84
|
-
input =
|
|
84
|
+
class FilterGenreField(nustd.ui.Field):
|
|
85
|
+
input = nustd.ui.SelectRef.slot(options=_GENRES_WITH_ANY, selected="")
|
|
85
86
|
|
|
86
87
|
|
|
87
|
-
class WatchedOnlyField(
|
|
88
|
-
input =
|
|
88
|
+
class WatchedOnlyField(nustd.ui.Field):
|
|
89
|
+
input = nustd.ui.SwitchRef.slot(default=False)
|
|
89
90
|
|
|
90
91
|
|
|
91
|
-
class FilterRow(
|
|
92
|
+
class FilterRow(nustd.ui.Row):
|
|
92
93
|
min_rating = MinRatingField.slot(label="Min rating")
|
|
93
94
|
genre = FilterGenreField.slot(label="Genre")
|
|
94
95
|
watched_only = WatchedOnlyField.slot(label="Already watched")
|
|
95
|
-
apply =
|
|
96
|
-
clear =
|
|
96
|
+
apply = nustd.ui.ButtonRef.slot(label="Apply", variant="secondary")
|
|
97
|
+
clear = nustd.ui.ButtonRef.slot(label="Clear", variant="ghost")
|
|
97
98
|
|
|
98
99
|
|
|
99
|
-
class FilterCard(
|
|
100
|
+
class FilterCard(nustd.ui.Card):
|
|
100
101
|
body = FilterRow.slot(gap=3, align="center", wrap=True)
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
class StatsRow(
|
|
104
|
-
total =
|
|
105
|
-
watched =
|
|
106
|
-
unseen =
|
|
107
|
-
latest =
|
|
108
|
-
health =
|
|
104
|
+
class StatsRow(nustd.ui.Row):
|
|
105
|
+
total = nustd.ui.StatRef.slot(label="Total")
|
|
106
|
+
watched = nustd.ui.StatRef.slot(label="Watched")
|
|
107
|
+
unseen = nustd.ui.StatRef.slot(label="Unseen")
|
|
108
|
+
latest = nustd.ui.TextRef.slot()
|
|
109
|
+
health = nustd.ui.BadgeRef.slot(label="Fresh", variant="ok")
|
|
109
110
|
|
|
110
111
|
|
|
111
|
-
class StatsCard(
|
|
112
|
+
class StatsCard(nustd.ui.Card):
|
|
112
113
|
body = StatsRow.slot(gap=6, align="center", wrap=True)
|
|
113
114
|
|
|
114
115
|
|
|
115
|
-
class TableBody(
|
|
116
|
-
table =
|
|
116
|
+
class TableBody(nustd.ui.Column):
|
|
117
|
+
table = nustd.ui.TableRef.slot(
|
|
117
118
|
columns=["title", "year", "genre", "rating", "watched", "notes"],
|
|
118
119
|
striped=True,
|
|
119
120
|
dense=True,
|
|
120
121
|
clickable_rows=True,
|
|
121
122
|
max_rows=200,
|
|
122
123
|
)
|
|
123
|
-
empty =
|
|
124
|
+
empty = nustd.ui.AlertRef.slot(
|
|
124
125
|
variant="info",
|
|
125
126
|
body="No movies match",
|
|
126
127
|
dismissible=False,
|
|
127
128
|
)
|
|
128
129
|
|
|
129
130
|
|
|
130
|
-
class TableCard(
|
|
131
|
+
class TableCard(nustd.ui.Card):
|
|
131
132
|
body = TableBody.slot(gap=3)
|
|
132
133
|
|
|
133
134
|
|
|
134
|
-
class Links(
|
|
135
|
-
docs =
|
|
135
|
+
class Links(nustd.ui.Row):
|
|
136
|
+
docs = nustd.ui.LinkRef.slot(
|
|
136
137
|
label="Read the docs", href="https://nustack.dev/docs", target="_blank"
|
|
137
138
|
)
|
|
138
|
-
github =
|
|
139
|
+
github = nustd.ui.LinkRef.slot(
|
|
139
140
|
label="Star on GitHub", href="https://github.com/nustackdev/nu", target="_blank"
|
|
140
141
|
)
|
|
141
|
-
examples =
|
|
142
|
+
examples = nustd.ui.LinkRef.slot(
|
|
142
143
|
label="Browse more demos",
|
|
143
144
|
href="https://github.com/nustackdev/nu/tree/main/examples",
|
|
144
145
|
target="_blank",
|
|
@@ -148,13 +149,13 @@ class Links(nu.ui.Row):
|
|
|
148
149
|
# ---- Pages ------------------------------------------------------------------
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
class TopBar(
|
|
152
|
-
about =
|
|
152
|
+
class TopBar(nustd.ui.Row):
|
|
153
|
+
about = nustd.ui.ButtonRef.slot(label="About this demo", variant="ghost")
|
|
153
154
|
|
|
154
155
|
|
|
155
|
-
class Movies(
|
|
156
|
-
heading =
|
|
157
|
-
intro =
|
|
156
|
+
class Movies(nustd.ui.Page):
|
|
157
|
+
heading = nustd.ui.HeadingRef.slot(label="Your movies")
|
|
158
|
+
intro = nustd.ui.TextRef.slot(
|
|
158
159
|
value="Log what you watch. Filter the shelf, click a row for details.",
|
|
159
160
|
)
|
|
160
161
|
topbar = TopBar.slot(gap=3, align="center")
|
|
@@ -165,13 +166,13 @@ class Movies(nu.ui.Page):
|
|
|
165
166
|
shelf = TableCard.slot(title="Movies")
|
|
166
167
|
|
|
167
168
|
|
|
168
|
-
class AboutActions(
|
|
169
|
-
back =
|
|
169
|
+
class AboutActions(nustd.ui.Row):
|
|
170
|
+
back = nustd.ui.ButtonRef.slot(label="Back to app", variant="ghost")
|
|
170
171
|
|
|
171
172
|
|
|
172
|
-
class About(
|
|
173
|
-
heading =
|
|
174
|
-
about =
|
|
173
|
+
class About(nustd.ui.Page):
|
|
174
|
+
heading = nustd.ui.HeadingRef.slot(label="How it works")
|
|
175
|
+
about = nustd.ui.MarkdownRef.slot(
|
|
175
176
|
value=(
|
|
176
177
|
"- Real app: form, filterable table, stats row, per-item detail page.\n"
|
|
177
178
|
"- Every row lives in rocksdb. Restart, everything is still there.\n"
|
|
@@ -179,73 +180,75 @@ class About(nu.ui.Page):
|
|
|
179
180
|
"- Same Interactions handle add, delete, filter, and page routing.\n"
|
|
180
181
|
),
|
|
181
182
|
)
|
|
182
|
-
links_heading =
|
|
183
|
-
links_intro =
|
|
183
|
+
links_heading = nustd.ui.HeadingRef.slot(label="Try Nu yourself")
|
|
184
|
+
links_intro = nustd.ui.TextRef.slot(
|
|
184
185
|
value="Full apps, forms, routing, no glue. See how far the primitive goes.",
|
|
185
186
|
)
|
|
186
187
|
links = Links.slot(gap=4, align="center", wrap=True)
|
|
187
|
-
source_heading =
|
|
188
|
-
source_intro =
|
|
188
|
+
source_heading = nustd.ui.HeadingRef.slot(label="Source")
|
|
189
|
+
source_intro = nustd.ui.TextRef.slot(
|
|
189
190
|
value="The whole app, one file. Storage, UI, and the wires between them.",
|
|
190
191
|
)
|
|
191
|
-
source =
|
|
192
|
+
source = nustd.ui.CodeBlockRef.slot(
|
|
192
193
|
code=Path(__file__).read_text(),
|
|
193
194
|
language="python",
|
|
194
195
|
)
|
|
195
196
|
actions = AboutActions.slot(gap=3, align="center")
|
|
196
197
|
|
|
197
198
|
|
|
198
|
-
class DetailRow(
|
|
199
|
-
year =
|
|
200
|
-
genre =
|
|
201
|
-
rating =
|
|
202
|
-
watched =
|
|
199
|
+
class DetailRow(nustd.ui.Row):
|
|
200
|
+
year = nustd.ui.StatRef.slot(label="Year")
|
|
201
|
+
genre = nustd.ui.StatRef.slot(label="Genre")
|
|
202
|
+
rating = nustd.ui.StatRef.slot(label="Rating")
|
|
203
|
+
watched = nustd.ui.BadgeRef.slot(label="Watched", variant="ok")
|
|
203
204
|
|
|
204
205
|
|
|
205
|
-
class MetaCard(
|
|
206
|
+
class MetaCard(nustd.ui.Card):
|
|
206
207
|
meta = DetailRow.slot(gap=6, align="center", wrap=True)
|
|
207
208
|
|
|
208
209
|
|
|
209
|
-
class NotesCard(
|
|
210
|
-
body =
|
|
210
|
+
class NotesCard(nustd.ui.Card):
|
|
211
|
+
body = nustd.ui.MarkdownRef.slot()
|
|
211
212
|
|
|
212
213
|
|
|
213
|
-
class DetailActions(
|
|
214
|
-
back =
|
|
215
|
-
remove =
|
|
214
|
+
class DetailActions(nustd.ui.Row):
|
|
215
|
+
back = nustd.ui.ButtonRef.slot(label="Back", variant="ghost")
|
|
216
|
+
remove = nustd.ui.ButtonRef.slot(label="Delete", variant="danger")
|
|
216
217
|
|
|
217
218
|
|
|
218
|
-
class MovieDetail(
|
|
219
|
-
heading =
|
|
219
|
+
class MovieDetail(nustd.ui.Page):
|
|
220
|
+
heading = nustd.ui.HeadingRef.slot()
|
|
220
221
|
meta = MetaCard.slot(title="Details")
|
|
221
222
|
notes = NotesCard.slot(title="Notes")
|
|
222
223
|
actions = DetailActions.slot(gap=3, align="center")
|
|
223
224
|
|
|
224
225
|
|
|
225
|
-
class App(
|
|
226
|
-
title:
|
|
227
|
-
nav:
|
|
228
|
-
|
|
226
|
+
class App(nustd.ui.Index):
|
|
227
|
+
title: nustd.ui.TitleRef
|
|
228
|
+
nav: nustd.ui.NavRef
|
|
229
|
+
movies = Movies.slot("/")
|
|
230
|
+
detail = MovieDetail.slot("/detail")
|
|
231
|
+
about = About.slot("/about")
|
|
229
232
|
|
|
230
233
|
|
|
231
234
|
# ---- State ------------------------------------------------------------------
|
|
232
235
|
|
|
233
236
|
|
|
234
237
|
class Movie(nu.Shape):
|
|
235
|
-
title =
|
|
236
|
-
year =
|
|
237
|
-
genre =
|
|
238
|
-
rating =
|
|
239
|
-
watched =
|
|
240
|
-
notes =
|
|
238
|
+
title = nustd.kv.StrRef.slot()
|
|
239
|
+
year = nustd.kv.IntRef.slot()
|
|
240
|
+
genre = nustd.kv.StrRef.slot()
|
|
241
|
+
rating = nustd.kv.FloatRef.slot()
|
|
242
|
+
watched = nustd.kv.BoolRef.slot()
|
|
243
|
+
notes = nustd.kv.StrRef.slot()
|
|
241
244
|
|
|
242
245
|
|
|
243
246
|
class State(nu.Shape):
|
|
244
|
-
movies =
|
|
245
|
-
total =
|
|
246
|
-
watched =
|
|
247
|
-
latest_title =
|
|
248
|
-
selected =
|
|
247
|
+
movies = nustd.kv.ShapesListRef.slot(Movie)
|
|
248
|
+
total = nustd.kv.IntRef.slot()
|
|
249
|
+
watched = nustd.kv.IntRef.slot()
|
|
250
|
+
latest_title = nustd.kv.StrRef.slot()
|
|
251
|
+
selected = nustd.kv.IntRef.slot() # index of the movie open in MovieDetail
|
|
249
252
|
|
|
250
253
|
|
|
251
254
|
# ---- Seed ------------------------------------------------------------------
|
|
@@ -310,9 +313,9 @@ def _rows_form() -> nu.Nu:
|
|
|
310
313
|
|
|
311
314
|
def _rows_filtered() -> nu.Nu:
|
|
312
315
|
"""Same shape as _rows_form, but honors the current FilterRow inputs."""
|
|
313
|
-
min_r = nu.Float(
|
|
314
|
-
genre = nu.Str(
|
|
315
|
-
watched_only = nu.Bool(
|
|
316
|
+
min_r = nu.Float(App.movies.filters.body.min_rating.input)
|
|
317
|
+
genre = nu.Str(App.movies.filters.body.genre.input)
|
|
318
|
+
watched_only = nu.Bool(App.movies.filters.body.watched_only.input)
|
|
316
319
|
predicate = nu.And(
|
|
317
320
|
nu.Ge(nu.DictAttrRef("r")["rating"], min_r),
|
|
318
321
|
nu.Or(nu.Eq(genre, ""), nu.Eq(nu.DictAttrRef("r")["genre"], genre)),
|
|
@@ -332,7 +335,7 @@ def _rows_filtered() -> nu.Nu:
|
|
|
332
335
|
# Seed once, on a store that has never been written. A restart then keeps what
|
|
333
336
|
# the user logged instead of replacing the shelf with the samples again.
|
|
334
337
|
# `selected` stays unconditional: it is a cursor into the detail page, not data.
|
|
335
|
-
init =
|
|
338
|
+
init = nustd.kv.Transaction(
|
|
336
339
|
nu.IfDo(
|
|
337
340
|
State.total.missing(),
|
|
338
341
|
State.total.set(len(_SEED_MOVIES))
|
|
@@ -344,62 +347,62 @@ init = nu.kv.Transaction(
|
|
|
344
347
|
)
|
|
345
348
|
|
|
346
349
|
|
|
347
|
-
hydrate =
|
|
348
|
-
|
|
349
|
-
|
|
|
350
|
-
|
|
|
351
|
-
|
|
|
352
|
-
|
|
|
350
|
+
hydrate = nustd.kv.Snapshot(
|
|
351
|
+
App.movies.stats.body.total.set_value(nu.str(State.total))
|
|
352
|
+
| App.movies.stats.body.watched.set_value(nu.str(State.watched))
|
|
353
|
+
| App.movies.stats.body.unseen.set_value(nu.str(State.total - State.watched))
|
|
354
|
+
| App.movies.stats.body.latest.set(State.latest_title)
|
|
355
|
+
| App.movies.shelf.body.table.set(_rows_form())
|
|
353
356
|
)
|
|
354
357
|
|
|
355
358
|
|
|
356
359
|
on_add = nu.ReactForever(
|
|
357
|
-
|
|
358
|
-
|
|
360
|
+
App.movies.form.submit.on_click(),
|
|
361
|
+
nustd.kv.Transaction(
|
|
359
362
|
State.movies.append(
|
|
360
363
|
nu.Dict.of(
|
|
361
|
-
title=nu.Str(
|
|
362
|
-
year=nu.Int(
|
|
363
|
-
genre=nu.Str(
|
|
364
|
-
rating=nu.Float(
|
|
365
|
-
watched=nu.Bool(
|
|
366
|
-
notes=nu.Str(
|
|
364
|
+
title=nu.Str(App.movies.form.details.title.input),
|
|
365
|
+
year=nu.Int(App.movies.form.details.year.input),
|
|
366
|
+
genre=nu.Str(App.movies.form.details.genre.input),
|
|
367
|
+
rating=nu.Float(App.movies.form.score.rating.input),
|
|
368
|
+
watched=nu.Bool(App.movies.form.score.watched.input),
|
|
369
|
+
notes=nu.Str(App.movies.form.score.notes.input),
|
|
367
370
|
),
|
|
368
371
|
)
|
|
369
372
|
| State.total.set(State.total + 1)
|
|
370
373
|
| State.watched.set(
|
|
371
|
-
State.watched + nu.If(nu.Bool(
|
|
374
|
+
State.watched + nu.If(nu.Bool(App.movies.form.score.watched.input), 1, 0),
|
|
372
375
|
)
|
|
373
|
-
| State.latest_title.set(nu.Str(
|
|
376
|
+
| State.latest_title.set(nu.Str(App.movies.form.details.title.input)),
|
|
374
377
|
)
|
|
375
|
-
>>
|
|
376
|
-
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
378
|
+
>> nustd.kv.Snapshot(
|
|
379
|
+
App.movies.shelf.body.table.set(_rows_form())
|
|
380
|
+
| App.movies.stats.body.total.set_value(nu.str(State.total))
|
|
381
|
+
| App.movies.stats.body.watched.set_value(nu.str(State.watched))
|
|
382
|
+
| App.movies.stats.body.unseen.set_value(nu.str(State.total - State.watched))
|
|
383
|
+
| App.movies.stats.body.latest.set(State.latest_title)
|
|
384
|
+
| App.movies.form.feedback.set(
|
|
382
385
|
title="Logged",
|
|
383
|
-
body="Added " + nu.Str(
|
|
386
|
+
body="Added " + nu.Str(App.movies.form.details.title.input),
|
|
384
387
|
)
|
|
385
388
|
),
|
|
386
389
|
)
|
|
387
390
|
|
|
388
391
|
|
|
389
392
|
on_row_click = nu.ReactForever(
|
|
390
|
-
|
|
393
|
+
App.movies.shelf.body.table.on_row_click(),
|
|
391
394
|
nu.IfDo(
|
|
392
395
|
nu.Contains(nu.DictAttrRef("row_click"), "row_index"),
|
|
393
|
-
|
|
394
|
-
>>
|
|
395
|
-
|
|
396
|
-
|
|
|
397
|
-
|
|
|
398
|
-
|
|
|
399
|
-
|
|
|
396
|
+
nustd.kv.Transaction(State.selected.set(nu.DictAttrRef("row_click")["row_index"]))
|
|
397
|
+
>> nustd.kv.Snapshot(
|
|
398
|
+
App.detail.heading.set(State.movies[State.selected].title)
|
|
399
|
+
| App.detail.meta.meta.year.set_value(nu.str(State.movies[State.selected].year))
|
|
400
|
+
| App.detail.meta.meta.genre.set_value(State.movies[State.selected].genre)
|
|
401
|
+
| App.detail.meta.meta.rating.set_value(nu.str(State.movies[State.selected].rating))
|
|
402
|
+
| App.detail.meta.meta.watched.set(
|
|
400
403
|
label=nu.If(State.movies[State.selected].watched, "Watched", "Unseen"),
|
|
401
404
|
)
|
|
402
|
-
|
|
|
405
|
+
| App.detail.notes.body.set(State.movies[State.selected].notes)
|
|
403
406
|
)
|
|
404
407
|
>> App.nav.set("/detail"),
|
|
405
408
|
),
|
|
@@ -408,41 +411,41 @@ on_row_click = nu.ReactForever(
|
|
|
408
411
|
|
|
409
412
|
|
|
410
413
|
on_delete = nu.ReactForever(
|
|
411
|
-
|
|
412
|
-
|
|
414
|
+
App.detail.actions.remove.on_click(),
|
|
415
|
+
nustd.kv.Transaction(
|
|
413
416
|
State.movies.del_at(State.selected) >> State.total.set(nu.Len(State.movies)),
|
|
414
417
|
)
|
|
415
|
-
>>
|
|
416
|
-
|
|
417
|
-
|
|
|
418
|
-
|
|
|
418
|
+
>> nustd.kv.Snapshot(
|
|
419
|
+
App.movies.shelf.body.table.set(_rows_form())
|
|
420
|
+
| App.movies.stats.body.total.set_value(nu.str(State.total))
|
|
421
|
+
| App.movies.stats.body.unseen.set_value(nu.str(State.total - State.watched))
|
|
419
422
|
)
|
|
420
423
|
>> App.nav.set("/"),
|
|
421
424
|
)
|
|
422
425
|
|
|
423
426
|
|
|
424
|
-
on_back = nu.ReactForever(
|
|
427
|
+
on_back = nu.ReactForever(App.detail.actions.back.on_click(), App.nav.set("/"))
|
|
425
428
|
|
|
426
429
|
|
|
427
|
-
on_about_open = nu.ReactForever(
|
|
430
|
+
on_about_open = nu.ReactForever(App.movies.topbar.about.on_click(), App.nav.set("/about"))
|
|
428
431
|
|
|
429
432
|
|
|
430
|
-
on_about_back = nu.ReactForever(
|
|
433
|
+
on_about_back = nu.ReactForever(App.about.actions.back.on_click(), App.nav.set("/"))
|
|
431
434
|
|
|
432
435
|
|
|
433
436
|
on_filter_apply = nu.ReactForever(
|
|
434
|
-
|
|
435
|
-
|
|
437
|
+
App.movies.filters.body.apply.on_click(),
|
|
438
|
+
nustd.kv.Snapshot(App.movies.shelf.body.table.set(_rows_filtered())),
|
|
436
439
|
)
|
|
437
440
|
|
|
438
441
|
|
|
439
442
|
on_filter_clear = nu.ReactForever(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
|
444
|
-
|
|
|
445
|
-
|
|
|
443
|
+
App.movies.filters.body.clear.on_click(),
|
|
444
|
+
nustd.kv.Snapshot(
|
|
445
|
+
App.movies.filters.body.min_rating.input.set(1.0)
|
|
446
|
+
| App.movies.filters.body.genre.input.set("")
|
|
447
|
+
| App.movies.filters.body.watched_only.input.set(False)
|
|
448
|
+
| App.movies.shelf.body.table.set(_rows_form())
|
|
446
449
|
),
|
|
447
450
|
)
|
|
448
451
|
|
|
@@ -464,9 +467,9 @@ ui = (
|
|
|
464
467
|
|
|
465
468
|
|
|
466
469
|
app = nu.With(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
body=
|
|
470
|
+
nustd.kv.rocksdb_navigator(str(_DB)),
|
|
471
|
+
nustd.ui.server(nustd.kv.auto_flow_atomic(ui)),
|
|
472
|
+
body=nustd.kv.auto_flow_atomic(init >> nu.ForeverDo(nu.Delay(3600))),
|
|
470
473
|
)
|
|
471
474
|
|
|
472
475
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import nu
|
|
6
|
+
import nustd
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
_DB = Path.home() / ".nu" / "demos" / "sampled"
|
|
@@ -12,31 +13,31 @@ _DB.parent.mkdir(parents=True, exist_ok=True)
|
|
|
12
13
|
class State(nu.Shape):
|
|
13
14
|
"""Persistent series and write cursor."""
|
|
14
15
|
|
|
15
|
-
nums =
|
|
16
|
-
cursor =
|
|
16
|
+
nums = nustd.kv.Kh57Ref.slot(int)
|
|
17
|
+
cursor = nustd.kv.IntRef.slot()
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class Links(
|
|
20
|
-
docs =
|
|
20
|
+
class Links(nustd.ui.Row):
|
|
21
|
+
docs = nustd.ui.LinkRef.slot(
|
|
21
22
|
label="Read the docs", href="https://nustack.dev/docs", target="_blank"
|
|
22
23
|
)
|
|
23
|
-
github =
|
|
24
|
+
github = nustd.ui.LinkRef.slot(
|
|
24
25
|
label="Star on GitHub", href="https://github.com/nustackdev/nu", target="_blank"
|
|
25
26
|
)
|
|
26
|
-
examples =
|
|
27
|
+
examples = nustd.ui.LinkRef.slot(
|
|
27
28
|
label="Browse more demos",
|
|
28
29
|
href="https://github.com/nustackdev/nu/tree/main/examples",
|
|
29
30
|
target="_blank",
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
class Dashboard(
|
|
34
|
+
class Dashboard(nustd.ui.Page):
|
|
34
35
|
"""Live chart page with description and source."""
|
|
35
36
|
|
|
36
|
-
heading =
|
|
37
|
-
chart =
|
|
38
|
-
about_heading =
|
|
39
|
-
about =
|
|
37
|
+
heading = nustd.ui.HeadingRef.slot(label="Unbounded series, instant chart")
|
|
38
|
+
chart = nustd.ui.LineChart.slot()
|
|
39
|
+
about_heading = nustd.ui.HeadingRef.slot(label="How it works")
|
|
40
|
+
about = nustd.ui.MarkdownRef.slot(
|
|
40
41
|
value=(
|
|
41
42
|
"- Writes 50 numbers a second into a **kh57-backed** series. Grows without limit.\n"
|
|
42
43
|
"- Chart repaints from a 200-point reservoir sample. Same cost at 1k rows or 1B.\n"
|
|
@@ -45,31 +46,31 @@ class Dashboard(nu.ui.Page):
|
|
|
45
46
|
"- Same Interactions used to orchestrate the feed and the redraw.\n"
|
|
46
47
|
),
|
|
47
48
|
)
|
|
48
|
-
links_heading =
|
|
49
|
-
links_intro =
|
|
49
|
+
links_heading = nustd.ui.HeadingRef.slot(label="Try Nu yourself")
|
|
50
|
+
links_intro = nustd.ui.TextRef.slot(
|
|
50
51
|
value="Billion-row backends, live UIs, no glue. See how far the primitive goes.",
|
|
51
52
|
)
|
|
52
53
|
links = Links.slot(gap=4, align="center", wrap=True)
|
|
53
|
-
source_heading =
|
|
54
|
-
source_intro =
|
|
54
|
+
source_heading = nustd.ui.HeadingRef.slot(label="Source")
|
|
55
|
+
source_intro = nustd.ui.TextRef.slot(
|
|
55
56
|
value="The whole app, one file. Storage, UI, and the wires between them.",
|
|
56
57
|
)
|
|
57
|
-
source =
|
|
58
|
+
source = nustd.ui.CodeBlockRef.slot(
|
|
58
59
|
code=Path(__file__).read_text(),
|
|
59
60
|
language="python",
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
|
|
63
|
-
class App(
|
|
64
|
+
class App(nustd.ui.Index):
|
|
64
65
|
"""UI index with one page."""
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
home = Dashboard.slot("/")
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
# reactive wire: repaint the chart on every write to `nums`
|
|
70
71
|
ui = nu.ReactForever(
|
|
71
72
|
State.nums.on_change(),
|
|
72
|
-
|
|
73
|
+
App.home.chart.set_points(
|
|
73
74
|
nu.Collect(nu.Sorted(nu.Iter(State.nums.sample(200, 0, State.cursor)))),
|
|
74
75
|
),
|
|
75
76
|
)
|
|
@@ -81,9 +82,9 @@ feed = State.cursor.init(0) >> nu.ForeverDo(
|
|
|
81
82
|
|
|
82
83
|
# assemble: rocksdb-backed, served over the browser
|
|
83
84
|
app = nu.With(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
body=
|
|
85
|
+
nustd.kv.rocksdb_navigator(str(_DB)),
|
|
86
|
+
nustd.ui.server(nustd.kv.auto_flow_atomic(ui)),
|
|
87
|
+
body=nustd.kv.auto_flow_atomic(feed),
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
|
|
@@ -4,11 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import rich_click as click
|
|
6
6
|
|
|
7
|
-
from nu._cli._meta import nu_version
|
|
8
|
-
from nu._cli.commands.demo import demo
|
|
9
|
-
from nu._cli.commands.doctor import doctor
|
|
10
|
-
from nu._cli.commands.telemetry import telemetry
|
|
11
7
|
from nu._config.branding import BLUE, PURPLE, render_header
|
|
8
|
+
from nucli._meta import nu_version
|
|
9
|
+
from nucli.commands.demo import demo
|
|
10
|
+
from nucli.commands.doctor import doctor
|
|
11
|
+
from nucli.commands.telemetry import telemetry
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
# Rich-click styling: strict brand duo -- purple headers, blue values.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|