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
b795c6184e
commit
d3113d12fe
@ -2,33 +2,36 @@
|
||||
|
||||
<div id="party-container"></div>
|
||||
|
||||
<div id="results-wrapper">
|
||||
<span class="trophy">🏆</span>
|
||||
<div id="results">
|
||||
<div>Player</div>
|
||||
<div id="player-wins" jetzig-connect="$.results.player"></div>
|
||||
<div>CPU</div>
|
||||
<div id="cpu-wins" jetzig-connect="$.results.cpu"></div>
|
||||
<div>Tie</div>
|
||||
<div id="ties" jetzig-connect="$.results.tie"></div>
|
||||
</div>
|
||||
<span class="trophy">🏆</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="board" id="board">
|
||||
@for (0..9) |index| {
|
||||
<div
|
||||
class="cell"
|
||||
jetzig-connect="$.cells.{{index}}"
|
||||
jetzig-transform="{ player: '✈️', cpu: '🦎', tie: '🤝' }[value] || ''"
|
||||
jetzig-click="move"
|
||||
id="tic-tac-toe-cell-{{index}}"
|
||||
data-cell="{{index}}"
|
||||
>
|
||||
<jetzig-scope name="game">
|
||||
<div id="results-wrapper">
|
||||
<span class="trophy">🏆</span>
|
||||
<div id="results">
|
||||
<div>Player</div>
|
||||
<div id="player-wins" jetzig-scope="game" jetzig-connect="$.results.player"></div>
|
||||
<div>CPU</div>
|
||||
<div id="cpu-wins" jetzig-scope="game" jetzig-connect="$.results.cpu"></div>
|
||||
<div>Tie</div>
|
||||
<div id="ties" jetzig-scope="game" jetzig-connect="$.results.tie"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<span class="trophy">🏆</span>
|
||||
</div>
|
||||
|
||||
<div class="board" id="board">
|
||||
@for (0..9) |index| {
|
||||
<div
|
||||
class="cell"
|
||||
jetzig-connect="$.cells.{{index}}"
|
||||
jetzig-transform="{ player: '✈️', cpu: '🦎', tie: '🤝' }[value] || ''"
|
||||
jetzig-scope="game"
|
||||
jetzig-click="move"
|
||||
id="tic-tac-toe-cell-{{index}}"
|
||||
data-cell="{{index}}"
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</jetzig-scope>
|
||||
|
||||
|
||||
<div id="reset-wrapper">
|
||||
<button jetzig-click="reset" id="reset-button">Reset Game</button>
|
||||
@ -38,23 +41,24 @@
|
||||
<span>🏆</span>
|
||||
<span
|
||||
jetzig-connect="$.victor"
|
||||
jetzig-scope="game"
|
||||
jetzig-transform="{ player: '✈️', cpu: '🦎', tie: '🤝' }[value] || ''"
|
||||
></span>
|
||||
<span>🏆</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@// jetzig.channel.onStateChanged(state => {
|
||||
@// Jetzig.channel.onStateChanged((scope, state) => {
|
||||
@// });
|
||||
@//
|
||||
@// jetzig.channel.onMessage(data => {
|
||||
@// Jetzig.channel.onMessage(data => {
|
||||
@// });
|
||||
@//
|
||||
@// jetzig.channel.receive("victor", data => {
|
||||
@// Jetzig.channel.receive("victor", data => {
|
||||
@// triggerPartyAnimation();
|
||||
@// });
|
||||
@//
|
||||
@// jetzig.channel.receive("game_over", data => {
|
||||
@// Jetzig.channel.receive("game_over", data => {
|
||||
@// const element = document.querySelector("#board");
|
||||
@// element.classList.remove('flash-animation');
|
||||
@// void element.offsetWidth;
|
||||
|
@ -23,8 +23,8 @@ pub fn RoutedChannel(Routes: type) type {
|
||||
env: Env,
|
||||
|
||||
const Connection = struct {
|
||||
object: *jetzig.data.Value,
|
||||
data: *jetzig.Data,
|
||||
state: *jetzig.data.Value,
|
||||
key: []const u8,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, websocket: *jetzig.websockets.Websocket) !Channel {
|
||||
@ -86,7 +86,9 @@ pub fn RoutedChannel(Routes: type) type {
|
||||
|
||||
pub fn connect(channel: Channel, comptime scope: []const u8) !*jetzig.data.Value {
|
||||
if (channel._connections.get(scope)) |cached| {
|
||||
return cached.object;
|
||||
// Ensure an identical value is returned for each invocation of `connect` for a
|
||||
// given scope.
|
||||
return cached.state;
|
||||
}
|
||||
|
||||
if (channel.websocket.session_id.len != 32) return error.JetzigInvalidSessionIdLength;
|
||||
@ -105,19 +107,14 @@ pub fn RoutedChannel(Routes: type) type {
|
||||
break :blk id;
|
||||
};
|
||||
|
||||
var buf: [32 + ":".len + 32]u8 = undefined;
|
||||
const connection_key = try std.fmt.bufPrint(&buf, "{s}:{s}", .{ channel.websocket.session_id, connection_id });
|
||||
return try channel.websocket.channels.get(channel.data, connection_key) orelse blk: {
|
||||
const data = try channel.allocator.create(jetzig.Data);
|
||||
data.* = jetzig.Data.init(channel.allocator);
|
||||
const object = try data.root(.object);
|
||||
const duped_connection_key = try channel.allocator.dupe(u8, connection_key);
|
||||
try channel.websocket.channels.put(duped_connection_key, object);
|
||||
try channel._connections.put(scope, .{ .data = data, .object = object });
|
||||
const connections_state = channel.get("_connections_state") orelse try channel.put("_connections_state", .object);
|
||||
try connections_state.put(scope, object);
|
||||
break :blk object;
|
||||
const connection_key = try std.fmt.allocPrint(channel.allocator, "{s}:{s}", .{ channel.websocket.session_id, connection_id });
|
||||
const state = try channel.websocket.channels.get(channel.data, connection_key) orelse blk: {
|
||||
const state = try channel.data.object();
|
||||
try channel.websocket.channels.put(connection_key, state);
|
||||
break :blk state;
|
||||
};
|
||||
try channel._connections.put(scope, .{ .key = connection_key, .state = state });
|
||||
return state;
|
||||
}
|
||||
|
||||
pub fn getT(
|
||||
@ -145,12 +142,13 @@ pub fn RoutedChannel(Routes: type) type {
|
||||
}
|
||||
|
||||
pub fn sync(channel: Channel) !void {
|
||||
try channel.websocket.syncState(channel.data, "__root__");
|
||||
try channel.websocket.syncState(channel.state, "__root__", channel.websocket.session_id);
|
||||
|
||||
var it = channel._connections.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const data = entry.value_ptr.*.data;
|
||||
const connection = entry.value_ptr.*;
|
||||
const scope = entry.key_ptr.*;
|
||||
try channel.websocket.syncState(data, scope);
|
||||
try channel.websocket.syncState(connection.state, scope, connection.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -155,8 +155,7 @@ pub fn Store(comptime options: KVOptions) type {
|
||||
|
||||
fn parseValue(data: *jetzig.data.Data, maybe_json: ?[]const u8) !?*jetzig.data.Value {
|
||||
if (maybe_json) |json| {
|
||||
try data.fromJson(json);
|
||||
return data.value.?;
|
||||
return try data.parseJsonSlice(json);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ pub const Blocks = struct {
|
||||
\\<script>
|
||||
\\ (() => {{
|
||||
\\ window.addEventListener('DOMContentLoaded', () => {{
|
||||
\\ jetzig.channel.init("{s}", "{s}");
|
||||
\\ Jetzig.channel.init("{s}", "{s}");
|
||||
\\ }});
|
||||
\\ }})();
|
||||
\\</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
window.jetzig = window.jetzig ? window.jetzig : {}
|
||||
jetzig = window.jetzig;
|
||||
window.Jetzig = window.Jetzig ? window.Jetzig : {}
|
||||
const Jetzig = window.Jetzig;
|
||||
|
||||
(() => {
|
||||
const state_tag = "__jetzig_channel_state__:";
|
||||
@ -8,7 +8,7 @@ jetzig = window.jetzig;
|
||||
|
||||
const transform = (value, state, element) => {
|
||||
const id = element.getAttribute('jetzig-id');
|
||||
const transformer = id && jetzig.channel.transformers[id];
|
||||
const transformer = id && Jetzig.channel.transformers[id];
|
||||
if (transformer) {
|
||||
return transformer(value, state, element);
|
||||
} else {
|
||||
@ -38,12 +38,14 @@ jetzig = window.jetzig;
|
||||
const detagged = event.data.slice(state_tag.length);
|
||||
const scope = detagged.split(':', 1)[0];
|
||||
const state = JSON.parse(detagged.slice(scope.length + 1));
|
||||
Object.entries(channel.elementMap).forEach(([ref, elements]) => {
|
||||
console.log(scope, state);
|
||||
Object.entries(channel.scopedElements(scope)).forEach(([ref, elements]) => {
|
||||
const value = reduceState(ref, state);
|
||||
console.log(ref, state);
|
||||
elements.forEach(element => element.innerHTML = transform(value, state, element));
|
||||
});
|
||||
channel.stateChangedCallbacks.forEach((callback) => {
|
||||
callback(state);
|
||||
callback(scope, state);
|
||||
});
|
||||
};
|
||||
|
||||
@ -61,40 +63,40 @@ jetzig = window.jetzig;
|
||||
data.actions.forEach(action => {
|
||||
channel.action_specs[action.name] = {
|
||||
callback: (...params) => {
|
||||
if (action.params.length != params.length) {
|
||||
throw new Error(`Invalid params for action '${action.name}'. Expected ${action.params.length} params, found ${params.length}`);
|
||||
}
|
||||
[...action.params].forEach((param, index) => {
|
||||
if (param.type !== typeof params[index]) {
|
||||
const err = `Incorrect argument type for argument ${index} in '${action.name}'. Expected: ${param.type}, found ${typeof params[index]}`;
|
||||
switch (param.type) {
|
||||
case "string":
|
||||
params[index] = `${params[index]}`;
|
||||
break;
|
||||
case "integer":
|
||||
try { params[index] = parseInt(params[index]) } catch {
|
||||
throw new Error(err);
|
||||
};
|
||||
break;
|
||||
case "float":
|
||||
try { params[index] = parseFloat(params[index]) } catch {
|
||||
throw new Error(err);
|
||||
};
|
||||
case "boolean":
|
||||
params[index] = ["true", "y", "1", "yes", "t"].includes(params[index]);
|
||||
break;
|
||||
default:
|
||||
throw new Error(err);
|
||||
}
|
||||
if (action.params.length != params.length) {
|
||||
throw new Error(`Invalid params for action '${action.name}'. Expected ${action.params.length} params, found ${params.length}`);
|
||||
}
|
||||
});
|
||||
|
||||
channel.websocket.send(`_invoke:${action.name}:${JSON.stringify(params)}`);
|
||||
[...action.params].forEach((param, index) => {
|
||||
if (param.type !== typeof params[index]) {
|
||||
const err = `Incorrect argument type for argument ${index} in '${action.name}'. Expected: ${param.type}, found ${typeof params[index]}`;
|
||||
switch (param.type) {
|
||||
case "string":
|
||||
params[index] = `${params[index]}`;
|
||||
break;
|
||||
case "integer":
|
||||
try { params[index] = parseInt(params[index]) } catch {
|
||||
throw new Error(err);
|
||||
};
|
||||
break;
|
||||
case "float":
|
||||
try { params[index] = parseFloat(params[index]) } catch {
|
||||
throw new Error(err);
|
||||
};
|
||||
case "boolean":
|
||||
params[index] = ["true", "y", "1", "yes", "t", true].includes(params[index]);
|
||||
break;
|
||||
default:
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
channel.websocket.send(`_invoke:${action.name}:${JSON.stringify(params)}`);
|
||||
},
|
||||
spec: { ...action },
|
||||
};
|
||||
channel.actions[action.name] = channel.action_specs[action.name].callback;
|
||||
spec: { ...action },
|
||||
};
|
||||
channel.actions[action.name] = channel.action_specs[action.name].callback;
|
||||
});
|
||||
|
||||
document.querySelectorAll('[jetzig-click]').forEach(element => {
|
||||
const ref = element.getAttribute('jetzig-click');
|
||||
const action = channel.action_specs[ref];
|
||||
@ -117,7 +119,58 @@ jetzig = window.jetzig;
|
||||
});
|
||||
};
|
||||
|
||||
jetzig.channel = {
|
||||
const initElementConnections = (channel) => {
|
||||
document.querySelectorAll('[jetzig-connect]').forEach(element => {
|
||||
const ref = element.getAttribute('jetzig-connect');
|
||||
const id = `jetzig-${crypto.randomUUID()}`;
|
||||
element.setAttribute('jetzig-id', id);
|
||||
const scope = element.getAttribute('jetzig-scope') || '__root__';
|
||||
if (!channel.elementMap[scope]) channel.elementMap[scope] = {};
|
||||
if (!channel.elementMap[scope][ref]) channel.elementMap[scope][ref] = [];
|
||||
channel.elementMap[scope][ref].push(element);
|
||||
const transformer = element.getAttribute('jetzig-transform');
|
||||
if (transformer) {
|
||||
channel.transformers[id] = new Function("value", "$", "element", `return ${transformer};`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initStyledElements = (channel) => {
|
||||
const styled_elements = document.querySelectorAll('[jetzig-style]');
|
||||
channel.onStateChanged(state => {
|
||||
styled_elements.forEach(element => {
|
||||
const func = new Function("$", `return ${element.getAttribute('jetzig-style')};`)
|
||||
const styles = func(state);
|
||||
Object.entries(styles).forEach(([key, value]) => {
|
||||
element.style.setProperty(key, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const initWebsocket = (channel, host, path) => {
|
||||
channel.websocket = new WebSocket(`ws://${host}${path}`);
|
||||
channel.websocket.addEventListener("message", (event) => {
|
||||
if (event.data.startsWith(state_tag)) {
|
||||
handleState(event, channel);
|
||||
} else if (event.data.startsWith(event_tag)) {
|
||||
handleEvent(event, channel);
|
||||
} else if (event.data.startsWith(actions_tag)) {
|
||||
handleAction(event, channel);
|
||||
} else {
|
||||
const data = JSON.parse(event.data);
|
||||
channel.messageCallbacks.forEach((callback) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
channel.websocket.addEventListener("open", (event) => {
|
||||
// TODO
|
||||
channel.publish("websockets", {});
|
||||
});
|
||||
};
|
||||
|
||||
Jetzig.channel = {
|
||||
websocket: null,
|
||||
actions: {},
|
||||
action_specs: {},
|
||||
@ -128,50 +181,11 @@ jetzig = window.jetzig;
|
||||
transformers: {},
|
||||
onStateChanged: function(callback) { this.stateChangedCallbacks.push(callback); },
|
||||
onMessage: function(callback) { this.messageCallbacks.push(callback); },
|
||||
scopedElements: function(scope) { return this.elementMap[scope] || {}; },
|
||||
init: function(host, path) {
|
||||
this.websocket = new WebSocket(`ws://${host}${path}`);
|
||||
this.websocket.addEventListener("message", (event) => {
|
||||
if (event.data.startsWith(state_tag)) {
|
||||
handleState(event, this);
|
||||
} else if (event.data.startsWith(event_tag)) {
|
||||
handleEvent(event, this);
|
||||
} else if (event.data.startsWith(actions_tag)) {
|
||||
handleAction(event, this);
|
||||
} else {
|
||||
const data = JSON.parse(event.data);
|
||||
this.messageCallbacks.forEach((callback) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
const transformer = element.getAttribute('jetzig-transform');
|
||||
if (transformer) {
|
||||
this.transformers[id] = new Function("value", "$", "element", `return ${transformer};`);
|
||||
}
|
||||
});
|
||||
|
||||
const styled_elements = document.querySelectorAll('[jetzig-style]');
|
||||
this.onStateChanged(state => {
|
||||
styled_elements.forEach(element => {
|
||||
const func = new Function("$", `return ${element.getAttribute('jetzig-style')};`)
|
||||
const styles = func(state);
|
||||
Object.entries(styles).forEach(([key, value]) => {
|
||||
element.style.setProperty(key, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// this.websocket.addEventListener("open", (event) => {
|
||||
// // TODO
|
||||
// this.publish("websockets", {});
|
||||
// });
|
||||
initWebsocket(this, host, path);
|
||||
initElementConnections(this);
|
||||
initStyledElements(this);
|
||||
},
|
||||
receive: function(ref, callback) {
|
||||
if (Object.hasOwn(this.invokeCallbacks, ref)) {
|
||||
|
@ -93,9 +93,7 @@ pub fn RoutedWebsocket(Routes: type) type {
|
||||
websocket.logger.DEBUG("Routed Channel message for `{s}`", .{websocket.route.path}) catch {};
|
||||
}
|
||||
|
||||
pub fn syncState(websocket: *Websocket, data: *jetzig.Data, scope: []const u8) !void {
|
||||
const value = data.value orelse return;
|
||||
|
||||
pub fn syncState(websocket: *Websocket, value: *jetzig.data.Value, scope: []const u8, state_key: []const u8) !void {
|
||||
var stack_fallback = std.heap.stackFallback(4096, websocket.allocator);
|
||||
const allocator = stack_fallback.get();
|
||||
|
||||
@ -105,8 +103,8 @@ pub fn RoutedWebsocket(Routes: type) type {
|
||||
const writer = write_buffer.writer();
|
||||
|
||||
// TODO: Make this really fast.
|
||||
try websocket.channels.put(websocket.session_id, value);
|
||||
try writer.print("__jetzig_channel_state__:{s}:{s}", .{ scope, try data.toJson() });
|
||||
try websocket.channels.put(state_key, value);
|
||||
try writer.print("__jetzig_channel_state__:{s}:{s}", .{ scope, try value.toJson() });
|
||||
try write_buffer.flush();
|
||||
|
||||
websocket.logger.DEBUG("Synchronized Channel state for `{s}`", .{websocket.route.path}) catch {};
|
||||
|
Loading…
x
Reference in New Issue
Block a user