@nil-/xit 0.0.1 → 0.0.3

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.
package/README.md CHANGED
@@ -1,5 +1,288 @@
1
- # @nil-/xit
1
+ # nil/xit
2
2
 
3
- front end for nil's xit project.
3
+ _nil/xit_ is a small project designed to bridge the gap between C++ and Svelte for creating GUI applications.
4
4
 
5
- TODO: this document
5
+ Note: _Xit_ is a temporary name. It's my playful way of saying, "This is my xit (💩)."
6
+
7
+ ## Motivation
8
+
9
+ This project isn't intended to replace existing, more mature GUI frameworks. Writing GUI applications in C++ can be complex, with frameworks like Qt, ImGui, and others requiring a deep understanding of setup and usage.
10
+
11
+ With _nil/xit_, you can write your UI in Svelte and add hooks/bindings to control the interface. While you'll need some familiarity with Svelte, it's a relatively small hurdle to overcome compared to mastering traditional C++ GUI frameworks.
12
+
13
+ ## Requirements
14
+
15
+ This library is developed in a repo with `vcpkg`.
16
+
17
+ To consume the library, add _nil_ registry to your _vcpkg-configuration.json_
18
+
19
+ ```json
20
+ {
21
+ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json",
22
+ "default-registry": {
23
+ "kind": "git",
24
+ "repository": "https://github.com/Microsoft/vcpkg",
25
+ "baseline": "da21e45c9ae689f63aa27f3a4ee0e64fe6b16494"
26
+ },
27
+ "registries": [
28
+ {
29
+ "kind": "git",
30
+ "repository": "https://github.com/njaldea/nil-vcpkg-ports",
31
+ "baseline": "02b02d3bc913cbd52da7654cb6f46207e6a9c6d1",
32
+ "packages": ["nil", "nil-service", "nil-xit"]
33
+ }
34
+ ]
35
+ }
36
+ ```
37
+
38
+ Create your target binary and link nil::xit.
39
+
40
+ ```cmake
41
+ project(YOUR_PROJECT)
42
+
43
+ find_package(nil-xit CONFIG REQUIRED)
44
+
45
+ add_executable(${PROJECT_NAME} main.cpp)
46
+ target_link_libraries(${PROJECT_NAME} PRIVATE nil::xit)
47
+ ```
48
+
49
+ The following libraries are required to use _nil/xit_ alongside `vcpkg`:
50
+
51
+ - [nil/service](https://github.com/njaldea/nil-service)
52
+
53
+ The following are the libraries needed to compile from source if vcpkg is available.
54
+
55
+ - `boost` (`asio` and `beast`)
56
+ - `protobuf`
57
+
58
+ TODO: publish libraries other than `vcpkg`
59
+
60
+ ## How it works
61
+
62
+ The C++ API primarily sets up the network service.
63
+ The GUI itself is not part of the C++ API but is served externally, for example, from [https://xit-ui.vercel.app](https://xit-ui.vercel.app).
64
+
65
+ At present, the frontend UI connects to a specified URL. For example, if you spawn a WebSocket server on port 1101, you can access the UI by visiting:
66
+
67
+ ```
68
+ https://xit-ui.vercel.app/localhost:1101/id-1
69
+ ```
70
+
71
+ The Svelte files provided to the framework are sent to the frontend, where they are recompiled into a consumable module. You can use any JavaScript libraries available on npm, typically through [https://unpkg.com](https://unpkg.com).
72
+
73
+ Note: Every page refresh will trigger a recompilation of the files. Caching will be implemented as the library matures.
74
+
75
+ ## Example
76
+
77
+ ### C++ Code
78
+
79
+ ```cpp
80
+ #include <nil/service/ws/Server.hpp>
81
+
82
+ #include <nil/xit.hpp>
83
+
84
+ #include <iostream>
85
+ #include <thread>
86
+
87
+ int main()
88
+ {
89
+ nil::service::ws::Server server({.port = 1101});
90
+ // https://xit-ui.vercel.app/{server or ip:port}/{frame id}
91
+ // https://xit-ui.vercel.app/localhost:1101/frame-id
92
+
93
+ auto core = nil::xit::make_core(server);
94
+
95
+ auto& frame = add_frame(
96
+ core,
97
+ "frame-id",
98
+ "/absolute/path/to/your/svelte/file.svelte"
99
+ );
100
+
101
+ auto& str_bind = bind(
102
+ frame,
103
+ "binding_0_1",
104
+ "initial value",
105
+ // this is to test gui -> cpp data flow
106
+ [](const std::string& value)
107
+ {
108
+ // local value is already updated when this is called.
109
+ std::cout << "value changed: " << value << std::endl;
110
+ }
111
+ );
112
+
113
+ // this is to test cpp -> gui data flow
114
+ const std::thread th(
115
+ [&]()
116
+ {
117
+ std::string line;
118
+ std::cout << "input here: ";
119
+ while (std::getline(std::cin, line))
120
+ {
121
+ // will not update the local data inside str_bind
122
+ post(str_bind, line);
123
+
124
+ // if local data needs to be overwritten, use `set` instead
125
+ // this will update the local data without waiting for an update
126
+ // from the UI.
127
+ set(str_bind, line);
128
+ std::cout << "input here: ";
129
+ }
130
+ }
131
+ );
132
+
133
+ server.run();
134
+ return 0;
135
+ }
136
+ ```
137
+
138
+ ### Svelte UI
139
+
140
+ ```html
141
+ <script>
142
+ import Text from "./components/Text.svelte";
143
+
144
+ import { getContext } from "svelte";
145
+
146
+ const { binding } = getContext("nil.xit");
147
+ const str_binding = binding.string("binding_0_1", "world");
148
+ </script>
149
+
150
+ <Text bind:value="{$str_binding}" placeholder="placeholder" label="text label here" />
151
+ ```
152
+
153
+ ## Supported Binding Types
154
+
155
+ - `std::int64_t`
156
+ - `std::string`
157
+ - `std::vector\<std::int8_t>`
158
+
159
+ These are accessible from a context provided to the user.
160
+
161
+ ```html
162
+ <script>
163
+ import { getContext } from "svelte";
164
+
165
+ const { binding } = getContext("nil.xit");
166
+
167
+ const store_string = binding.string("id-string", "default_value"); // string
168
+ const store_number = binding.number("id-number", 1101); // number
169
+ const store_buffer = binding.buffer("id-buffer", []); // UInt8Array
170
+ </script>
171
+ ```
172
+
173
+ ## How about JSON?
174
+
175
+ JSON is not directly supported. This is intentional since the frontend might not be a browser. It will be up to the user to serialize and deserialize this kind of data.
176
+
177
+ Example below is an example for handling json by simply stringifying it.
178
+
179
+ ### C++ Code
180
+
181
+ ```cpp
182
+ struct JSON
183
+ {
184
+ std::string buffer;
185
+ };
186
+
187
+ namespace nil::xit
188
+ {
189
+ template <>
190
+ struct buffer_type<JSON>
191
+ {
192
+ static JSON deserialize(const void* data, std::uint64_t size)
193
+ {
194
+ return {.buffer = {static_cast<const char*>(data), size}};
195
+ }
196
+
197
+ static std::vector<std::uint8_t> serialize(const JSON& value)
198
+ {
199
+ return {value.buffer.begin(), value.buffer.end()};
200
+ }
201
+ };
202
+ }
203
+
204
+ ...
205
+ {
206
+ auto& json_editor = add_frame(
207
+ core,
208
+ "frame-id",
209
+ "/absolute/path/to/your/svelte/file.svelte"
210
+ );
211
+ bind<JSON>(
212
+ json_editor,
213
+ "json_binding",
214
+ JSON{.buffer = R"({ "hello": "hello this is buffer" })"},
215
+ [](const JSON& value) { std::cout << "value changed: " << value.buffer << std::endl; }
216
+ );
217
+ }
218
+ ```
219
+
220
+ ### Svelte UI
221
+
222
+ ```html
223
+ <script>
224
+ import { getContext } from "svelte";
225
+
226
+ const { binding } = getContext("nil.xit");
227
+ const str_binding = binding.json(
228
+ "binding-id",
229
+ {}, // default value
230
+ {
231
+ encode: (o) => new TextEncoder().encode(JSON.stringify(o)),
232
+ decode: (o) => JSON.parse(new TextDecoder().decode(o))
233
+ }
234
+ );
235
+ </script>
236
+ ```
237
+
238
+ ## Frame Composition
239
+
240
+ Composing multiple frames into one is possible.
241
+
242
+ ### C++ Code
243
+
244
+ ```cpp
245
+ auto& group = add_frame(
246
+ core,
247
+ "group",
248
+ std::filesystem::path(__FILE__).parent_path() / "gui/GroupUp.svelte"
249
+ );
250
+
251
+ bind<JSON>(
252
+ group,
253
+ "frames",
254
+ JSON{.buffer = R"({ "frames": ["frame-1", "frame-2"] })"}
255
+ );
256
+ ```
257
+
258
+ ### Svelte UI
259
+
260
+ ```html
261
+ <script>
262
+ import { getContext } from "svelte";
263
+ const { binding, loader } = getContext("nil.xit");
264
+
265
+ const links = binding.json(
266
+ "links",
267
+ { links: [] },
268
+ {
269
+ encode: (o) => new TextEncoder().encode(JSON.stringify(o)),
270
+ decode: (o) => JSON.parse(new TextDecoder().decode(o))
271
+ }
272
+ );
273
+ </script>
274
+
275
+ <div class="wrapper">
276
+ {#each $links.links as link} {@const frame = loader.one(link)}
277
+ <div use:frame />
278
+ {/each}
279
+ </div>
280
+ ```
281
+
282
+ TODO:
283
+
284
+ - Implement signal/event binding
285
+ - Develop a usable component library
286
+ - Create a more intuitive way to specify the connection URL for the page
287
+ - Enable bundle caching
288
+ - Optimize json type support
package/index.d.ts CHANGED
@@ -1 +1,21 @@
1
- export { binding } from "./src/binding";
1
+ import { type Action } from "svelte/action";
2
+ import { type Writable } from "svelte/store";
3
+ export type CoDec = {
4
+ encode: (o: object) => Uint8Array;
5
+ decode: (a: Uint8Array) => object;
6
+ };
7
+ export type Loader = {
8
+ one: (f: string) => Action<HTMLDivElement>;
9
+ all: (f: string[]) => Action<HTMLDivElement>;
10
+ };
11
+ export type Xit = {
12
+ binding: {
13
+ string: (t: string, v: string) => Writable<string>;
14
+ number: (t: string, v: number) => Writable<number>;
15
+ buffer: (t: string, v: Uint8Array) => Writable<Uint8Array>;
16
+ json: (t: string, v: object, codec: CoDec) => Writable<object>;
17
+ };
18
+ loader: Loader;
19
+ };
20
+ export declare const nil_xit: () => Xit;
21
+ export declare const json_string_codec: CoDec;
package/index.js CHANGED
@@ -1 +1,10 @@
1
- export { binding } from "./src/binding";
1
+ import { getContext } from "svelte";
2
+ import {} from "svelte/action";
3
+ import {} from "svelte/store";
4
+ export const nil_xit = () => {
5
+ return getContext("@nil-/xit");
6
+ };
7
+ export const json_string_codec = {
8
+ encode: (o) => new TextEncoder().encode(JSON.stringify(o)),
9
+ decode: (o) => JSON.parse(new TextDecoder().decode(o))
10
+ };
package/package.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "name": "@nil-/xit",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
+ "dependencies": {
5
+ "@rollup/browser": "^4.21.2",
6
+ "acorn": "^8.12.1",
7
+ "estree-walker": "^3.0.3",
8
+ "mdsvex": "^0.12.3",
9
+ "remark-admonitions": "^1.2.1",
10
+ "resolve.exports": "^2.0.2"
11
+ },
4
12
  "type": "module",
5
13
  "exports": {
6
14
  "./package.json": "./package.json",
package/message.proto DELETED
@@ -1,49 +0,0 @@
1
- syntax = "proto3";
2
-
3
- option optimize_for = LITE_RUNTIME;
4
-
5
- package nil.xit.proto;
6
-
7
- enum MessageType {
8
- MessageType_MarkupRequest = 0;
9
- MessageType_MarkupResponse = 1;
10
- MessageType_BindingRequest = 2;
11
- MessageType_BindingResponse = 3;
12
- MessageType_BindingUpdate = 4;
13
- MessageType_FileRequest = 5;
14
- MessageType_FileResponse = 6;
15
- }
16
-
17
- message MarkupResponse
18
- {
19
- repeated string components = 1;
20
- }
21
-
22
- message Binding
23
- {
24
- string tag = 1;
25
- oneof value {
26
- int64 value_i64 = 2;
27
- string value_str = 3;
28
- }
29
- }
30
-
31
- message BindingGroup
32
- {
33
- repeated Binding bindings = 1;
34
- }
35
-
36
- message BindingResponse
37
- {
38
- repeated BindingGroup info = 1;
39
- }
40
-
41
- message FileRequest
42
- {
43
- string target = 1;
44
- }
45
-
46
- message FileResponse {
47
- string target = 1;
48
- string content = 3;
49
- }
package/src/binding.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare const binding: <T>(tag: string, default_value: T) => {};
package/src/binding.js DELETED
@@ -1,10 +0,0 @@
1
- import { getContext } from "svelte";
2
- import { writable } from 'svelte/store';
3
- const create_writable = (value) => {
4
- const w = writable(value);
5
- w.subscribe((v) => console.log("not used", v));
6
- return w;
7
- };
8
- export const binding = (tag, default_value) => {
9
- return getContext(tag) ?? create_writable(default_value);
10
- };