mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
First web server route, use of preact for raylib html version
This commit is contained in:
parent
b2fdf5c03b
commit
432d72c80c
13 changed files with 933 additions and 189 deletions
|
|
@ -30,7 +30,7 @@ MainWindow::MainWindow()
|
||||||
, m_nodeEditorWindow(*this)
|
, m_nodeEditorWindow(*this)
|
||||||
, m_libraryWindow(*this, m_libraryManager)
|
, m_libraryWindow(*this, m_libraryManager)
|
||||||
, m_player(*this)
|
, m_player(*this)
|
||||||
|
, m_webServer(m_libraryManager)
|
||||||
{
|
{
|
||||||
// VM Initialize
|
// VM Initialize
|
||||||
m_chip32_ctx.stack_size = 512;
|
m_chip32_ctx.stack_size = 512;
|
||||||
|
|
|
||||||
|
|
@ -1,175 +1,68 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
#include "web_server.h"
|
#include "web_server.h"
|
||||||
|
|
||||||
#define DOCUMENT_ROOT "."
|
#define DOCUMENT_ROOT "."
|
||||||
#define PORT "8081"
|
#define PORT "8081"
|
||||||
|
|
||||||
class FooHandler : public CivetHandler
|
bool HandlerBase::Reply(struct mg_connection *conn, const nlohmann::json &json)
|
||||||
{
|
{
|
||||||
public:
|
std::stringstream ss;
|
||||||
bool
|
|
||||||
handleGet(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);
|
|
||||||
|
|
||||||
mg_printf(conn,
|
std::string data = json.dump();
|
||||||
"HTTP/1.1 200 OK\r\nContent-Type: "
|
|
||||||
"text/html\r\nConnection: close\r\n\r\n");
|
|
||||||
|
|
||||||
mg_printf(conn, "<html><body>\n");
|
/* Send HTTP message header (+1 for \n) */
|
||||||
mg_printf(conn, "<h2>This is the Foo GET handler!!!</h2>\n");
|
mg_send_http_ok(conn, "application/json; charset=utf-8", data.size() + 1);
|
||||||
mg_printf(conn,
|
|
||||||
"<p>The request was:<br><pre>%s %s HTTP/%s</pre></p>\n",
|
|
||||||
req_info->request_method,
|
|
||||||
req_info->request_uri,
|
|
||||||
req_info->http_version);
|
|
||||||
mg_printf(conn, "</body></html>\n");
|
|
||||||
|
|
||||||
return true;
|
/* Send HTTP message content */
|
||||||
}
|
mg_write(conn, data.c_str(), data.size());
|
||||||
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,
|
/* Add a newline. This is not required, but the result is more
|
||||||
"HTTP/1.1 200 OK\r\nContent-Type: "
|
* human-readable in a debugger. */
|
||||||
"text/html\r\nConnection: close\r\n\r\n");
|
mg_write(conn, "\n", 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
mg_printf(conn, "<html><body>\n");
|
bool LibraryManagerHandler::handleGet(CivetServer *server, struct mg_connection *conn)
|
||||||
mg_printf(conn, "<h2>This is the Foo POST handler!!!</h2>\n");
|
{
|
||||||
mg_printf(conn,
|
const struct mg_request_info *req_info = mg_get_request_info(conn);
|
||||||
"<p>The request was:<br><pre>%s %s HTTP/%s</pre></p>\n",
|
nlohmann::json json;
|
||||||
req_info->request_method,
|
|
||||||
req_info->request_uri,
|
|
||||||
req_info->http_version);
|
|
||||||
mg_printf(conn, "<p>Content Length: %li</p>\n", (long)tlen);
|
|
||||||
mg_printf(conn, "<pre>\n");
|
|
||||||
|
|
||||||
while (nlen < tlen) {
|
for (auto &s : m_libraryManager)
|
||||||
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</pre>\n");
|
|
||||||
mg_printf(conn, "</body></html>\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 */
|
nlohmann::json story = {
|
||||||
const struct mg_request_info *req_info = mg_get_request_info(conn);
|
{"title", s->GetName() },
|
||||||
long long rlen, wlen;
|
{"uuid", s->GetUuid() },
|
||||||
long long nlen = 0;
|
};
|
||||||
long long tlen = req_info->content_length;
|
json.push_back(story);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
};
|
return Reply(conn, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static const char *options[] = {
|
static const char *options[] = {
|
||||||
"document_root", DOCUMENT_ROOT,
|
"document_root", DOCUMENT_ROOT,
|
||||||
"listening_ports", PORT,
|
"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()
|
static const std::string gRestBase = "/api/v1";
|
||||||
: m_server(options)
|
|
||||||
|
WebServer::WebServer(LibraryManager &libraryManager)
|
||||||
|
: m_libraryManager(libraryManager)
|
||||||
|
, m_server(options)
|
||||||
|
, m_libraryManagerHandler(libraryManager)
|
||||||
{
|
{
|
||||||
mg_init_library(0);
|
mg_init_library(0);
|
||||||
|
|
||||||
FooHandler h_foo;
|
m_server.addHandler(gRestBase + "/library/list", m_libraryManagerHandler);
|
||||||
m_server.addHandler("**.foo", h_foo);
|
// printf("Browse files at http://localhost:%s/\n", PORT);
|
||||||
printf("Browse files at http://localhost:%s/\n", PORT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebServer::~WebServer()
|
WebServer::~WebServer()
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,169 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CivetServer.h"
|
#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, "<html><body>\n");
|
||||||
|
mg_printf(conn, "<h2>This is the Foo POST handler!!!</h2>\n");
|
||||||
|
mg_printf(conn,
|
||||||
|
"<p>The request was:<br><pre>%s %s HTTP/%s</pre></p>\n",
|
||||||
|
req_info->request_method,
|
||||||
|
req_info->request_uri,
|
||||||
|
req_info->http_version);
|
||||||
|
mg_printf(conn, "<p>Content Length: %li</p>\n", (long)tlen);
|
||||||
|
mg_printf(conn, "<pre>\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</pre>\n");
|
||||||
|
mg_printf(conn, "</body></html>\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
|
class WebServer : public CivetHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WebServer();
|
WebServer(LibraryManager &libraryManager);
|
||||||
~WebServer();
|
~WebServer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
LibraryManager &m_libraryManager;
|
||||||
|
|
||||||
CivetServer m_server;
|
CivetServer m_server;
|
||||||
|
LibraryManagerHandler m_libraryManagerHandler;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
39
story-player-raylib/app.js
Normal file
39
story-player-raylib/app.js
Normal file
|
|
@ -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'));
|
||||||
54
story-player-raylib/classes/api-client.js
Normal file
54
story-player-raylib/classes/api-client.js
Normal file
|
|
@ -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;
|
||||||
27
story-player-raylib/classes/event-bus.js
Normal file
27
story-player-raylib/classes/event-bus.js
Normal file
|
|
@ -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();
|
||||||
48
story-player-raylib/classes/storage.js
Normal file
48
story-player-raylib/classes/storage.js
Normal file
|
|
@ -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_');
|
||||||
82
story-player-raylib/components/ParametersDialog.js
Normal file
82
story-player-raylib/components/ParametersDialog.js
Normal file
|
|
@ -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`
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
/* Modal Styles */
|
||||||
|
.modal {
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover,
|
||||||
|
.close-button:focus {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="modal" class="modal">
|
||||||
|
<div class="block fixed">
|
||||||
|
<span class="close-button" onClick="${handleCloseClick}">⨯</span>
|
||||||
|
<label for="url-input">URL du serveur:</label>
|
||||||
|
<div class="wrapper block">
|
||||||
|
<input type="text" id="url-input" name="url-input" placeholder="127.0.0.1:8080" value="${serverUrl}" />
|
||||||
|
</div>
|
||||||
|
<button id="submit-button" class="block accent" onClick="${handleOkClick}">Ok</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParametersDialog;
|
||||||
65
story-player-raylib/components/TopMenu.js
Normal file
65
story-player-raylib/components/TopMenu.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { html } from 'htm/preact';
|
||||||
|
import eventBus from '../classes/event-bus.js';
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
|
||||||
|
function MessageComponent({ show, message }) {
|
||||||
|
return html`
|
||||||
|
<div>
|
||||||
|
${show
|
||||||
|
? html`<div class="themed">
|
||||||
|
<div class="block fixed accent">
|
||||||
|
${message}
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
: html`<p />`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function TopMenu() {
|
||||||
|
|
||||||
|
const [message, setMessage] = useState('Erreur ');
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
eventBus.publish('show-modal', { type: 'parameters' });
|
||||||
|
|
||||||
|
eventBus.subscribe('server-state-changed', function(data) {
|
||||||
|
if (data.connected) {
|
||||||
|
setMessage('Connecté au serveur');
|
||||||
|
} else {
|
||||||
|
setMessage('Serveur non trouvé');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.flexRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.themed {
|
||||||
|
--block-accent-color: #EA471A;
|
||||||
|
|
||||||
|
<!-- background: #abcdef; -->
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div class="flexRow">
|
||||||
|
<${MessageComponent} show=${error} message=${message} />
|
||||||
|
<div class="block accent" onClick="${handleClick}">
|
||||||
|
Paramètres
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
export default TopMenu;
|
||||||
|
|
@ -11,29 +11,44 @@
|
||||||
|
|
||||||
<title>OpenStoryTeller Web Player</title>
|
<title>OpenStoryTeller Web Player</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"preact": "https://esm.sh/preact@10.23.1",
|
||||||
|
"preact/hooks": "https://esm.sh/preact@10.23.1/hooks",
|
||||||
|
"htm/preact": "https://esm.sh/htm@3.1.1/preact?external=preact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="shortcut icon" href="https://www.raylib.com/favicon.ico">
|
<link rel="shortcut icon" href="https://www.raylib.com/favicon.ico">
|
||||||
<style>
|
<style>
|
||||||
/* body {
|
body {
|
||||||
margin: 0px;
|
line-height: 1;
|
||||||
text-align: center;
|
font-family: 'Inter', sans-serif;
|
||||||
} */
|
margin: 0;
|
||||||
canvas.emscripten { border: 0px none; background-color: black; display: inline; }
|
padding: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.emscripten { border: 0px none; background-color: black; display: inline; }
|
||||||
#canvas-container {
|
#canvas-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="min-width: min-content;">
|
||||||
<main class="container-fluid">
|
<main>
|
||||||
<vertical-menu></vertical-menu>
|
<div id="app"></div>
|
||||||
|
|
||||||
<div id="canvas-container">
|
<div id="canvas-container" class="block fixed">
|
||||||
<canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas>
|
<canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas>
|
||||||
</div>
|
</div>
|
||||||
<p id="output" />
|
<p id="output" />
|
||||||
|
|
@ -64,7 +79,6 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script async type="text/javascript" src="bin/story-player.js"></script>
|
<script async type="text/javascript" src="bin/story-player.js"></script>
|
||||||
<script src="vertical-menu.js" type="module"></script>
|
<script src="app.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
54
story-player-raylib/main.css
Normal file
54
story-player-raylib/main.css
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* Extra styles to make the demo look nicer.
|
||||||
|
* Feel free to look around! */
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin: 4px auto;
|
||||||
|
max-width: 800px;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 824px) {
|
||||||
|
main {
|
||||||
|
margin: 4px 12px;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
width: 90vw;
|
||||||
|
height: 56vw;
|
||||||
|
}
|
||||||
|
.preWrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2:first-child {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 28px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
342
story-player-raylib/style.css
Normal file
342
story-player-raylib/style.css
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
/* Basic CSS reset */
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
color: #222;
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
font-size: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
pre {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
pre {
|
||||||
|
font-family: 'Menlo', 'Monaco', monospace;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 2px 4px 1px 4px;
|
||||||
|
background: rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* util.css
|
||||||
|
* custom utility CSS library for projects by @thesephist */
|
||||||
|
|
||||||
|
/* margins */
|
||||||
|
|
||||||
|
.m0 {margin: 0}
|
||||||
|
|
||||||
|
.mt0, .my0 {margin-top: 0}
|
||||||
|
.mt1, .my1 {margin-top: .25rem}
|
||||||
|
.mt2, .my2 {margin-top: .5rem}
|
||||||
|
.mt3, .my3 {margin-top: .75rem}
|
||||||
|
.mt4, .my4 {margin-top: 1.5rem}
|
||||||
|
.mt5, .my5 {margin-top: 2.25rem}
|
||||||
|
.mt6, .my6 {margin-top: 3.25rem}
|
||||||
|
|
||||||
|
.mb0, .my0 {margin-bottom: 0}
|
||||||
|
.mb1, .my1 {margin-bottom: .25rem}
|
||||||
|
.mb2, .my2 {margin-bottom: .5rem}
|
||||||
|
.mb3, .my3 {margin-bottom: .75rem}
|
||||||
|
.mb4, .my4 {margin-bottom: 1.5rem}
|
||||||
|
.mb5, .my5 {margin-bottom: 2.25rem}
|
||||||
|
.mb6, .my6 {margin-bottom: 3.25rem}
|
||||||
|
|
||||||
|
.ml0, .mx0 {margin-left: 0}
|
||||||
|
.ml1, .mx1 {margin-left: .25rem}
|
||||||
|
.ml2, .mx2 {margin-left: .5rem}
|
||||||
|
.ml3, .mx3 {margin-left: .75rem}
|
||||||
|
.ml4, .mx4 {margin-left: 1.5rem}
|
||||||
|
.ml5, .mx5 {margin-left: 2.25rem}
|
||||||
|
.ml6, .mx6 {margin-left: 3.25rem}
|
||||||
|
|
||||||
|
.mr0, .mx0 {margin-right: 0}
|
||||||
|
.mr1, .mx1 {margin-right: .25rem}
|
||||||
|
.mr2, .mx2 {margin-right: .5rem}
|
||||||
|
.mr3, .mx3 {margin-right: .75rem}
|
||||||
|
.mr4, .mx4 {margin-right: 1.5rem}
|
||||||
|
.mr5, .mx5 {margin-right: 2.25rem}
|
||||||
|
.mr6, .mx6 {margin-right: 3.25rem}
|
||||||
|
|
||||||
|
/* paddings */
|
||||||
|
|
||||||
|
.p0 {padding: 0}
|
||||||
|
|
||||||
|
.pt0, .py0 {padding-top: 0}
|
||||||
|
.pt1, .py1 {padding-top: .25rem}
|
||||||
|
.pt2, .py2 {padding-top: .5rem}
|
||||||
|
.pt3, .py3 {padding-top: .75rem}
|
||||||
|
.pt4, .py4 {padding-top: 1rem}
|
||||||
|
|
||||||
|
.pb0, .py0 {padding-bottom: 0}
|
||||||
|
.pb1, .py1 {padding-bottom: .25rem}
|
||||||
|
.pb2, .py2 {padding-bottom: .5rem}
|
||||||
|
.pb3, .py3 {padding-bottom: .75rem}
|
||||||
|
.pb4, .py4 {padding-bottom: 1rem}
|
||||||
|
|
||||||
|
.pl0, .px0 {padding-left: 0}
|
||||||
|
.pl1, .px1 {padding-left: .25rem}
|
||||||
|
.pl2, .px2 {padding-left: .5rem}
|
||||||
|
.pl3, .px3 {padding-left: .75rem}
|
||||||
|
.pl4, .px4 {padding-left: 1rem}
|
||||||
|
|
||||||
|
.pr0, .px0 {padding-right: 0}
|
||||||
|
.pr1, .px1 {padding-right: .25rem}
|
||||||
|
.pr2, .px2 {padding-right: .5rem}
|
||||||
|
.pr3, .px3 {padding-right: .75rem}
|
||||||
|
.pr4, .px4 {padding-right: 1rem}
|
||||||
|
|
||||||
|
/* typography */
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {font-weight: 400}
|
||||||
|
.bold {font-weight: bold}
|
||||||
|
.clean {text-decoration: none}
|
||||||
|
.underline {text-decoration: underline}
|
||||||
|
.f0 {font-size: .75rem}
|
||||||
|
.f1 {font-size: 1rem}
|
||||||
|
.f2 {font-size: 1.25rem}
|
||||||
|
.f3 {font-size: 1.6rem}
|
||||||
|
.f4 {font-size: 2rem}
|
||||||
|
.f5 {font-size: 3rem}
|
||||||
|
.text-start {text-align: start}
|
||||||
|
.text-end {text-align: end}
|
||||||
|
.text-center {text-align: center}
|
||||||
|
|
||||||
|
/* flex & layout */
|
||||||
|
|
||||||
|
.flex {display: flex}
|
||||||
|
.flex-row {flex-direction: row}
|
||||||
|
.flex-column {flex-direction: column}
|
||||||
|
.flex-wrap {flex-wrap: wrap}
|
||||||
|
.align-center {align-items: center}
|
||||||
|
.align-start {align-items: flex-start}
|
||||||
|
.align-end {align-items: flex-end}
|
||||||
|
.justify-center {justify-content: center}
|
||||||
|
.justify-start {justify-content: flex-start}
|
||||||
|
.justify-end {justify-content: flex-end}
|
||||||
|
.fill, .fill-width {width: 100%}
|
||||||
|
.fill, .fill-height {height: 100%}
|
||||||
|
|
||||||
|
/* page widths */
|
||||||
|
|
||||||
|
.page-xs {max-width: 480px}
|
||||||
|
.page-s {max-width: 600px}
|
||||||
|
.page-m {max-width: 800px}
|
||||||
|
.page-l {max-width: 1200px}
|
||||||
|
.page-xl {max-width: 1600px}
|
||||||
|
|
||||||
|
/* shadows */
|
||||||
|
|
||||||
|
.shadow1 {box-shadow: 0 2px 3px rgba(0, 0, 0, .36)}
|
||||||
|
.shadow2 {box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .34)}
|
||||||
|
.shadow3 {box-shadow: 0 4px 7px -1px rgba(0, 0, 0, .32)}
|
||||||
|
.shadow4 {box-shadow: 0 5px 9px -2px rgba(0, 0, 0, .3)}
|
||||||
|
.shadow5 {box-shadow: 0 7px 12px -3px rgba(0, 0, 0, .28)}
|
||||||
|
|
||||||
|
/* list */
|
||||||
|
|
||||||
|
ul.list-reset,
|
||||||
|
ol.list-reset {
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* other utility */
|
||||||
|
|
||||||
|
.borderless {border: 0 solid transparent}
|
||||||
|
.rounded {border-radius: 4px}
|
||||||
|
.block {display: block}
|
||||||
|
.inline-block {display: inline-block}
|
||||||
|
.inline {display: inline}
|
||||||
|
.hidden {display: none}
|
||||||
|
.pointer {cursor: pointer}
|
||||||
|
.auto-center {margin-left: auto; margin-right: auto}
|
||||||
|
|
||||||
|
|
||||||
|
/* DEFAULT VARIABLES */
|
||||||
|
body {
|
||||||
|
--block-text-color: #222;
|
||||||
|
--block-background-color: #fff;
|
||||||
|
--block-accent-color: #00ae86;
|
||||||
|
--block-shadow-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BASIC BLOCK STYLES */
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
color: var(--block-text-color);
|
||||||
|
border: 3px solid var(--block-text-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: var(--block-background-color);
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
margin: 8px 6px 10px 6px;
|
||||||
|
z-index: 1;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.wrapper,
|
||||||
|
.block.wrapper.inline {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.wrapper > * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* INTERACTIVE BLOCK STYLES */
|
||||||
|
.block::before {
|
||||||
|
content: "";
|
||||||
|
background: var(--block-background-color);
|
||||||
|
border: 3px solid var(--block-text-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: -3px;
|
||||||
|
left: -3px;
|
||||||
|
height: calc(100% + 6px);
|
||||||
|
width: calc(100% + 6px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block:hover,
|
||||||
|
.block:focus {
|
||||||
|
transform: translate(2px, 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--block-shadow-color);
|
||||||
|
border: 3px solid var(--block-text-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
height: calc(100% + 6px);
|
||||||
|
width: calc(100% + 6px);
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
right: 0;
|
||||||
|
z-index: -2;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block:hover::after,
|
||||||
|
.block:focus::after {
|
||||||
|
transform: translate(-2px, -3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block:active {
|
||||||
|
color: var(--block-text-color);
|
||||||
|
transform: translate(3px, 3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block:active::after {
|
||||||
|
transform: translate(-4px, -4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.fixed {
|
||||||
|
cursor: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXED STYLES */
|
||||||
|
.block.fixed:hover,
|
||||||
|
.block.fixed:hover::before,
|
||||||
|
.block.fixed:hover::after,
|
||||||
|
.block.fixed:active,
|
||||||
|
.block.fixed:active::before,
|
||||||
|
.block.fixed:active::after,
|
||||||
|
.block.fixed:focus,
|
||||||
|
.block.fixed:focus::before,
|
||||||
|
.block.fixed:focus::after {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ACCENT STYLES */
|
||||||
|
.block.accent {
|
||||||
|
color: var(--block-background-color);
|
||||||
|
background: var(--block-accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.accent::before {
|
||||||
|
background: var(--block-accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* INLINE STYLES */
|
||||||
|
.block.inline {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 0 6px;
|
||||||
|
margin: 3px 2px 1px 4px;
|
||||||
|
}
|
||||||
|
.block.inline::after {
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
}
|
||||||
|
.block.inline:hover,
|
||||||
|
.block.inline:focus {
|
||||||
|
transform: translate(1px, 1px);
|
||||||
|
}
|
||||||
|
.block.inline:hover::after,
|
||||||
|
.block.inline:focus::after {
|
||||||
|
transform: translate(-1px, -1px);
|
||||||
|
}
|
||||||
|
.block.inline:active {
|
||||||
|
transform: translate(2px, 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ROUND STYLES */
|
||||||
|
.block.round,
|
||||||
|
.block.round::before,
|
||||||
|
.block.round::after {
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block.round::after {
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
class VerticalMenu extends HTMLElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// Create a wrapper nav element using Bulma
|
|
||||||
this.innerHTML = `
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li><strong>OpenStoryTeller</strong></li>
|
|
||||||
</ul>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#">Paramètres</a></li>
|
|
||||||
<li><a href="#">À propos</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the new element
|
|
||||||
customElements.define('vertical-menu', VerticalMenu);
|
|
||||||
Loading…
Reference in a new issue