diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp
index ff1a55d..2dfc625 100644
--- a/story-editor/src/main_window.cpp
+++ b/story-editor/src/main_window.cpp
@@ -30,7 +30,7 @@ MainWindow::MainWindow()
, m_nodeEditorWindow(*this)
, m_libraryWindow(*this, m_libraryManager)
, m_player(*this)
-
+ , m_webServer(m_libraryManager)
{
// VM Initialize
m_chip32_ctx.stack_size = 512;
diff --git a/story-editor/src/web_server.cpp b/story-editor/src/web_server.cpp
index 008e041..2fc0148 100644
--- a/story-editor/src/web_server.cpp
+++ b/story-editor/src/web_server.cpp
@@ -1,175 +1,68 @@
#include The request was:This is the Foo GET handler!!!
\n");
- mg_printf(conn,
- "%s %s HTTP/%s
The request was:
%s %s HTTP/%s\n", - req_info->request_method, - req_info->request_uri, - req_info->http_version); - mg_printf(conn, "
Content Length: %li
\n", (long)tlen); - mg_printf(conn, "\n");
+bool LibraryManagerHandler::handleGet(CivetServer *server, struct mg_connection *conn)
+{
+ const struct mg_request_info *req_info = mg_get_request_info(conn);
+ nlohmann::json json;
- while (nlen < tlen) {
- rlen = tlen - nlen;
- if (rlen > sizeof(buf)) {
- rlen = sizeof(buf);
- }
- rlen = mg_read(conn, buf, (size_t)rlen);
- if (rlen <= 0) {
- break;
- }
- wlen = mg_write(conn, buf, (size_t)rlen);
- if (wlen != rlen) {
- break;
- }
- nlen += wlen;
- }
-
- mg_printf(conn, "\n\n");
- mg_printf(conn, "\n");
-
- return true;
- }
-
- #define fopen_recursive fopen
-
- bool
- handlePut(CivetServer *server, struct mg_connection *conn)
+ for (auto &s : m_libraryManager)
{
- /* Handler may access the request info using mg_get_request_info */
- const struct mg_request_info *req_info = mg_get_request_info(conn);
- long long rlen, wlen;
- long long nlen = 0;
- long long tlen = req_info->content_length;
- FILE * f;
- char buf[1024];
- int fail = 0;
-
-#ifdef _WIN32
- _snprintf(buf, sizeof(buf), "D:\\somewhere\\%s\\%s", req_info->remote_user, req_info->local_uri);
- buf[sizeof(buf)-1] = 0;
- if (strlen(buf)>255) {
- /* Windows will not work with path > 260 (MAX_PATH), unless we use
- * the unicode API. However, this is just an example code: A real
- * code will probably never store anything to D:\\somewhere and
- * must be adapted to the specific needs anyhow. */
- fail = 1;
- f = NULL;
- } else {
- f = fopen_recursive(buf, "wb");
- }
-#else
- snprintf(buf, sizeof(buf), "~/somewhere/%s/%s", req_info->remote_user, req_info->local_uri);
- buf[sizeof(buf)-1] = 0;
- if (strlen(buf)>1020) {
- /* The string is too long and probably truncated. Make sure an
- * UTF-8 string is never truncated between the UTF-8 code bytes.
- * This example code must be adapted to the specific needs. */
- fail = 1;
- f = NULL;
- } else {
- f = fopen_recursive(buf, "w");
- }
-#endif
-
- if (!f) {
- fail = 1;
- } else {
- while (nlen < tlen) {
- rlen = tlen - nlen;
- if (rlen > sizeof(buf)) {
- rlen = sizeof(buf);
- }
- rlen = mg_read(conn, buf, (size_t)rlen);
- if (rlen <= 0) {
- fail = 1;
- break;
- }
- wlen = fwrite(buf, 1, (size_t)rlen, f);
- if (wlen != rlen) {
- fail = 1;
- break;
- }
- nlen += wlen;
- }
- fclose(f);
- }
-
- if (fail) {
- mg_printf(conn,
- "HTTP/1.1 409 Conflict\r\n"
- "Content-Type: text/plain\r\n"
- "Connection: close\r\n\r\n");
- } else {
- mg_printf(conn,
- "HTTP/1.1 201 Created\r\n"
- "Content-Type: text/plain\r\n"
- "Connection: close\r\n\r\n");
- }
-
- return true;
+ nlohmann::json story = {
+ {"title", s->GetName() },
+ {"uuid", s->GetUuid() },
+ };
+ json.push_back(story);
+
}
-};
+ return Reply(conn, json);
+}
+
static const char *options[] = {
"document_root", DOCUMENT_ROOT,
"listening_ports", PORT,
-0
+ "access_control_allow_origin", "*",
+ "access_control_allow_methods", "GET, POST, PUT, DELETE, OPTIONS",
+ "access_control_allow_headers", "Content-Type",
+ 0
};
-WebServer::WebServer()
- : m_server(options)
+static const std::string gRestBase = "/api/v1";
+
+WebServer::WebServer(LibraryManager &libraryManager)
+ : m_libraryManager(libraryManager)
+ , m_server(options)
+ , m_libraryManagerHandler(libraryManager)
{
mg_init_library(0);
- FooHandler h_foo;
- m_server.addHandler("**.foo", h_foo);
- printf("Browse files at http://localhost:%s/\n", PORT);
+ m_server.addHandler(gRestBase + "/library/list", m_libraryManagerHandler);
+// printf("Browse files at http://localhost:%s/\n", PORT);
}
WebServer::~WebServer()
diff --git a/story-editor/src/web_server.h b/story-editor/src/web_server.h
index eb016f6..6c75ec7 100644
--- a/story-editor/src/web_server.h
+++ b/story-editor/src/web_server.h
@@ -1,16 +1,169 @@
#pragma once
#include "CivetServer.h"
+#include "library_manager.h"
+
+class HandlerBase : public CivetHandler
+{
+public:
+
+protected:
+ // Utility methods for children
+
+ bool Reply(struct mg_connection *conn, const nlohmann::json &json);
+};
+
+class LibraryManagerHandler : public HandlerBase
+{
+public:
+ LibraryManagerHandler(LibraryManager &libraryManager)
+ : m_libraryManager(libraryManager)
+ {
+
+ }
+
+ bool handleGet(CivetServer *server, struct mg_connection *conn);
+
+ bool
+ handlePost(CivetServer *server, struct mg_connection *conn)
+ {
+ /* Handler may access the request info using mg_get_request_info */
+ const struct mg_request_info *req_info = mg_get_request_info(conn);
+ long long rlen, wlen;
+ long long nlen = 0;
+ long long tlen = req_info->content_length;
+ char buf[1024];
+
+ mg_printf(conn,
+ "HTTP/1.1 200 OK\r\nContent-Type: "
+ "text/html\r\nConnection: close\r\n\r\n");
+
+ mg_printf(conn, "\n");
+ mg_printf(conn, "The request was:
%s %s HTTP/%s\n", + req_info->request_method, + req_info->request_uri, + req_info->http_version); + mg_printf(conn, "
Content Length: %li
\n", (long)tlen); + mg_printf(conn, "\n");
+
+ while (nlen < tlen) {
+ rlen = tlen - nlen;
+ if (rlen > sizeof(buf)) {
+ rlen = sizeof(buf);
+ }
+ rlen = mg_read(conn, buf, (size_t)rlen);
+ if (rlen <= 0) {
+ break;
+ }
+ wlen = mg_write(conn, buf, (size_t)rlen);
+ if (wlen != rlen) {
+ break;
+ }
+ nlen += wlen;
+ }
+
+ mg_printf(conn, "\n\n");
+ mg_printf(conn, "\n");
+
+ return true;
+ }
+
+ #define fopen_recursive fopen
+
+ bool
+ handlePut(CivetServer *server, struct mg_connection *conn)
+ {
+ /* Handler may access the request info using mg_get_request_info */
+ const struct mg_request_info *req_info = mg_get_request_info(conn);
+ long long rlen, wlen;
+ long long nlen = 0;
+ long long tlen = req_info->content_length;
+ FILE * f;
+ char buf[1024];
+ int fail = 0;
+
+#ifdef _WIN32
+ _snprintf(buf, sizeof(buf), "D:\\somewhere\\%s\\%s", req_info->remote_user, req_info->local_uri);
+ buf[sizeof(buf)-1] = 0;
+ if (strlen(buf)>255) {
+ /* Windows will not work with path > 260 (MAX_PATH), unless we use
+ * the unicode API. However, this is just an example code: A real
+ * code will probably never store anything to D:\\somewhere and
+ * must be adapted to the specific needs anyhow. */
+ fail = 1;
+ f = NULL;
+ } else {
+ f = fopen_recursive(buf, "wb");
+ }
+#else
+ snprintf(buf, sizeof(buf), "~/somewhere/%s/%s", req_info->remote_user, req_info->local_uri);
+ buf[sizeof(buf)-1] = 0;
+ if (strlen(buf)>1020) {
+ /* The string is too long and probably truncated. Make sure an
+ * UTF-8 string is never truncated between the UTF-8 code bytes.
+ * This example code must be adapted to the specific needs. */
+ fail = 1;
+ f = NULL;
+ } else {
+ f = fopen_recursive(buf, "w");
+ }
+#endif
+
+ if (!f) {
+ fail = 1;
+ } else {
+ while (nlen < tlen) {
+ rlen = tlen - nlen;
+ if (rlen > sizeof(buf)) {
+ rlen = sizeof(buf);
+ }
+ rlen = mg_read(conn, buf, (size_t)rlen);
+ if (rlen <= 0) {
+ fail = 1;
+ break;
+ }
+ wlen = fwrite(buf, 1, (size_t)rlen, f);
+ if (wlen != rlen) {
+ fail = 1;
+ break;
+ }
+ nlen += wlen;
+ }
+ fclose(f);
+ }
+
+ if (fail) {
+ mg_printf(conn,
+ "HTTP/1.1 409 Conflict\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n");
+ } else {
+ mg_printf(conn,
+ "HTTP/1.1 201 Created\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n");
+ }
+
+ return true;
+ }
+
+private:
+ LibraryManager &m_libraryManager;
+};
class WebServer : public CivetHandler
{
public:
- WebServer();
+ WebServer(LibraryManager &libraryManager);
~WebServer();
private:
-
+ LibraryManager &m_libraryManager;
+
CivetServer m_server;
+ LibraryManagerHandler m_libraryManagerHandler;
};
diff --git a/story-player-raylib/app.js b/story-player-raylib/app.js
new file mode 100644
index 0000000..206e2ab
--- /dev/null
+++ b/story-player-raylib/app.js
@@ -0,0 +1,39 @@
+import apiClient from './classes/api-client.js'
+import eventBus from './classes/event-bus.js';
+import storage from './classes/storage.js';
+
+
+import { render } from 'preact';
+import { html } from 'htm/preact';
+import TopMenu from './components/TopMenu.js'
+import ParametersDialog from './components/ParametersDialog.js';
+
+export function App() {
+
+ this.params = storage.getItem('server') || {
+ serverUrl: '127.0.0.1',
+ serverPort: 8081,
+ };
+
+ storage.setItem('server', this.params);
+
+ // try to connect to the server
+ apiClient.setBaseUrl(`http://${this.params.serverUrl}:${this.params.serverPort}/api/v1`);
+ apiClient.get('/library/list')
+ .then(data => {
+ console.log('Server is up and running', data);
+ eventBus.publish('server-state-changed', {connected: true});
+ })
+ .catch(error => {
+ console.error('Server is down', error);
+ eventBus.publish('server-state-changed', {connected: false});
+ });
+
+
+ return html`
+ <${TopMenu} />
+ <${ParametersDialog} />
+ `;
+}
+
+render(html`<${App} />`, document.getElementById('app'));
diff --git a/story-player-raylib/classes/api-client.js b/story-player-raylib/classes/api-client.js
new file mode 100644
index 0000000..c0fc121
--- /dev/null
+++ b/story-player-raylib/classes/api-client.js
@@ -0,0 +1,54 @@
+class ApiClient {
+ constructor(baseURL) {
+ this.baseURL = baseURL;
+ }
+
+ setBaseUrl(baseURL) {
+ this.baseURL = baseURL;
+ }
+
+ async request(endpoint, method = 'GET', data = null, headers = {}) {
+ const config = {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...headers
+ }
+ };
+
+ if (data) {
+ config.body = JSON.stringify(data);
+ }
+
+ try {
+ const response = await fetch(`${this.baseURL}${endpoint}`, config);
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.message || 'Something went wrong');
+ }
+ return await response.json();
+ } catch (error) {
+ console.error('API request error:', error);
+ throw error;
+ }
+ }
+
+ get(endpoint, headers = {}) {
+ return this.request(endpoint, 'GET', null, headers);
+ }
+
+ post(endpoint, data, headers = {}) {
+ return this.request(endpoint, 'POST', data, headers);
+ }
+
+ put(endpoint, data, headers = {}) {
+ return this.request(endpoint, 'PUT', data, headers);
+ }
+
+ delete(endpoint, headers = {}) {
+ return this.request(endpoint, 'DELETE', null, headers);
+ }
+}
+// Export de l'instance ApiClient pour l'importer facilement
+const apiClient = new ApiClient('127.0.0.1:8081');
+export default apiClient;
diff --git a/story-player-raylib/classes/event-bus.js b/story-player-raylib/classes/event-bus.js
new file mode 100644
index 0000000..ed3a5ec
--- /dev/null
+++ b/story-player-raylib/classes/event-bus.js
@@ -0,0 +1,27 @@
+class EventBus {
+ constructor() {
+ this.events = {};
+ this.id = Math.floor(Math.random() * 10000);
+ }
+
+ subscribe(event, listener) {
+ if (!this.events[event]) {
+ this.events[event] = [];
+ }
+ this.events[event].push(listener);
+ }
+
+ unsubscribe(event, listener) {
+ if (this.events[event]) {
+ this.events[event] = this.events[event].filter(l => l !== listener);
+ }
+ }
+
+ publish(event, data) {
+ if (this.events[event]) {
+ this.events[event].forEach(listener => listener(data));
+ }
+ }
+}
+
+export default new EventBus();
diff --git a/story-player-raylib/classes/storage.js b/story-player-raylib/classes/storage.js
new file mode 100644
index 0000000..d456e8b
--- /dev/null
+++ b/story-player-raylib/classes/storage.js
@@ -0,0 +1,48 @@
+class Storage {
+ constructor(prefix = '') {
+ this.prefix = prefix;
+ }
+
+ setItem(key, value) {
+ try {
+ const data = JSON.stringify(value);
+ localStorage.setItem(this.prefix + key, data);
+ } catch (error) {
+ console.error('Error saving to localStorage', error);
+ }
+ }
+
+ getItem(key) {
+ try {
+ const data = localStorage.getItem(this.prefix + key);
+ return data ? JSON.parse(data) : null;
+ } catch (error) {
+ console.error('Error reading from localStorage', error);
+ return null;
+ }
+ }
+
+ removeItem(key) {
+ try {
+ localStorage.removeItem(this.prefix + key);
+ } catch (error) {
+ console.error('Error removing from localStorage', error);
+ }
+ }
+
+ clear() {
+ try {
+ const keys = Object.keys(localStorage);
+ keys.forEach(key => {
+ if (key.startsWith(this.prefix)) {
+ localStorage.removeItem(key);
+ }
+ });
+ } catch (error) {
+ console.error('Error clearing localStorage', error);
+ }
+ }
+}
+
+// Exemple d'utilisation
+export default new Storage('ost_player_v1_');
\ No newline at end of file
diff --git a/story-player-raylib/components/ParametersDialog.js b/story-player-raylib/components/ParametersDialog.js
new file mode 100644
index 0000000..71ab5f2
--- /dev/null
+++ b/story-player-raylib/components/ParametersDialog.js
@@ -0,0 +1,82 @@
+import { render } from 'preact';
+import { html } from 'htm/preact';
+import { useState } from 'preact/hooks';
+import eventBus from '../classes/event-bus.js';
+
+function ParametersDialog() {
+
+ const [serverUrl, setServerUrl] = useState('127.0.0.1:8081');
+
+ // Function to show the modal
+ function showModal() {
+ modal.style.display = 'block';
+ }
+
+ // Function to hide the modal
+ function hideModal() {
+ modal.style.display = 'none';
+ }
+
+ // Event listener for the close button
+ function handleCloseClick() {
+ hideModal();
+ }
+
+ // Event listener for the submit button
+ function handleOkClick () {
+ const urlInput = document.getElementById('url-input').value;
+ console.log('URL entered:', urlInput);
+ hideModal();
+ }
+
+ eventBus.subscribe('show-modal', function(data) {
+ showModal();
+ });
+
+ return html`
+
+
+