mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
WIP
This commit is contained in:
parent
ff6d8cf942
commit
3bd296821c
@ -25,8 +25,9 @@
|
||||
.hash = "jetkv-0.0.0-zCv0fmCGAgCyYqwHjk0P5KrYVRew1MJAtbtAcIO-WPpT",
|
||||
},
|
||||
.zmpl = .{
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/9c54edd62b47aabdcb0e5f17beb45637b9de6a33.tar.gz",
|
||||
.hash = "zmpl-0.0.1-SYFGBl5sAwDu45IUKKH3TQRc4qZ8A1P2nNaU4iO3Zf-6",
|
||||
// .url = "https://github.com/jetzig-framework/zmpl/archive/89ee0ce9b4c96c316cc0575266fb66c864f24a49.tar.gz",
|
||||
// .hash = "zmpl-0.0.1-SYFGBtuNAwCj2YbqnoEJt3bk1iFIZjGK6JwMc72toZBR",
|
||||
.path = "../zmpl",
|
||||
},
|
||||
.httpz = .{
|
||||
.url = "https://github.com/karlseguin/http.zig/archive/37d7cb9819b804ade5f4b974b82f8dd0622225ed.tar.gz",
|
||||
|
@ -6,10 +6,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="/prism.css" />
|
||||
{{context.middleware.renderHeader()}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>{{zmpl.content}}</main>
|
||||
<script src="/prism.js"></script>
|
||||
{{context.middleware.renderFooter()}}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,6 +3,8 @@ const jetzig = @import("jetzig");
|
||||
|
||||
const Game = @import("../lib/Game.zig");
|
||||
|
||||
pub const layout = "application";
|
||||
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
@ -1,143 +1,3 @@
|
||||
<script>
|
||||
const channel = {
|
||||
websocket: null,
|
||||
actions: {},
|
||||
stateChangedCallbacks: [],
|
||||
messageCallbacks: [],
|
||||
invokeCallbacks: {},
|
||||
elementMap: {},
|
||||
transformerMap: {},
|
||||
transformers: {},
|
||||
onStateChanged: function(callback) { this.stateChangedCallbacks.push(callback); },
|
||||
onMessage: function(callback) { this.messageCallbacks.push(callback); },
|
||||
transform: function(ref, callback) {
|
||||
if (Object.hasOwn(this.transformers, ref)) {
|
||||
this.transformers[ref].push(callback);
|
||||
} else {
|
||||
this.transformers[ref] = [callback];
|
||||
}
|
||||
},
|
||||
receive: function(ref, callback) {
|
||||
if (Object.hasOwn(this.invokeCallbacks, ref)) {
|
||||
this.invokeCallbacks[ref].push(callback);
|
||||
} else {
|
||||
this.invokeCallbacks[ref] = [callback];
|
||||
}
|
||||
},
|
||||
publish: function(data) {
|
||||
if (this.websocket) {
|
||||
const json = JSON.stringify(data);
|
||||
this.websocket.send(json);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
(() => {
|
||||
|
||||
@if (context.request) |request|
|
||||
const transform = (value, state, element) => {
|
||||
const id = element.getAttribute('jetzig-id');
|
||||
const key = id && channel.transformerMap[id];
|
||||
const transformers = key && channel.transformers[key];
|
||||
if (transformers) {
|
||||
return transformers.reduce((acc, val) => val(acc), value);
|
||||
} else {
|
||||
return value === undefined || value == null ? '' : `${value}`
|
||||
}
|
||||
};
|
||||
|
||||
@if (request.headers.get("host")) |host|
|
||||
channel.websocket = new WebSocket('ws://{{host}}{{request.path.base_path}}');
|
||||
channel.websocket.addEventListener("message", (event) => {
|
||||
const state_tag = "__jetzig_channel_state__:";
|
||||
const actions_tag = "__jetzig_actions__:";
|
||||
const event_tag = "__jetzig_event__:";
|
||||
|
||||
if (event.data.startsWith(state_tag)) {
|
||||
const state = JSON.parse(event.data.slice(state_tag.length));
|
||||
Object.entries(channel.elementMap).forEach(([ref, elements]) => {
|
||||
const value = reduceState(ref, state);
|
||||
elements.forEach(element => element.innerHTML = transform(value, state, element));
|
||||
});
|
||||
channel.stateChangedCallbacks.forEach((callback) => {
|
||||
callback(state);
|
||||
});
|
||||
} else if (event.data.startsWith(event_tag)) {
|
||||
const data = JSON.parse(event.data.slice(event_tag.length));
|
||||
if (Object.hasOwn(channel.invokeCallbacks, data.method)) {
|
||||
channel.invokeCallbacks[data.method].forEach(callback => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
} else if (event.data.startsWith(actions_tag)) {
|
||||
const data = JSON.parse(event.data.slice(actions_tag.length));
|
||||
data.actions.forEach(action => {
|
||||
channel.actions[action.name] = (...params) => {
|
||||
if (action.params.length != params.length) {
|
||||
throw new Error(`Invalid params for action '${action.name}'`);
|
||||
}
|
||||
[...action.params].forEach((param, index) => {
|
||||
const map = {
|
||||
s: "string",
|
||||
b: "boolean",
|
||||
i: "number",
|
||||
f: "number",
|
||||
};
|
||||
if (map[param] !== typeof params[index]) {
|
||||
throw new Error(`Incorrect argument type for argument ${index} in '${action.name}'. Expected: ${map[param]}, found ${typeof params[index]}`);
|
||||
}
|
||||
});
|
||||
|
||||
channel.websocket.send(`_invoke:${action.name}:${JSON.stringify(params)}`);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
const data = JSON.parse(event.data);
|
||||
channel.messageCallbacks.forEach((callback) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const reduceState = (ref, state) => {
|
||||
if (!ref.startsWith('$.')) throw new Error(`Unexpected ref format: ${ref}`);
|
||||
const args = ref.split('.');
|
||||
args.shift();
|
||||
const isNumeric = (string) => [...string].every(char => '0123456789'.includes(char));
|
||||
const isObject = (object) => object && typeof object === 'object';
|
||||
return args.reduce((acc, arg) => {
|
||||
if (isNumeric(arg)) {
|
||||
if (acc && Array.isArray(acc) && acc.length > arg) return acc[parseInt(arg)];
|
||||
return null;
|
||||
} else {
|
||||
if (acc && isObject(acc)) return acc[arg];
|
||||
return null;
|
||||
}
|
||||
}, state);
|
||||
};
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('[jetzig-connect]').forEach(element => {
|
||||
const ref = element.getAttribute('jetzig-connect');
|
||||
if (!channel.elementMap[ref]) channel.elementMap[ref] = [];
|
||||
const id = `jetzig-${crypto.randomUUID()}`;
|
||||
element.setAttribute('jetzig-id', id);
|
||||
channel.elementMap[ref].push(element);
|
||||
channel.transformerMap[id] = element.getAttribute('jetzig-transform');
|
||||
});
|
||||
});
|
||||
|
||||
@// channel.websocket.addEventListener("open", (event) => {
|
||||
@// // TODO
|
||||
@// channel.publish("websockets", {});
|
||||
@// });
|
||||
@end
|
||||
@end
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div id="results-wrapper">
|
||||
<span class="trophy">🏆</span>
|
||||
<div id="results">
|
||||
@ -155,7 +15,15 @@
|
||||
|
||||
<div class="board" id="board">
|
||||
@for (0..9) |index| {
|
||||
<div class="cell" jetzig-connect="$.cells.{{index}}" jetzig-transform="cell" id="tic-tac-toe-cell-{{index}}" data-cell="{{index}}"></div>
|
||||
<div
|
||||
class="cell"
|
||||
jetzig-connect="$.cells.{{index}}"
|
||||
jetzig-transform="cell"
|
||||
jetzig-click="move"
|
||||
id="tic-tac-toe-cell-{{index}}"
|
||||
data-cell="{{index}}"
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -167,20 +35,20 @@
|
||||
<link rel="stylesheet" href="/party.css" />
|
||||
|
||||
<script>
|
||||
channel.onStateChanged(state => {
|
||||
jetzig.channel.onStateChanged(state => {
|
||||
if (!state.victor) {
|
||||
const element = document.querySelector("#victor");
|
||||
element.style.visibility = 'hidden';
|
||||
}
|
||||
});
|
||||
|
||||
channel.onMessage(message => {
|
||||
jetzig.channel.onMessage(message => {
|
||||
if (message.err) {
|
||||
console.log(message.err);
|
||||
}
|
||||
});
|
||||
|
||||
channel.receive("victor", (data) => {
|
||||
jetzig.channel.receive("victor", (data) => {
|
||||
const element = document.querySelector("#victor");
|
||||
const emoji = {
|
||||
player: "✈️",
|
||||
@ -192,24 +60,24 @@
|
||||
triggerPartyAnimation();
|
||||
});
|
||||
|
||||
channel.receive("game_over", (data) => {
|
||||
jetzig.channel.receive("game_over", (data) => {
|
||||
const element = document.querySelector("#board");
|
||||
element.classList.remove('flash-animation');
|
||||
void element.offsetWidth;
|
||||
element.classList.add('flash-animation');
|
||||
});
|
||||
|
||||
channel.transform("cell", (value) => (
|
||||
jetzig.channel.transform("cell", (value) => (
|
||||
{ player: "✈️", cpu: "🦎" }[value] || ""
|
||||
));
|
||||
document.querySelectorAll("#board div.cell").forEach(element => {
|
||||
element.addEventListener("click", () => {
|
||||
channel.actions.move(parseInt(element.dataset.cell));
|
||||
jetzig.channel.actions.move(parseInt(element.dataset.cell));
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector("#reset-button").addEventListener("click", () => {
|
||||
channel.actions.reset();
|
||||
jetzig.channel.actions.reset();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -17,6 +17,7 @@ pub const jetzig_options = struct {
|
||||
// jetzig.middleware.AuthMiddleware,
|
||||
// jetzig.middleware.AntiCsrfMiddleware,
|
||||
jetzig.middleware.HtmxMiddleware,
|
||||
jetzig.middleware.ChannelsMiddleware,
|
||||
// jetzig.middleware.InertiaMiddleware,
|
||||
// jetzig.middleware.CompressionMiddleware,
|
||||
// @import("app/middleware/DemoMiddleware.zig"),
|
||||
|
@ -28,16 +28,16 @@ const AppOptions = struct {
|
||||
pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
||||
defer self.env.deinit();
|
||||
|
||||
const action_router = comptime jetzig.channels.ActionRouter.initComptime(routes_module);
|
||||
_ = action_router;
|
||||
// inline for (action_router.actions) |action| std.debug.print("{s}\n", .{@typeName(action.params[0].type.?)});
|
||||
|
||||
if (self.initHook) |hook| try hook(@constCast(self));
|
||||
|
||||
var mime_map = jetzig.http.mime.MimeMap.init(self.allocator);
|
||||
defer mime_map.deinit();
|
||||
try mime_map.build();
|
||||
|
||||
inline for (jetzig.http.middleware.middlewares) |middleware| {
|
||||
if (@hasDecl(middleware, "setup")) try middleware.setup(@constCast(self));
|
||||
}
|
||||
|
||||
const routes = try createRoutes(self.allocator, if (@hasDecl(routes_module, "routes"))
|
||||
&routes_module.routes
|
||||
else
|
||||
|
@ -9,6 +9,31 @@ pub const TemplateContext = @This();
|
||||
|
||||
request: ?*http.Request = null,
|
||||
route: ?views.Route = null,
|
||||
middleware: Middleware = .{},
|
||||
|
||||
pub const Middleware = struct {
|
||||
context: *TemplateContext = undefined,
|
||||
|
||||
pub inline fn renderHeader(middleware: Middleware) ![]const u8 {
|
||||
return try middleware.render("header");
|
||||
}
|
||||
|
||||
pub inline fn renderFooter(middleware: Middleware) ![]const u8 {
|
||||
return try middleware.render("footer");
|
||||
}
|
||||
|
||||
fn render(middleware: Middleware, comptime section: []const u8) ![]const u8 {
|
||||
var buf = std.ArrayList(u8).init(middleware.context.*.request.?.allocator);
|
||||
const writer = buf.writer();
|
||||
inline for (http.middleware.middlewares) |middleware_type| {
|
||||
if (@hasDecl(middleware_type, "Blocks") and @hasDecl(middleware_type.Blocks, section)) {
|
||||
const renderFn = @field(middleware_type.Blocks, section);
|
||||
try renderFn(middleware.context.*, writer);
|
||||
}
|
||||
}
|
||||
return try buf.toOwnedSlice();
|
||||
}
|
||||
};
|
||||
|
||||
/// Return an authenticity token stored in the current request's session. If no token exists,
|
||||
/// generate and store before returning.
|
||||
|
@ -17,6 +17,7 @@ pub const RequestState = enum {
|
||||
after_request, // Initial middleware processing
|
||||
after_view, // View returned, response data ready for full response render
|
||||
rendered, // Rendered by middleware or view
|
||||
rendered_content, // Rendered a plain string by middleware or view
|
||||
redirected, // Redirected by middleware or view
|
||||
failed, // Failed by middleware or view
|
||||
before_response, // Post middleware processing
|
||||
@ -249,8 +250,8 @@ pub fn renderContent(
|
||||
) jetzig.views.View {
|
||||
if (self.isRendered()) self.rendered_multiple = true;
|
||||
|
||||
self.state = .rendered;
|
||||
self.rendered_view = .{ .data = self.response_data, .status_code = status_code, .content = content };
|
||||
self.state = .rendered_content;
|
||||
return self.rendered_view.?;
|
||||
}
|
||||
|
||||
@ -267,7 +268,7 @@ pub fn fail(self: *Request, status_code: jetzig.http.status_codes.StatusCode) je
|
||||
pub inline fn isRendered(self: *const Request) bool {
|
||||
return switch (self.state) {
|
||||
.initial, .processed, .after_request, .before_response => false,
|
||||
.after_view, .rendered, .redirected, .failed, .finalized => true,
|
||||
.after_view, .rendered, .rendered_content, .redirected, .failed, .finalized => true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -335,6 +336,9 @@ pub fn renderRedirect(self: *Request, state: RedirectState) !void {
|
||||
|
||||
var root = try self.response_data.root(.object);
|
||||
try root.put("location", self.response_data.string(state.location));
|
||||
var template_context = jetzig.TemplateContext{ .request = self };
|
||||
template_context.middleware.context = &template_context;
|
||||
|
||||
const content = switch (self.requestFormat()) {
|
||||
.HTML, .UNKNOWN => if (maybe_template) |template| blk: {
|
||||
try view.data.addConst("jetzig_view", view.data.string("internal"));
|
||||
@ -342,7 +346,7 @@ pub fn renderRedirect(self: *Request, state: RedirectState) !void {
|
||||
break :blk try template.render(
|
||||
self.response_data,
|
||||
jetzig.TemplateContext,
|
||||
.{ .request = self },
|
||||
template_context,
|
||||
&.{},
|
||||
.{},
|
||||
);
|
||||
|
@ -208,10 +208,13 @@ pub fn RoutedServer(Routes: type) type {
|
||||
) !bool {
|
||||
const route = self.matchChannelRoute(request.path.view_name) orelse return false;
|
||||
const session = try request.session();
|
||||
const session_id = session.getT(.string, "_id") orelse {
|
||||
const session_id = session.getT(.string, "_id") orelse blk: {
|
||||
try session.reset();
|
||||
break :blk session.getT(.string, "_id") orelse {
|
||||
try self.logger.ERROR("Error fetching session ID for websocket, aborting.", .{});
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
return try httpz.upgradeWebsocket(
|
||||
jetzig.websockets.RoutedWebsocket(Routes),
|
||||
@ -344,9 +347,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
return request.setResponse(rendered_error, .{});
|
||||
};
|
||||
|
||||
return if (request.state == .redirected or
|
||||
request.state == .failed or
|
||||
request.dynamic_assigned_template != null)
|
||||
return if (request.isRendered() or request.dynamic_assigned_template != null)
|
||||
request.setResponse(rendered, .{})
|
||||
else
|
||||
request.setResponse(try self.renderNotFound(request), .{});
|
||||
@ -429,6 +430,10 @@ pub fn RoutedServer(Routes: type) type {
|
||||
|
||||
if (request.rendered_view) |rendered_view| {
|
||||
if (request.state == .redirected) return .{ .view = rendered_view, .content = "" };
|
||||
if (request.state == .rendered_content) return .{
|
||||
.view = rendered_view,
|
||||
.content = rendered_view.content.?,
|
||||
};
|
||||
|
||||
if (template) |capture| {
|
||||
return .{
|
||||
@ -471,7 +476,8 @@ pub fn RoutedServer(Routes: type) type {
|
||||
) ![]const u8 {
|
||||
try addTemplateConstants(view, route);
|
||||
|
||||
const template_context = jetzig.TemplateContext{ .request = request };
|
||||
var template_context = jetzig.TemplateContext{ .request = request };
|
||||
template_context.middleware.context = &template_context;
|
||||
|
||||
if (request.getLayout(route)) |layout_name| {
|
||||
// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
|
||||
@ -487,6 +493,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
view.data,
|
||||
jetzig.TemplateContext,
|
||||
template_context,
|
||||
&.{},
|
||||
.{ .layout = layout },
|
||||
);
|
||||
} else {
|
||||
@ -495,6 +502,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
view.data,
|
||||
jetzig.TemplateContext,
|
||||
template_context,
|
||||
&.{},
|
||||
.{},
|
||||
);
|
||||
}
|
||||
@ -666,6 +674,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
request.response_data,
|
||||
jetzig.TemplateContext,
|
||||
.{ .request = request },
|
||||
&.{},
|
||||
.{},
|
||||
),
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ pub const CompressionMiddleware = @import("middleware/CompressionMiddleware.zig"
|
||||
pub const AuthMiddleware = @import("middleware/AuthMiddleware.zig");
|
||||
pub const AntiCsrfMiddleware = @import("middleware/AntiCsrfMiddleware.zig");
|
||||
pub const InertiaMiddleware = @import("middleware/InertiaMiddleware.zig");
|
||||
pub const ChannelsMiddleware = @import("middleware/ChannelsMiddleware.zig");
|
||||
|
||||
const RouteOptions = struct {
|
||||
content: ?[]const u8 = null,
|
||||
|
35
src/jetzig/middleware/ChannelsMiddleware.zig
Normal file
35
src/jetzig/middleware/ChannelsMiddleware.zig
Normal file
@ -0,0 +1,35 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
const ChannelsMiddleware = @This();
|
||||
|
||||
pub fn setup(app: *jetzig.App) !void {
|
||||
app.route(.GET, "/_channels.js", ChannelsMiddleware, .renderChannels);
|
||||
}
|
||||
|
||||
pub const Blocks = struct {
|
||||
pub fn header(_: jetzig.TemplateContext, writer: anytype) !void {
|
||||
try writer.writeAll(
|
||||
\\<script src="/_channels.js"></script>
|
||||
);
|
||||
}
|
||||
|
||||
pub fn footer(context: jetzig.TemplateContext, writer: anytype) !void {
|
||||
const request = context.request orelse return;
|
||||
const host = request.headers.getLower("host") orelse return;
|
||||
try writer.print(
|
||||
\\<script>
|
||||
\\ (() => {{
|
||||
\\ window.addEventListener('DOMContentLoaded', () => {{
|
||||
\\ jetzig.channel.init("{s}", "{s}");
|
||||
\\ }});
|
||||
\\ }})();
|
||||
\\</script>
|
||||
\\
|
||||
, .{ host, request.path.base_path });
|
||||
}
|
||||
};
|
||||
|
||||
pub fn renderChannels(request: *jetzig.Request) !jetzig.View {
|
||||
return request.renderContent(.ok, @embedFile("channels/channels.js"));
|
||||
}
|
@ -9,7 +9,7 @@ const HtmxMiddleware = @This();
|
||||
/// content rendered directly by the view function.
|
||||
pub fn afterRequest(request: *jetzig.http.Request) !void {
|
||||
if (request.headers.get("HX-Request")) |_| {
|
||||
try request.server.logger.DEBUG(
|
||||
try request.logger.DEBUG(
|
||||
"[middleware-htmx] HX-Request header, disabling layout.",
|
||||
.{},
|
||||
);
|
||||
|
145
src/jetzig/middleware/channels/channels.js
Normal file
145
src/jetzig/middleware/channels/channels.js
Normal file
@ -0,0 +1,145 @@
|
||||
window.jetzig = window.jetzig ? window.jetzig : {}
|
||||
jetzig = window.jetzig;
|
||||
|
||||
(() => {
|
||||
const transform = (value, state, element) => {
|
||||
const id = element.getAttribute('jetzig-id');
|
||||
const key = id && jetzig.channel.transformerMap[id];
|
||||
const transformers = key && jetzig.channel.transformers[key];
|
||||
if (transformers) {
|
||||
return transformers.reduce((acc, val) => val(acc), value);
|
||||
} else {
|
||||
return value === undefined || value == null ? '' : `${value}`
|
||||
}
|
||||
};
|
||||
|
||||
jetzig.channel = {
|
||||
websocket: null,
|
||||
actions: {},
|
||||
stateChangedCallbacks: [],
|
||||
messageCallbacks: [],
|
||||
invokeCallbacks: {},
|
||||
elementMap: {},
|
||||
transformerMap: {},
|
||||
transformers: {},
|
||||
onStateChanged: function(callback) { this.stateChangedCallbacks.push(callback); },
|
||||
onMessage: function(callback) { this.messageCallbacks.push(callback); },
|
||||
init: function(host, path) {
|
||||
console.log("here");
|
||||
this.websocket = new WebSocket(`ws://${host}${path}`);
|
||||
this.websocket.addEventListener("message", (event) => {
|
||||
const state_tag = "__jetzig_channel_state__:";
|
||||
const actions_tag = "__jetzig_actions__:";
|
||||
const event_tag = "__jetzig_event__:";
|
||||
|
||||
if (event.data.startsWith(state_tag)) {
|
||||
const state = JSON.parse(event.data.slice(state_tag.length));
|
||||
Object.entries(this.elementMap).forEach(([ref, elements]) => {
|
||||
const value = reduceState(ref, state);
|
||||
elements.forEach(element => element.innerHTML = transform(value, state, element));
|
||||
});
|
||||
this.stateChangedCallbacks.forEach((callback) => {
|
||||
callback(state);
|
||||
});
|
||||
} else if (event.data.startsWith(event_tag)) {
|
||||
const data = JSON.parse(event.data.slice(event_tag.length));
|
||||
if (Object.hasOwn(this.invokeCallbacks, data.method)) {
|
||||
this.invokeCallbacks[data.method].forEach(callback => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
} else if (event.data.startsWith(actions_tag)) {
|
||||
const data = JSON.parse(event.data.slice(actions_tag.length));
|
||||
data.actions.forEach(action => {
|
||||
this.actions[action.name] = {
|
||||
callback: (...params) => {
|
||||
if (action.params.length != params.length) {
|
||||
throw new Error(`Invalid params for action '${action.name}'`);
|
||||
}
|
||||
[...action.params].forEach((param, index) => {
|
||||
const map = {
|
||||
s: "string",
|
||||
b: "boolean",
|
||||
i: "number",
|
||||
f: "number",
|
||||
};
|
||||
if (map[param] !== typeof params[index]) {
|
||||
throw new Error(`Incorrect argument type for argument ${index} in '${action.name}'. Expected: ${map[param]}, found ${typeof params[index]}`);
|
||||
}
|
||||
});
|
||||
|
||||
this.websocket.send(`_invoke:${action.name}:${JSON.stringify(params)}`);
|
||||
},
|
||||
spec: { ...action },
|
||||
};
|
||||
});
|
||||
console.log(this.actions);
|
||||
} else {
|
||||
const data = JSON.parse(event.data);
|
||||
this.messageCallbacks.forEach((callback) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const reduceState = (ref, state) => {
|
||||
if (!ref.startsWith('$.')) throw new Error(`Unexpected ref format: ${ref}`);
|
||||
const args = ref.split('.');
|
||||
args.shift();
|
||||
const isNumeric = (string) => [...string].every(char => '0123456789'.includes(char));
|
||||
const isObject = (object) => object && typeof object === 'object';
|
||||
return args.reduce((acc, arg) => {
|
||||
if (isNumeric(arg)) {
|
||||
if (acc && Array.isArray(acc) && acc.length > arg) return acc[parseInt(arg)];
|
||||
return null;
|
||||
} else {
|
||||
if (acc && isObject(acc)) return acc[arg];
|
||||
return null;
|
||||
}
|
||||
}, state);
|
||||
};
|
||||
|
||||
document.querySelectorAll('[jetzig-connect]').forEach(element => {
|
||||
const ref = element.getAttribute('jetzig-connect');
|
||||
if (!this.elementMap[ref]) this.elementMap[ref] = [];
|
||||
const id = `jetzig-${crypto.randomUUID()}`;
|
||||
element.setAttribute('jetzig-id', id);
|
||||
this.elementMap[ref].push(element);
|
||||
this.transformerMap[id] = element.getAttribute('jetzig-transform');
|
||||
});
|
||||
document.querySelectorAll('[jetzig-click]').forEach(element => {
|
||||
const ref = element.getAttribute('jetzig-click');
|
||||
const action = this.actions[ref];
|
||||
if (action) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// this.websocket.addEventListener("open", (event) => {
|
||||
// // TODO
|
||||
// this.publish("websockets", {});
|
||||
// });
|
||||
},
|
||||
transform: function(ref, callback) {
|
||||
if (Object.hasOwn(this.transformers, ref)) {
|
||||
this.transformers[ref].push(callback);
|
||||
} else {
|
||||
this.transformers[ref] = [callback];
|
||||
}
|
||||
},
|
||||
receive: function(ref, callback) {
|
||||
if (Object.hasOwn(this.invokeCallbacks, ref)) {
|
||||
this.invokeCallbacks[ref].push(callback);
|
||||
} else {
|
||||
this.invokeCallbacks[ref] = [callback];
|
||||
}
|
||||
},
|
||||
publish: function(data) {
|
||||
if (this.websocket) {
|
||||
const json = JSON.stringify(data);
|
||||
this.websocket.send(json);
|
||||
}
|
||||
},
|
||||
};
|
||||
})();
|
3
src/jetzig/templates/channels.zmpl
Normal file
3
src/jetzig/templates/channels.zmpl
Normal file
@ -0,0 +1,3 @@
|
||||
@block head {
|
||||
<script>console.log("hello");</script>
|
||||
}
|
@ -92,6 +92,8 @@ pub fn format(self: Route, _: []const u8, _: anytype, writer: anytype) !void {
|
||||
pub fn match(self: Route, request: *const jetzig.http.Request) bool {
|
||||
if (self.method != request.method) return false;
|
||||
|
||||
if (std.mem.eql(u8, request.path.file_path, self.uri_path)) return true;
|
||||
|
||||
var request_path_it = std.mem.splitScalar(u8, request.path.base_path, '/');
|
||||
var uri_path_it = std.mem.splitScalar(u8, self.uri_path, '/');
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user