diff options
| author | frosty <gabriel@bwaaa.monster> | 2026-03-30 10:37:46 +0300 |
|---|---|---|
| committer | frosty <gabriel@bwaaa.monster> | 2026-03-30 10:37:46 +0300 |
| commit | c3ed9017385342944badec46de263560c6ab07c8 (patch) | |
| tree | d7f7e86e26ebffd9f277fc340b74a1681eb707d7 | |
| parent | 9e6e76306471b3cc139ae68c0363ec95616d1b23 (diff) | |
| download | omnisearch-c3ed9017385342944badec46de263560c6ab07c8.tar.gz | |
feat: begin adding settings menu, move theme to settings
| -rw-r--r-- | src/Main.c | 4 | ||||
| -rw-r--r-- | src/Routes/Home.c | 5 | ||||
| -rw-r--r-- | src/Routes/Images.c | 8 | ||||
| -rw-r--r-- | src/Routes/Search.c | 8 | ||||
| -rw-r--r-- | src/Routes/Settings.c | 30 | ||||
| -rw-r--r-- | src/Routes/Settings.h | 8 | ||||
| -rw-r--r-- | src/Routes/SettingsSave.c | 28 | ||||
| -rw-r--r-- | src/Routes/SettingsSave.h | 8 | ||||
| -rw-r--r-- | src/Utility/Utility.c | 14 | ||||
| -rw-r--r-- | src/Utility/Utility.h | 1 | ||||
| -rw-r--r-- | static/main.css | 232 | ||||
| -rw-r--r-- | static/settings.svg | 4 | ||||
| -rw-r--r-- | static/theme-dark.css | 10 | ||||
| -rw-r--r-- | static/theme-light.css | 10 | ||||
| -rw-r--r-- | templates/home.html | 10 | ||||
| -rw-r--r-- | templates/images.html | 8 | ||||
| -rw-r--r-- | templates/results.html | 8 | ||||
| -rw-r--r-- | templates/settings.html | 67 |
18 files changed, 444 insertions, 19 deletions
@@ -13,6 +13,8 @@ #include "Routes/ImageProxy.h" #include "Routes/Images.h" #include "Routes/Search.h" +#include "Routes/Settings.h" +#include "Routes/SettingsSave.h" #include "Scraping/Scraping.h" Config global_config; @@ -97,6 +99,8 @@ int main() { set_handler("/search", results_handler); set_handler("/images", images_handler); set_handler("/proxy", image_proxy_handler); + set_handler("/settings", settings_handler); + set_handler("/save_settings", settings_save_handler); fprintf(stderr, "[INFO] Starting Omnisearch on %s:%d\n", cfg.host, cfg.port); diff --git a/src/Routes/Home.c b/src/Routes/Home.c index 4526a9d..c857663 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -1,14 +1,19 @@ #include "Home.h" +#include "../Utility/Utility.h" #include <stdlib.h> int home_handler(UrlParams *params) { (void)params; + char *theme = get_theme(""); + TemplateContext ctx = new_context(); + context_set(&ctx, "theme", theme); char *rendered_html = render_template("home.html", &ctx); send_response(rendered_html); free(rendered_html); free_context(&ctx); + free(theme); return 0; } diff --git a/src/Routes/Images.c b/src/Routes/Images.c index 0f8ff1e..03eb280 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -1,6 +1,7 @@ #include "Images.h" #include "../Scraping/ImageScraping.h" #include "../Utility/Unescape.h" +#include "../Utility/Utility.h" #include "Config.h" int images_handler(UrlParams *params) { @@ -29,6 +30,11 @@ int images_handler(UrlParams *params) { snprintf(two_prev_str, sizeof(two_prev_str), "%d", page > 2 ? page - 2 : 0); snprintf(two_next_str, sizeof(two_next_str), "%d", page + 2); context_set(&ctx, "query", raw_query); + + char *theme = get_theme(""); + context_set(&ctx, "theme", theme); + free(theme); + context_set(&ctx, "page", page_str); context_set(&ctx, "prev_page", prev_str); context_set(&ctx, "next_page", next_str); @@ -39,7 +45,7 @@ int images_handler(UrlParams *params) { context_set(&ctx, "query", display_query); if (!raw_query || strlen(raw_query) == 0) { - send_response("<h1>No query provided</h1>"); + send_redirect("/"); if (display_query) free(display_query); free_context(&ctx); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 5f89752..81e43d4 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -7,6 +7,7 @@ #include "../Scraping/Scraping.h" #include "../Utility/Display.h" #include "../Utility/Unescape.h" +#include "../Utility/Utility.h" #include "Config.h" #include <ctype.h> #include <pthread.h> @@ -401,12 +402,17 @@ int results_handler(UrlParams *params) { } context_set(&ctx, "query", raw_query); + + char *theme = get_theme(""); + context_set(&ctx, "theme", theme); + free(theme); + char page_str[16]; snprintf(page_str, sizeof(page_str), "%d", page); context_set(&ctx, "page", page_str); if (!raw_query || strlen(raw_query) == 0) { - send_response("<h1>No query provided</h1>"); + send_redirect("/"); free_context(&ctx); return -1; } diff --git a/src/Routes/Settings.c b/src/Routes/Settings.c new file mode 100644 index 0000000..05edc56 --- /dev/null +++ b/src/Routes/Settings.c @@ -0,0 +1,30 @@ +#include "Settings.h" +#include "../Utility/Utility.h" +#include <stdlib.h> +#include <string.h> + +int settings_handler(UrlParams *params) { + const char *query = ""; + if (params) { + for (int i = 0; i < params->count; i++) { + if (strcmp(params->params[i].key, "q") == 0) { + query = params->params[i].value; + break; + } + } + } + + char *theme = get_theme("system"); + + TemplateContext ctx = new_context(); + context_set(&ctx, "query", query); + context_set(&ctx, "theme", theme); + char *rendered_html = render_template("settings.html", &ctx); + send_response(rendered_html); + + free(rendered_html); + free(theme); + free_context(&ctx); + + return 0; +} diff --git a/src/Routes/Settings.h b/src/Routes/Settings.h new file mode 100644 index 0000000..269c085 --- /dev/null +++ b/src/Routes/Settings.h @@ -0,0 +1,8 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include <beaker.h> + +int settings_handler(UrlParams *params); + +#endif diff --git a/src/Routes/SettingsSave.c b/src/Routes/SettingsSave.c new file mode 100644 index 0000000..d286507 --- /dev/null +++ b/src/Routes/SettingsSave.c @@ -0,0 +1,28 @@ +#include "SettingsSave.h" +#include <stdlib.h> +#include <string.h> + +int settings_save_handler(UrlParams *params) { + const char *theme = ""; + const char *query = ""; + + if (params) { + for (int i = 0; i < params->count; i++) { + if (strcmp(params->params[i].key, "theme") == 0) { + theme = params->params[i].value; + } else if (strcmp(params->params[i].key, "q") == 0) { + query = params->params[i].value; + } + } + } + + if (strlen(theme) > 0) { + set_cookie("theme", theme, "Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false); + } + + char redirect_url[512]; + snprintf(redirect_url, sizeof(redirect_url), "/settings?q=%s", query); + send_redirect(redirect_url); + + return 0; +} diff --git a/src/Routes/SettingsSave.h b/src/Routes/SettingsSave.h new file mode 100644 index 0000000..e10cc2f --- /dev/null +++ b/src/Routes/SettingsSave.h @@ -0,0 +1,8 @@ +#ifndef SETTINGS_SAVE_H +#define SETTINGS_SAVE_H + +#include <beaker.h> + +int settings_save_handler(UrlParams *params); + +#endif diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index 3be4ef4..b4ad91d 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -1,4 +1,7 @@ #include "Utility.h" +#include <beaker.h> +#include <stdlib.h> +#include <string.h> int hex_to_int(char c) { if (c >= '0' && c <= '9') @@ -9,3 +12,14 @@ int hex_to_int(char c) { return c - 'A' + 10; return -1; } + +char *get_theme(const char *default_theme) { + char *cookie = get_cookie("theme"); + if (cookie && + (strcmp(cookie, "light") == 0 || + strcmp(cookie, "dark") == 0)) { + return cookie; + } + free(cookie); + return strdup(default_theme); +} diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index 3b0181c..e67282f 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -2,5 +2,6 @@ #define UTILITY_H int hex_to_int(char c); +char *get_theme(const char *default_theme); #endif diff --git a/static/main.css b/static/main.css index 6f899cf..5f7c0f9 100644 --- a/static/main.css +++ b/static/main.css @@ -25,12 +25,19 @@ box-sizing: border-box; } +html { + height:100%; +} + body { background-color:var(--bg-main); + background-image:radial-gradient(circle at top right, var(--bg-card) 0%, var(--bg-main) 100%); + background-attachment:fixed; color:var(--text-primary); font-family:system-ui,-apple-system,sans-serif; margin:0; padding:0; + min-height:100%; -webkit-tap-highlight-color: transparent; } @@ -44,13 +51,12 @@ img[src=""] { align-items: center; min-height: 100vh; padding: 20px; - background: radial-gradient(circle at top right, var(--bg-card) 0%, var(--bg-main) 100%); } .view-home .container { width: 100%; max-width: 580px; - margin: 0 auto; + margin: 0 auto; text-align: center; display: flex; flex-direction: column; @@ -100,11 +106,56 @@ img[src=""] { background:var(--bg-card); color:var(--text-primary); border-color:var(--border); + text-decoration:none; + display:inline-flex; + align-items:center; + padding:10px 24px; + border-radius:8px; + font-weight:600; + font-size:0.9rem; + cursor:pointer; + transition:all 0.2s; + border:1px solid var(--border); } .view-home .btn-secondary:hover { background:var(--border); border-color:var(--text-secondary); } +.home-settings-btn { + position:fixed; + top:27px; + right:60px; + width:24px; + height:24px; + background-color:var(--text-primary); + -webkit-mask-image:url('/static/settings.svg'); + mask-image:url('/static/settings.svg'); + mask-size:contain; + mask-repeat:no-repeat; + mask-position:center; + text-decoration:none; +} +.nav-settings-icon { + width:24px; + height:24px; + flex-shrink:0; + margin-left:auto; + margin-top:3px; + background-color:var(--text-secondary); + -webkit-mask-image:url('/static/settings.svg'); + mask-image:url('/static/settings.svg'); + mask-size:100% 100%; + transition:background-color 0.2s; + text-decoration:none; +} +.nav-settings-icon:hover, +.nav-settings-icon.active { + background-color:var(--text-primary); +} +.nav-settings-link { + display:none; + margin-left:auto; +} header { display:flex; align-items:center; @@ -112,6 +163,7 @@ header { padding:15px 60px; border-bottom:1px solid var(--border); background:var(--bg-main); + width:100%; } .search-form { flex-grow:1; @@ -145,11 +197,11 @@ h1 span { padding:0 60px; border-bottom:1px solid var(--border); background:var(--bg-main); + width:100%; } .nav-container { display:flex; gap:30px; - max-width:1200px; } .nav-tabs a { padding:14px 0; @@ -168,6 +220,9 @@ h1 span { color:var(--accent); border-bottom-color:var(--accent); } +.nav-right { + margin-left:auto; +} .image-results-container { padding:30px 60px; } @@ -473,19 +528,23 @@ h1 span { @media (max-width:1200px) { - body { - padding-left: 16px; - padding-right: 16px; - } .content-layout { grid-template-columns:1fr; padding:20px 30px; gap:20px; } + header { + gap:20px; + } .results-container,.infobox-sidebar { grid-column:1; max-width:100%; } + .settings-layout { + padding:20px 30px; + display:flex; + justify-content:center; + } .infobox-sidebar { order:-1; } @@ -498,9 +557,11 @@ h1 span { } @media (max-width:768px) { - body { - padding-left: 16px; - padding-right: 16px; + .nav-settings-icon { + display:none; + } + .nav-settings-link { + display:inline; } header { flex-direction:column; @@ -532,7 +593,7 @@ h1 span { font-size:0.95rem; } .content-layout { - padding:16px; + padding:16px 16px 16px 40px; gap:16px; } .result { @@ -591,7 +652,6 @@ h1 span { display: flex; justify-content: center; align-items: center; - transform: translateY(-5vh); padding:20px 16px; min-height: 100vh; } @@ -621,6 +681,12 @@ h1 span { } @media (max-width:600px) { + .content-layout { + padding:16px 16px 16px 28px; + } + .settings-layout { + padding:12px 0; + } header { padding:12px 12px; } @@ -657,3 +723,145 @@ h1 span { font-size:0.75rem; } } + +.settings-layout { + padding:30px 60px 30px 260px; +} + +.settings-container { + max-width:700px; +} + +.settings-title { + font-size:1.8rem; + font-weight:700; + margin:0 0 32px 0; + letter-spacing:-0.5px; +} + +.settings-section { + background:var(--bg-card); + border:1px solid var(--border); + border-radius:12px; + padding:24px; + margin-bottom:32px; +} + +.settings-section-title { + font-size:1.1rem; + font-weight:700; + margin:0 0 4px 0; +} + +.settings-section-desc { + color:var(--text-secondary); + font-size:0.9rem; + margin:0 0 20px 0; + line-height:1.4; +} + +.settings-field { + display:flex; + align-items:center; + justify-content:space-between; + padding:10px 0; +} + +.settings-field + .settings-field { + border-top:1px solid var(--border); +} + +.settings-label { + font-size:0.95rem; + color:var(--text-primary); +} + +.settings-select { + padding:8px 12px; + border-radius:8px; + border:1px solid var(--border); + background:var(--bg-main); + color:var(--text-primary); + font-size:0.9rem; + outline:none; + cursor:pointer; + transition:border-color 0.2s; +} + +.settings-select:focus { + border-color:var(--accent); +} + +.settings-actions { + display:flex; + gap:12px; + margin-top:8px; + padding-bottom:40px; + justify-content:flex-start; +} + +.settings-actions .btn-primary { + background:var(--accent); + color:var(--bg-main); + border:1px solid transparent; + padding:10px 24px; + border-radius:8px; + font-weight:600; + font-size:0.9rem; + cursor:pointer; + transition:all 0.2s; + touch-action:manipulation; +} + +.settings-actions .btn-primary:hover { + filter:brightness(1.1); + transform:translateY(-1px); +} + +.settings-actions .btn-secondary { + background:var(--bg-card); + color:var(--text-primary); + border:1px solid var(--border); + padding:10px 24px; + border-radius:8px; + font-weight:600; + font-size:0.9rem; + cursor:pointer; + transition:all 0.2s; + touch-action:manipulation; +} + +.settings-actions .btn-secondary:hover { + background:var(--border); + border-color:var(--text-secondary); +} + +@media (max-width:768px) { + .settings-layout { + padding:12px; + display:block; + } + .settings-container { + max-width:100%; + } + .settings-title { + font-size:1.4rem; + margin-bottom:24px; + } + .settings-section { + padding:16px; + } + .settings-field { + flex-direction:column; + align-items:stretch; + gap:8px; + } + .settings-actions { + flex-direction:column; + } + .settings-actions .btn-primary, + .settings-actions .btn-secondary { + width:100%; + text-align:center; + } +} diff --git a/static/settings.svg b/static/settings.svg new file mode 100644 index 0000000..4aebf8f --- /dev/null +++ b/static/settings.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <circle cx="12" cy="12" r="3"/> + <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/> +</svg> diff --git a/static/theme-dark.css b/static/theme-dark.css new file mode 100644 index 0000000..246aabe --- /dev/null +++ b/static/theme-dark.css @@ -0,0 +1,10 @@ +:root { + --bg-main: #121212; + --bg-card: #1e1e1e; + --border: #333333; + --text-primary: #ffffff; + --text-secondary: #a0a0a0; + --text-muted: #d1d1d1; + --accent: #e2e2e2; + --accent-glow: rgba(255,255,255,0.1); +} diff --git a/static/theme-light.css b/static/theme-light.css new file mode 100644 index 0000000..1c69377 --- /dev/null +++ b/static/theme-light.css @@ -0,0 +1,10 @@ +:root { + --bg-main: #ffffff; + --bg-card: #f8f9fa; + --border: #e0e0e0; + --text-primary: #1a1a1a; + --text-secondary: #5f6368; + --text-muted: #757575; + --accent: #202124; + --accent-glow: rgba(0,0,0,0.05); +} diff --git a/templates/home.html b/templates/home.html index 90fc904..ae00824 100644 --- a/templates/home.html +++ b/templates/home.html @@ -8,7 +8,9 @@ OmniSearch </title> <link rel="stylesheet" href="static/main.css"> - <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + {{if theme == "light"}}<link rel="stylesheet" href="static/theme-light.css">{{endif}} + {{if theme == "dark"}}<link rel="stylesheet" href="static/theme-dark.css">{{endif}} + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> <link rel="search" type="application/opensearchdescription+xml" title="OmniSearch" href="/opensearch.xml"> @@ -30,11 +32,13 @@ Search </button> <button type="submit" name="btnI" value="1" class="btn-secondary"> - Surprise me - </div> + Surprise me + </button> + </div> </form> </div> </div> + <a href="/settings" class="home-settings-btn" title="Settings"></a> </body> </html> diff --git a/templates/images.html b/templates/images.html index f04867b..92ab7b5 100644 --- a/templates/images.html +++ b/templates/images.html @@ -7,8 +7,10 @@ <title> OmniSearch Images - {{query}} </title> - <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> <link rel="stylesheet" href="static/main.css"> + {{if theme == "light"}}<link rel="stylesheet" href="static/theme-light.css">{{endif}} + {{if theme == "dark"}}<link rel="stylesheet" href="static/theme-dark.css">{{endif}} </head> <body class="images-view"> @@ -20,6 +22,7 @@ <input name="q" autocomplete="off"="text" class="search-box" placeholder="Search for images..." value="{{query}}"> </form> + <a href="/settings?q={{query}}" class="nav-settings-icon" title="Settings"></a> </header> <nav class="nav-tabs"> <div class="nav-container"> @@ -29,6 +32,9 @@ <a href="/images?q={{query}}" class="active"> Images </a> + <a href="/settings?q={{query}}" class="nav-settings-link"> + Settings + </a> </div> </nav> <main class="image-results-container"> diff --git a/templates/results.html b/templates/results.html index 57c2265..4128245 100644 --- a/templates/results.html +++ b/templates/results.html @@ -8,7 +8,9 @@ OmniSearch - {{query}} </title> <link rel="stylesheet" href="static/main.css"> - <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + {{if theme == "light"}}<link rel="stylesheet" href="static/theme-light.css">{{endif}} + {{if theme == "dark"}}<link rel="stylesheet" href="static/theme-dark.css">{{endif}} + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> <link rel="search" type="application/opensearchdescription+xml" title="OmniSearch" href="/opensearch.xml"> @@ -24,6 +26,7 @@ <input name="q" type="text" class="search-box" autocomplete="off" placeholder="Search the web..." value="{{query}}"> </form> + <a href="/settings?q={{query}}" class="nav-settings-icon" title="Settings"></a> </header> <nav class="nav-tabs"> <div class="nav-container"> @@ -33,6 +36,9 @@ <a href="/images?q={{query}}"> Images </a> + <a href="/settings?q={{query}}" class="nav-settings-link"> + Settings + </a> </div> </nav> <div class="content-layout"> diff --git a/templates/settings.html b/templates/settings.html new file mode 100644 index 0000000..780e438 --- /dev/null +++ b/templates/settings.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html lang="en"> + + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0"> + <title> + OmniSearch - Settings + </title> + <link rel="stylesheet" href="static/main.css"> + {{if theme == "light"}}<link rel="stylesheet" href="static/theme-light.css">{{endif}} + {{if theme == "dark"}}<link rel="stylesheet" href="static/theme-dark.css">{{endif}} + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + <link rel="search" + type="application/opensearchdescription+xml" + title="OmniSearch" href="/opensearch.xml"> + </head> + + <body class="settings-view"> + <header> + <h1> + Omni<span>Search</span> + </h1> + <form action="/search" method="GET" class="search-form"> + <input name="q" type="text" class="search-box" autocomplete="off" placeholder="Search the web..." + value="{{query}}"> + </form> + <a href="/settings?q={{query}}" class="nav-settings-icon active" title="Settings"></a> + </header> + <nav class="nav-tabs"> + <div class="nav-container"> + <a href="/search?q={{query}}"> + All + </a> + <a href="/images?q={{query}}"> + Images + </a> + <a href="/settings" class="active nav-settings-link"> + Settings + </a> + </div> + </nav> + <div class="settings-layout"> + <main class="settings-container"> + <form action="/save_settings" method="GET"> + <input type="hidden" name="q" value="{{query}}"> + <section class="settings-section"> + <h3 class="settings-section-title">Theme</h3> + <p class="settings-section-desc">Choose your preferred colour scheme.</p> + <div class="settings-field"> + <label class="settings-label" for="theme">Appearance</label> + <select id="theme" name="theme" class="settings-select"> + <option value="system" {{if theme == "system"}}selected{{endif}}>System</option> + <option value="light" {{if theme == "light"}}selected{{endif}}>Light</option> + <option value="dark" {{if theme == "dark"}}selected{{endif}}>Dark</option> + </select> + </div> + </section> + <div class="settings-actions"> + <button type="submit" class="btn-primary">Save Settings</button> + </div> + </form> + </main> + </div> + </body> + +</html> |
