roki-app 0.0.0-stage → 1.0.0

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/bin.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const targetDir = process.cwd();
11
+ const templateDir = path.join(__dirname, 'template');
12
+
13
+ console.log('šŸš€ Initializing a new Roki project...');
14
+
15
+ // Copy everything from template/ directly into the user's project folder
16
+ if (fs.existsSync(templateDir)) {
17
+ const files = fs.readdirSync(templateDir);
18
+ for (const file of files) {
19
+ const srcPath = path.join(templateDir, file);
20
+ const destPath = path.join(targetDir, file);
21
+
22
+ if (!fs.existsSync(destPath)) {
23
+ fs.copyFileSync(srcPath, destPath);
24
+ console.log(`āœ” Created ${file}`);
25
+ }
26
+ }
27
+ } else {
28
+ console.error('āŒ Error: Template directory not found!');
29
+ }
30
+
31
+ console.log('\nšŸŽ‰ Roki project initialized successfully!');
32
+ console.log('Run:\n npm install\n npm start');
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "roki-app",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "node main.js"
7
+ },
8
+ "dependencies": {
9
+ "@ogui/roki": "^1.0.0"
10
+ }
11
+ }
@@ -0,0 +1,285 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import express from 'express';
4
+
5
+ const app = express();
6
+ const PORT = 8000;
7
+
8
+ // --- 1. Widget & Stack Primitive ---
9
+ class Widget {
10
+ constructor(type) {
11
+ this.widgetType = type;
12
+ this._name = '';
13
+ this._text = '';
14
+ this._headerType = 1;
15
+ this._onClickFunc = null;
16
+ this._params = [];
17
+ }
18
+
19
+ name(val) {
20
+ this._name = val;
21
+ return this;
22
+ }
23
+
24
+ text(val) {
25
+ this._text = val;
26
+ return this;
27
+ }
28
+
29
+ type(val) {
30
+ this._headerType = parseInt(val, 10) || 1;
31
+ return this;
32
+ }
33
+
34
+ onclick(funcRef) {
35
+ // Automatically extract the function name string whether passed as a reference or a string
36
+ this._onClickFunc = typeof funcRef === 'function' ? funcRef.name : funcRef;
37
+ return this;
38
+ }
39
+
40
+ params(...args) {
41
+ this._params = args;
42
+ return this;
43
+ }
44
+
45
+ render() {
46
+ if (this.widgetType === 'Header') {
47
+ const tag = `h${Math.min(Math.max(this._headerType, 1), 6)}`;
48
+ return `<${tag} data-name="${this._name}" style="margin: 8px 0; color: #111;">${this._text}</${tag}>`;
49
+ } else if (this.widgetType === 'Label') {
50
+ return `<span data-name="${this._name}" style="font-size: 16px; color: #555; display: block; margin-top: 6px;">${this._text}</span>`;
51
+ } else if (this.widgetType === 'Button') {
52
+ let actionUrl = '?';
53
+ if (this._onClickFunc) {
54
+ actionUrl += `action=${this._onClickFunc}`;
55
+ }
56
+
57
+ if (this._params.length > 0) {
58
+ this._params.forEach((param, index) => {
59
+ actionUrl += `&arg${index}=${encodeURIComponent(param)}`;
60
+ });
61
+ }
62
+
63
+ return `<a href="${actionUrl}" data-name="${this._name}" style="display: inline-block; margin-top: 12px; padding: 10px 18px; background-color: #0066cc; color: white; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 14px;">${this._text}</a>`;
64
+ }
65
+ return `<div data-name="${this._name}">${this._text}</div>`;
66
+ }
67
+ }
68
+
69
+ class WidgetStack {
70
+ constructor() {
71
+ this.stack = [];
72
+ }
73
+
74
+ add(type) {
75
+ const widget = new Widget(type);
76
+ this.stack.push(widget);
77
+ return widget;
78
+ }
79
+ }
80
+
81
+ // --- 2. Screen Context ---
82
+ class Screen {
83
+ constructor(title, varName) {
84
+ this.title = title;
85
+ this.varName = varName;
86
+ this.pageRoute = '';
87
+ this.widgetStack = new WidgetStack();
88
+ this.renderedHTML = '';
89
+ }
90
+
91
+ selectpage(route) {
92
+ this.pageRoute = route.startsWith('/') ? route : `/${route}`;
93
+ return this;
94
+ }
95
+
96
+ get widgets() {
97
+ return {
98
+ add: (type) => this.widgetStack.add(type)
99
+ };
100
+ }
101
+
102
+ updatewidgets() {
103
+ let elements = '';
104
+
105
+ for (const widget of this.widgetStack.stack) {
106
+ elements += widget.render() + '\n';
107
+ }
108
+
109
+ this.renderedHTML = `
110
+ <div style="font-family: system-ui, sans-serif; padding: 24px; border: 1px solid #ddd; border-radius: 8px; max-width: 400px; margin: 40px auto; box-shadow: 0 4px 6px rgba(0,0,0,0.1); background: white;">
111
+ <div style="font-size: 14px; font-weight: 500; color: #666; margin-bottom: 12px; border-bottom: 1px solid #eee; padding-bottom: 6px;">
112
+ ${this.title}
113
+ </div>
114
+ ${elements}
115
+ </div>
116
+ `;
117
+ }
118
+
119
+ show() {
120
+ return this.renderedHTML;
121
+ }
122
+ }
123
+
124
+ // --- Helper: Screen Resolver ---
125
+ function resolveScreen(identifier, screensByVar, screenOrder) {
126
+ if (screensByVar[identifier]) return screensByVar[identifier];
127
+ const cleanRoute = identifier.startsWith('/') ? identifier : `/${identifier}`;
128
+ for (const s of screenOrder) {
129
+ if (s.pageRoute === cleanRoute) return s;
130
+ }
131
+ for (const s of screenOrder) {
132
+ if (s.title.toLowerCase() === identifier.toLowerCase()) return s;
133
+ }
134
+ return null;
135
+ }
136
+
137
+ // --- 3. Line-by-Line Interpreter ---
138
+ function runRoki(code, requestedPath, queryParams = {}) {
139
+ const screensByVar = {};
140
+ const screenOrder = [];
141
+ const functions = {};
142
+
143
+ const rawLines = code.split('\n').map((l) => l.trim());
144
+ const lines = [];
145
+
146
+ // Pass 1: Parse custom functions
147
+ let i = 0;
148
+ while (i < rawLines.length) {
149
+ const line = rawLines[i];
150
+ if (line.startsWith('createfunc')) {
151
+ const match = line.match(/^createfunc\s+(\w+)\(([^)]*)\)\s+do/);
152
+ if (match) {
153
+ const [, funcName] = match;
154
+ const body = [];
155
+ i++;
156
+ while (i < rawLines.length && rawLines[i] !== 'end') {
157
+ if (rawLines[i] && !rawLines[i].startsWith('#')) body.push(rawLines[i]);
158
+ i++;
159
+ }
160
+ functions[funcName] = (...passedArgs) => {
161
+ console.log(`[Roki Function Executed -> ${funcName}]`, passedArgs);
162
+ for (const bodyLine of body) {
163
+ if (bodyLine.startsWith('gotoscreen ') || bodyLine.startsWith('show ')) {
164
+ const target = bodyLine.replace(/^(gotoscreen|show)\s+/, '').replace(/"/g, '').trim();
165
+ const targetScreen = resolveScreen(target, screensByVar, screenOrder);
166
+ if (targetScreen) return targetScreen.show();
167
+ }
168
+ }
169
+ };
170
+ }
171
+ } else if (line) {
172
+ lines.push(line);
173
+ }
174
+ i++;
175
+ }
176
+
177
+ // Pass 2: Execute standard layout commands
178
+ for (const line of lines) {
179
+ if (line.startsWith('createscreen')) {
180
+ const match = line.match(/^createscreen\s+"([^"]+)"\s+as\s+(\w+)/);
181
+ if (match) {
182
+ const [, title, varName] = match;
183
+ const screen = new Screen(title, varName);
184
+ screensByVar[varName] = screen;
185
+ screenOrder.push(screen);
186
+ }
187
+ } else if (line.includes('.selectpage(')) {
188
+ const match = line.match(/^(\w+)\.selectpage\("([^"]+)"\)/);
189
+ if (match) {
190
+ const [, varName, route] = match;
191
+ if (screensByVar[varName]) {
192
+ screensByVar[varName].selectpage(route);
193
+ }
194
+ }
195
+ } else if (line.endsWith('.updatewidgets')) {
196
+ const varName = line.split('.')[0];
197
+ if (screensByVar[varName]) screensByVar[varName].updatewidgets();
198
+ } else if (line.includes('.widgets/add')) {
199
+ const parts = line.split('.widgets/add');
200
+ const varName = parts[0];
201
+ const chain = '.add' + parts[1];
202
+
203
+ if (screensByVar[varName]) {
204
+ const evalContext = { ...functions, screen: screensByVar[varName] };
205
+ const keys = Object.keys(evalContext);
206
+ const values = Object.values(evalContext);
207
+
208
+ new Function(...keys, `screen.widgets${chain}`)(...values);
209
+ }
210
+ }
211
+ }
212
+
213
+ // If a button action callback query was triggered, execute it!
214
+ if (queryParams.action && functions[queryParams.action]) {
215
+ const fnArgs = [];
216
+ let argIdx = 0;
217
+ while (queryParams[`arg${argIdx}`] !== undefined) {
218
+ fnArgs.push(queryParams[`arg${argIdx}`]);
219
+ argIdx++;
220
+ }
221
+ const resultHtml = functions[queryParams.action](...fnArgs);
222
+ if (resultHtml) return resultHtml;
223
+ }
224
+
225
+ let matchedScreen = null;
226
+
227
+ for (const screen of screenOrder) {
228
+ if (screen.pageRoute && screen.pageRoute === requestedPath) {
229
+ matchedScreen = screen;
230
+ break;
231
+ }
232
+ }
233
+
234
+ if (!matchedScreen) {
235
+ const cleanPath = requestedPath.replace(/^\//, '');
236
+ if (screensByVar[cleanPath]) {
237
+ matchedScreen = screensByVar[cleanPath];
238
+ }
239
+ }
240
+
241
+ if (!matchedScreen && requestedPath === '/' && screenOrder.length > 0) {
242
+ matchedScreen = screenOrder[0];
243
+ }
244
+
245
+ if (matchedScreen) {
246
+ return matchedScreen.show();
247
+ }
248
+
249
+ return `
250
+ <div style="font-family: system-ui, sans-serif; text-align: center; margin-top: 100px;">
251
+ <h1>404 - Screen Not Found</h1>
252
+ <p>No screen assigned to path <code>${requestedPath}</code></p>
253
+ <a href="/" style="color: #0066cc;">Return Home</a>
254
+ </div>
255
+ `;
256
+ }
257
+
258
+ // --- 4. Catch-All Route ---
259
+ app.get('*', (req, res) => {
260
+ const scriptPath = path.resolve('main.ruk');
261
+
262
+ if (fs.existsSync(scriptPath)) {
263
+ const rukCode = fs.readFileSync(scriptPath, 'utf-8');
264
+ const htmlBody = runRoki(rukCode, req.path, req.query);
265
+
266
+ res.send(`
267
+ <!DOCTYPE html>
268
+ <html lang="en">
269
+ <head>
270
+ <meta charset="UTF-8">
271
+ <title>Roki Engine</title>
272
+ </head>
273
+ <body style="background-color: #f9f9f9; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
274
+ ${htmlBody}
275
+ </body>
276
+ </html>
277
+ `);
278
+ } else {
279
+ res.status(404).send('Could not find main.ruk file!');
280
+ }
281
+ });
282
+
283
+ app.listen(PORT, () => {
284
+ console.log(`Roki engine active at http://localhost:${PORT}`);
285
+ });
@@ -0,0 +1,4 @@
1
+ createscreen "Example" as my
2
+ my.widgets/add("Header").name("HEADER").type(1).text("ROKI THING")
3
+ my.updatewidgets
4
+ show my
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "roki-app",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "roki"
7
+ },
8
+ "dependencies": {
9
+ "roki": "latest"
10
+ }
11
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.