too lazy to think of a message, so enjoy this pointless text. Good luck future me...
All checks were successful
Test action / Print hello world (push) Successful in 8m32s

This commit is contained in:
Chris Kruining 2025-10-08 10:49:59 +00:00
parent e7b0307690
commit 28fdba8b00
15 changed files with 3868 additions and 2785 deletions

3322
bun.lock

File diff suppressed because it is too large Load diff

1466
bun.nix

File diff suppressed because it is too large Load diff

View file

@ -1,39 +1,39 @@
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
flake-parts = { flake-parts = {
url = "github:hercules-ci/flake-parts"; url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs"; inputs.nixpkgs-lib.follows = "nixpkgs";
}; };
bun2nix = { bun2nix = {
url = "github:baileyluTCD/bun2nix"; url = "github:baileyluTCD/bun2nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
outputs = inputs@{ flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } { outputs = inputs@{ flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ systems = [
"x86_64-linux" "x86_64-linux"
"aarch64-linux" "aarch64-linux"
"aarch64-darwin" "aarch64-darwin"
"x86_64-darwin" "x86_64-darwin"
]; ];
imports = [ imports = [
./nix/devShells/flake-module.nix ./nix/devShells/flake-module.nix
./nix/packages/flake-module.nix ./nix/packages/flake-module.nix
./nix/modules/flake-module.nix ./nix/modules/flake-module.nix
]; ];
perSystem = { lib, self', ... }: { perSystem = { lib, self', ... }: {
checks = checks =
let let
packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages; packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages;
devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells; devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells;
in in
packages // devShells; packages // devShells;
}; };
}; };
} }

View file

@ -1,6 +1,6 @@
push: push:
git add . git add .
git commit -m 'too lazy to think of a message, so enjoy this pointless text. Good luck future me...' git commit -m 'too lazy to think of a message, so enjoy this pointless text. Good luck future me...'
git push git push

View file

@ -1,11 +1,11 @@
{ inputs, ... }: { inputs, ... }:
{ {
perSystem = { pkgs, system, ... }: { perSystem = { pkgs, system, ... }: {
devShells.default = pkgs.mkShellNoCC { devShells.default = pkgs.mkShellNoCC {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
bun bun
inputs.bun2nix.packages.${system}.default inputs.bun2nix.packages.${system}.default
]; ];
}; };
}; };
} }

View file

@ -1,142 +1,144 @@
{ self, ... }: { self, ... }:
{ {
flake.nixosModules.default = flake.nixosModules.default =
nixos@{ config, pkgs, lib, utils, ... }: nixos@{ config, pkgs, lib, utils, ... }:
let let
inherit (lib) mkEnableOption mkPackageOption mkOption mkIf types; inherit (lib) mkEnableOption mkPackageOption mkOption mkIf types;
format = pkgs.formats.json {}; format = pkgs.formats.json {};
cfg = config.services.amarth-customer-portal; cfg = config.services.amarth-customer-portal;
in in
{ {
options.services.amarth-customer-portal = { options.services.amarth-customer-portal = {
enable = mkEnableOption "Enable Amarth cloud's customer portal."; enable = mkEnableOption "Enable Amarth cloud's customer portal.";
package = mkPackageOption self.packages.${pkgs.hostPlatform.system} "amarth-customer-portal" {}; package = mkPackageOption self.packages.${pkgs.hostPlatform.system} "amarth-customer-portal" {};
openFirewall = mkOption { openFirewall = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
example = "true"; example = "true";
description = '' description = ''
Open the configured port in the firewall. Open the configured port in the firewall.
''; '';
}; };
user = lib.mkOption { user = lib.mkOption {
type = types.str; type = types.str;
default = "amarth"; default = "amarth";
description = '' description = ''
User account under which FileBrowser runs. User account under which FileBrowser runs.
''; '';
}; };
group = lib.mkOption { group = lib.mkOption {
type = types.str; type = types.str;
default = "amarth"; default = "amarth";
description = '' description = ''
Group under which FileBrowser runs. Group under which FileBrowser runs.
''; '';
}; };
settings = mkOption { settings = mkOption {
default = {}; default = {};
description = '' description = ''
''; '';
type = types.submodule { type = types.submodule {
freeformType = format.type; freeformType = format.type;
options = { options = {
address = mkOption { address = mkOption {
default = "localhost"; default = "localhost";
description = '' description = ''
The address to listen on. The address to listen on.
''; '';
type = types.str; type = types.str;
}; };
port = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 8080; default = 8080;
description = '' description = ''
Which port to run the portal on. Which port to run the portal on.
''; '';
}; };
dataDir = lib.mkOption { dataDir = lib.mkOption {
default = "/var/lib/amarth/customer-portal"; default = "/var/lib/amarth/customer-portal";
description = '' description = ''
Directory where the portal persists files. Directory where the portal persists files.
''; '';
type = types.path; type = types.path;
}; };
}; };
}; };
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
systemd = { systemd = {
services.amarthCustomerPortal = { services.amarthCustomerPortal = {
after = [ "network.target" ]; after = [ "network.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
description = "Amarth cloud's customer portal"; description = "Amarth cloud's customer portal";
serviceConfig = { serviceConfig = {
ExecStart = utils.escapeSystemdExecArgs [ ExecStart = utils.escapeSystemdExecArgs [
(lib.getExe cfg.package) (lib.getExe cfg.bun)
"--config" "run"
(format.generate "config.json" cfg.settings) "start"
]; "--config"
(format.generate "config.json" cfg.settings)
StateDirectory = "amarth-customer-portal"; ];
CacheDirectory = "amarth-customer-portal";
WorkingDirectory = cfg.settings.dataDir; StateDirectory = "amarth-customer-portal";
CacheDirectory = "amarth-customer-portal";
User = cfg.user; WorkingDirectory = cfg.settings.dataDir;
Group = cfg.group;
UMask = "0077"; User = cfg.user;
Group = cfg.group;
NoNewPrivileges = true; UMask = "0077";
PrivateDevices = true;
ProtectKernelTunables = true; NoNewPrivileges = true;
ProtectKernelModules = true; PrivateDevices = true;
ProtectControlGroups = true; ProtectKernelTunables = true;
MemoryDenyWriteExecute = true; ProtectKernelModules = true;
LockPersonality = true; ProtectControlGroups = true;
RestrictAddressFamilies = [ MemoryDenyWriteExecute = true;
"AF_UNIX" LockPersonality = true;
"AF_INET" RestrictAddressFamilies = [
"AF_INET6" "AF_UNIX"
]; "AF_INET"
DevicePolicy = "closed"; "AF_INET6"
RestrictNamespaces = true; ];
RestrictRealtime = true; DevicePolicy = "closed";
RestrictSUIDSGID = true; RestrictNamespaces = true;
}; RestrictRealtime = true;
}; RestrictSUIDSGID = true;
};
tmpfiles.settings.amarth-customer-portal = { };
"${cfg.settings.dataDir}".d = {
inherit (cfg) user group; tmpfiles.settings.amarth-customer-portal = {
mode = "0700"; "${cfg.settings.dataDir}".d = {
}; inherit (cfg) user group;
}; mode = "0700";
}; };
};
users = { };
users = mkIf (cfg.user == "amarth") {
amarth = { inherit (cfg) group; isSystemUser = true; }; users = {
}; users = mkIf (cfg.user == "amarth") {
amarth = { inherit (cfg) group; isSystemUser = true; };
groups = mkIf (cfg.group == "amarth") { };
amarth = {};
}; groups = mkIf (cfg.group == "amarth") {
}; amarth = {};
};
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ]; };
};
}; networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ];
};
};
} }

View file

@ -1,21 +1,21 @@
{ ... }: { ... }:
{ {
imports = imports =
let let
# Get all subdirectories in the current directory # Get all subdirectories in the current directory
dirContents = builtins.readDir ./.; dirContents = builtins.readDir ./.;
# Filter to include only directories that have a flake-module.nix file # Filter to include only directories that have a flake-module.nix file
# and exclude special directories like 'result' # and exclude special directories like 'result'
validModuleDirs = builtins.filter ( validModuleDirs = builtins.filter (
name: name:
name != "result" name != "result"
&& dirContents.${name} == "directory" && dirContents.${name} == "directory"
&& builtins.pathExists (./. + "/${name}/flake-module.nix") && builtins.pathExists (./. + "/${name}/flake-module.nix")
) (builtins.attrNames dirContents); ) (builtins.attrNames dirContents);
# Create import paths for each valid directory # Create import paths for each valid directory
imports = map (name: ./. + "/${name}/flake-module.nix") validModuleDirs; imports = map (name: ./. + "/${name}/flake-module.nix") validModuleDirs;
in in
imports; imports;
} }

View file

@ -16,6 +16,9 @@
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild
export BETTER_AUTH_SECRET='8&!3$!^U!&56qvSydEJ^E$cr^GSBWWFmbHJCLJ@w7vRWm7!R5b$DSoCmY$GW7HEF'
export SESSION_SECRET='jJBqeVMvQe52HqLYWDunLEKbkkC9JqCrgP92nV5j2dC99eZWCtK9H2NrASH8AbxF'
bun run build --bun bun run build --bun

View file

@ -1,471 +1,471 @@
@layer reset, base, tokens, recipes, utilities; @layer reset, base, tokens, recipes, utilities;
@import "open-props/style" layer(tokens); @import "open-props/style" layer(tokens);
@import "open-props/normalize" layer(reset); @import "open-props/normalize" layer(reset);
@import "open-props/durations" layer(base); @import "open-props/durations" layer(base);
@import "open-props/theme.light.switch.min.css" layer(tokens); @import "open-props/theme.light.switch.min.css" layer(tokens);
@import "open-props/theme.dark.switch.min.css" layer(tokens); @import "open-props/theme.dark.switch.min.css" layer(tokens);
@layer base { @layer base {
html { html {
display: grid; display: grid;
grid: 100% / 100%; grid: 100% / 100%;
inline-size: 100%; inline-size: 100%;
block-size: 100%; block-size: 100%;
overflow: clip; overflow: clip;
/* font-size: clamp(1rem, -0.875rem + 8.333vw, 3.5rem); */ /* font-size: clamp(1rem, -0.875rem + 8.333vw, 3.5rem); */
& > body { & > body {
display: grid; display: grid;
grid: 100% / 100%; grid: 100% / 100%;
inline-size: 100%; inline-size: 100%;
block-size: 100%; block-size: 100%;
contain: layout style paint; contain: layout style paint;
margin: 0; margin: 0;
font-family: sans-serif; font-family: sans-serif;
overflow: clip; overflow: clip;
background-color: var(--surface-3); background-color: var(--surface-3);
color: var(--text-2); color: var(--text-2);
accent-color: var(--primary-500); accent-color: var(--primary-500);
* { * {
box-sizing: border-box; box-sizing: border-box;
&:focus-visible { &:focus-visible {
outline: 1px solid var(--info); outline: 1px solid var(--info);
} }
} }
} }
} }
} }
@layer reset { @layer reset {
@property --sibling-index { @property --sibling-index {
syntax: "<integer>"; syntax: "<integer>";
inherits: false; inherits: false;
initial-value: 1; initial-value: 1;
} }
@property --sibling-count { @property --sibling-count {
syntax: "<integer>"; syntax: "<integer>";
inherits: false; inherits: false;
initial-value: 0; initial-value: 0;
} }
:nth-child(1) { :nth-child(1) {
--sibling-index: 1; --sibling-index: 1;
} }
:nth-child(2) { :nth-child(2) {
--sibling-index: 2; --sibling-index: 2;
} }
:nth-child(3) { :nth-child(3) {
--sibling-index: 3; --sibling-index: 3;
} }
:nth-child(4) { :nth-child(4) {
--sibling-index: 4; --sibling-index: 4;
} }
:nth-child(5) { :nth-child(5) {
--sibling-index: 5; --sibling-index: 5;
} }
:nth-child(6) { :nth-child(6) {
--sibling-index: 6; --sibling-index: 6;
} }
:nth-child(7) { :nth-child(7) {
--sibling-index: 7; --sibling-index: 7;
} }
:nth-child(8) { :nth-child(8) {
--sibling-index: 8; --sibling-index: 8;
} }
:nth-child(9) { :nth-child(9) {
--sibling-index: 9; --sibling-index: 9;
} }
:nth-child(10) { :nth-child(10) {
--sibling-index: 10; --sibling-index: 10;
} }
:nth-child(11) { :nth-child(11) {
--sibling-index: 11; --sibling-index: 11;
} }
:nth-child(12) { :nth-child(12) {
--sibling-index: 12; --sibling-index: 12;
} }
:nth-child(13) { :nth-child(13) {
--sibling-index: 13; --sibling-index: 13;
} }
:nth-child(14) { :nth-child(14) {
--sibling-index: 14; --sibling-index: 14;
} }
:nth-child(15) { :nth-child(15) {
--sibling-index: 15; --sibling-index: 15;
} }
:nth-child(16) { :nth-child(16) {
--sibling-index: 16; --sibling-index: 16;
} }
:nth-child(17) { :nth-child(17) {
--sibling-index: 17; --sibling-index: 17;
} }
:nth-child(18) { :nth-child(18) {
--sibling-index: 18; --sibling-index: 18;
} }
:nth-child(19) { :nth-child(19) {
--sibling-index: 19; --sibling-index: 19;
} }
:nth-child(20) { :nth-child(20) {
--sibling-index: 20; --sibling-index: 20;
} }
:nth-child(21) { :nth-child(21) {
--sibling-index: 21; --sibling-index: 21;
} }
:nth-child(22) { :nth-child(22) {
--sibling-index: 22; --sibling-index: 22;
} }
:nth-child(23) { :nth-child(23) {
--sibling-index: 23; --sibling-index: 23;
} }
:nth-child(24) { :nth-child(24) {
--sibling-index: 24; --sibling-index: 24;
} }
:nth-child(25) { :nth-child(25) {
--sibling-index: 25; --sibling-index: 25;
} }
:nth-child(26) { :nth-child(26) {
--sibling-index: 26; --sibling-index: 26;
} }
:nth-child(27) { :nth-child(27) {
--sibling-index: 27; --sibling-index: 27;
} }
:nth-child(28) { :nth-child(28) {
--sibling-index: 28; --sibling-index: 28;
} }
:nth-child(29) { :nth-child(29) {
--sibling-index: 29; --sibling-index: 29;
} }
:nth-child(30) { :nth-child(30) {
--sibling-index: 30; --sibling-index: 30;
} }
:nth-child(31) { :nth-child(31) {
--sibling-index: 31; --sibling-index: 31;
} }
:nth-child(32) { :nth-child(32) {
--sibling-index: 32; --sibling-index: 32;
} }
:nth-child(33) { :nth-child(33) {
--sibling-index: 33; --sibling-index: 33;
} }
:nth-child(34) { :nth-child(34) {
--sibling-index: 34; --sibling-index: 34;
} }
:nth-child(35) { :nth-child(35) {
--sibling-index: 35; --sibling-index: 35;
} }
:nth-child(36) { :nth-child(36) {
--sibling-index: 36; --sibling-index: 36;
} }
:nth-child(37) { :nth-child(37) {
--sibling-index: 37; --sibling-index: 37;
} }
:nth-child(38) { :nth-child(38) {
--sibling-index: 38; --sibling-index: 38;
} }
:nth-child(39) { :nth-child(39) {
--sibling-index: 39; --sibling-index: 39;
} }
:nth-child(40) { :nth-child(40) {
--sibling-index: 40; --sibling-index: 40;
} }
:nth-child(41) { :nth-child(41) {
--sibling-index: 41; --sibling-index: 41;
} }
:nth-child(42) { :nth-child(42) {
--sibling-index: 42; --sibling-index: 42;
} }
:nth-child(43) { :nth-child(43) {
--sibling-index: 43; --sibling-index: 43;
} }
:nth-child(44) { :nth-child(44) {
--sibling-index: 44; --sibling-index: 44;
} }
:nth-child(45) { :nth-child(45) {
--sibling-index: 45; --sibling-index: 45;
} }
:nth-child(46) { :nth-child(46) {
--sibling-index: 46; --sibling-index: 46;
} }
:nth-child(47) { :nth-child(47) {
--sibling-index: 47; --sibling-index: 47;
} }
:nth-child(48) { :nth-child(48) {
--sibling-index: 48; --sibling-index: 48;
} }
:nth-child(49) { :nth-child(49) {
--sibling-index: 49; --sibling-index: 49;
} }
:nth-child(50) { :nth-child(50) {
--sibling-index: 50; --sibling-index: 50;
} }
:nth-child(51) { :nth-child(51) {
--sibling-index: 51; --sibling-index: 51;
} }
:nth-child(52) { :nth-child(52) {
--sibling-index: 52; --sibling-index: 52;
} }
:nth-child(53) { :nth-child(53) {
--sibling-index: 53; --sibling-index: 53;
} }
:nth-child(54) { :nth-child(54) {
--sibling-index: 54; --sibling-index: 54;
} }
:nth-child(55) { :nth-child(55) {
--sibling-index: 55; --sibling-index: 55;
} }
:nth-child(56) { :nth-child(56) {
--sibling-index: 56; --sibling-index: 56;
} }
:nth-child(57) { :nth-child(57) {
--sibling-index: 57; --sibling-index: 57;
} }
:nth-child(58) { :nth-child(58) {
--sibling-index: 58; --sibling-index: 58;
} }
:nth-child(59) { :nth-child(59) {
--sibling-index: 59; --sibling-index: 59;
} }
:has(> :last-child:nth-child(1)) > * { :has(> :last-child:nth-child(1)) > * {
--sibling-count: 1; --sibling-count: 1;
} }
:has(> :last-child:nth-child(2)) > * { :has(> :last-child:nth-child(2)) > * {
--sibling-count: 2; --sibling-count: 2;
} }
:has(> :last-child:nth-child(3)) > * { :has(> :last-child:nth-child(3)) > * {
--sibling-count: 3; --sibling-count: 3;
} }
:has(> :last-child:nth-child(4)) > * { :has(> :last-child:nth-child(4)) > * {
--sibling-count: 4; --sibling-count: 4;
} }
:has(> :last-child:nth-child(5)) > * { :has(> :last-child:nth-child(5)) > * {
--sibling-count: 5; --sibling-count: 5;
} }
:has(> :last-child:nth-child(6)) > * { :has(> :last-child:nth-child(6)) > * {
--sibling-count: 6; --sibling-count: 6;
} }
:has(> :last-child:nth-child(7)) > * { :has(> :last-child:nth-child(7)) > * {
--sibling-count: 7; --sibling-count: 7;
} }
:has(> :last-child:nth-child(8)) > * { :has(> :last-child:nth-child(8)) > * {
--sibling-count: 8; --sibling-count: 8;
} }
:has(> :last-child:nth-child(9)) > * { :has(> :last-child:nth-child(9)) > * {
--sibling-count: 9; --sibling-count: 9;
} }
:has(> :last-child:nth-child(10)) > * { :has(> :last-child:nth-child(10)) > * {
--sibling-count: 10; --sibling-count: 10;
} }
:has(> :last-child:nth-child(11)) > * { :has(> :last-child:nth-child(11)) > * {
--sibling-count: 11; --sibling-count: 11;
} }
:has(> :last-child:nth-child(12)) > * { :has(> :last-child:nth-child(12)) > * {
--sibling-count: 12; --sibling-count: 12;
} }
:has(> :last-child:nth-child(13)) > * { :has(> :last-child:nth-child(13)) > * {
--sibling-count: 13; --sibling-count: 13;
} }
:has(> :last-child:nth-child(14)) > * { :has(> :last-child:nth-child(14)) > * {
--sibling-count: 14; --sibling-count: 14;
} }
:has(> :last-child:nth-child(15)) > * { :has(> :last-child:nth-child(15)) > * {
--sibling-count: 15; --sibling-count: 15;
} }
:has(> :last-child:nth-child(16)) > * { :has(> :last-child:nth-child(16)) > * {
--sibling-count: 16; --sibling-count: 16;
} }
:has(> :last-child:nth-child(17)) > * { :has(> :last-child:nth-child(17)) > * {
--sibling-count: 17; --sibling-count: 17;
} }
:has(> :last-child:nth-child(18)) > * { :has(> :last-child:nth-child(18)) > * {
--sibling-count: 18; --sibling-count: 18;
} }
:has(> :last-child:nth-child(19)) > * { :has(> :last-child:nth-child(19)) > * {
--sibling-count: 19; --sibling-count: 19;
} }
:has(> :last-child:nth-child(20)) > * { :has(> :last-child:nth-child(20)) > * {
--sibling-count: 20; --sibling-count: 20;
} }
:has(> :last-child:nth-child(21)) > * { :has(> :last-child:nth-child(21)) > * {
--sibling-count: 21; --sibling-count: 21;
} }
:has(> :last-child:nth-child(22)) > * { :has(> :last-child:nth-child(22)) > * {
--sibling-count: 22; --sibling-count: 22;
} }
:has(> :last-child:nth-child(23)) > * { :has(> :last-child:nth-child(23)) > * {
--sibling-count: 23; --sibling-count: 23;
} }
:has(> :last-child:nth-child(24)) > * { :has(> :last-child:nth-child(24)) > * {
--sibling-count: 24; --sibling-count: 24;
} }
:has(> :last-child:nth-child(25)) > * { :has(> :last-child:nth-child(25)) > * {
--sibling-count: 25; --sibling-count: 25;
} }
:has(> :last-child:nth-child(26)) > * { :has(> :last-child:nth-child(26)) > * {
--sibling-count: 26; --sibling-count: 26;
} }
:has(> :last-child:nth-child(27)) > * { :has(> :last-child:nth-child(27)) > * {
--sibling-count: 27; --sibling-count: 27;
} }
:has(> :last-child:nth-child(28)) > * { :has(> :last-child:nth-child(28)) > * {
--sibling-count: 28; --sibling-count: 28;
} }
:has(> :last-child:nth-child(29)) > * { :has(> :last-child:nth-child(29)) > * {
--sibling-count: 29; --sibling-count: 29;
} }
:has(> :last-child:nth-child(30)) > * { :has(> :last-child:nth-child(30)) > * {
--sibling-count: 30; --sibling-count: 30;
} }
:has(> :last-child:nth-child(31)) > * { :has(> :last-child:nth-child(31)) > * {
--sibling-count: 31; --sibling-count: 31;
} }
:has(> :last-child:nth-child(32)) > * { :has(> :last-child:nth-child(32)) > * {
--sibling-count: 32; --sibling-count: 32;
} }
:has(> :last-child:nth-child(33)) > * { :has(> :last-child:nth-child(33)) > * {
--sibling-count: 33; --sibling-count: 33;
} }
:has(> :last-child:nth-child(34)) > * { :has(> :last-child:nth-child(34)) > * {
--sibling-count: 34; --sibling-count: 34;
} }
:has(> :last-child:nth-child(35)) > * { :has(> :last-child:nth-child(35)) > * {
--sibling-count: 35; --sibling-count: 35;
} }
:has(> :last-child:nth-child(36)) > * { :has(> :last-child:nth-child(36)) > * {
--sibling-count: 36; --sibling-count: 36;
} }
:has(> :last-child:nth-child(37)) > * { :has(> :last-child:nth-child(37)) > * {
--sibling-count: 37; --sibling-count: 37;
} }
:has(> :last-child:nth-child(38)) > * { :has(> :last-child:nth-child(38)) > * {
--sibling-count: 38; --sibling-count: 38;
} }
:has(> :last-child:nth-child(39)) > * { :has(> :last-child:nth-child(39)) > * {
--sibling-count: 39; --sibling-count: 39;
} }
:has(> :last-child:nth-child(40)) > * { :has(> :last-child:nth-child(40)) > * {
--sibling-count: 40; --sibling-count: 40;
} }
:has(> :last-child:nth-child(41)) > * { :has(> :last-child:nth-child(41)) > * {
--sibling-count: 41; --sibling-count: 41;
} }
:has(> :last-child:nth-child(42)) > * { :has(> :last-child:nth-child(42)) > * {
--sibling-count: 42; --sibling-count: 42;
} }
:has(> :last-child:nth-child(43)) > * { :has(> :last-child:nth-child(43)) > * {
--sibling-count: 43; --sibling-count: 43;
} }
:has(> :last-child:nth-child(44)) > * { :has(> :last-child:nth-child(44)) > * {
--sibling-count: 44; --sibling-count: 44;
} }
:has(> :last-child:nth-child(45)) > * { :has(> :last-child:nth-child(45)) > * {
--sibling-count: 45; --sibling-count: 45;
} }
:has(> :last-child:nth-child(46)) > * { :has(> :last-child:nth-child(46)) > * {
--sibling-count: 46; --sibling-count: 46;
} }
:has(> :last-child:nth-child(47)) > * { :has(> :last-child:nth-child(47)) > * {
--sibling-count: 47; --sibling-count: 47;
} }
:has(> :last-child:nth-child(48)) > * { :has(> :last-child:nth-child(48)) > * {
--sibling-count: 48; --sibling-count: 48;
} }
:has(> :last-child:nth-child(49)) > * { :has(> :last-child:nth-child(49)) > * {
--sibling-count: 49; --sibling-count: 49;
} }
:has(> :last-child:nth-child(50)) > * { :has(> :last-child:nth-child(50)) > * {
--sibling-count: 50; --sibling-count: 50;
} }
:has(> :last-child:nth-child(51)) > * { :has(> :last-child:nth-child(51)) > * {
--sibling-count: 51; --sibling-count: 51;
} }
:has(> :last-child:nth-child(52)) > * { :has(> :last-child:nth-child(52)) > * {
--sibling-count: 52; --sibling-count: 52;
} }
:has(> :last-child:nth-child(53)) > * { :has(> :last-child:nth-child(53)) > * {
--sibling-count: 53; --sibling-count: 53;
} }
:has(> :last-child:nth-child(54)) > * { :has(> :last-child:nth-child(54)) > * {
--sibling-count: 54; --sibling-count: 54;
} }
:has(> :last-child:nth-child(55)) > * { :has(> :last-child:nth-child(55)) > * {
--sibling-count: 55; --sibling-count: 55;
} }
:has(> :last-child:nth-child(56)) > * { :has(> :last-child:nth-child(56)) > * {
--sibling-count: 56; --sibling-count: 56;
} }
:has(> :last-child:nth-child(57)) > * { :has(> :last-child:nth-child(57)) > * {
--sibling-count: 57; --sibling-count: 57;
} }
:has(> :last-child:nth-child(58)) > * { :has(> :last-child:nth-child(58)) > * {
--sibling-count: 58; --sibling-count: 58;
} }
:has(> :last-child:nth-child(59)) > * { :has(> :last-child:nth-child(59)) > * {
--sibling-count: 59; --sibling-count: 59;
} }
:has(> :last-child:nth-child(60)) > * { :has(> :last-child:nth-child(60)) > * {
--sibling-count: 60; --sibling-count: 60;
} }
:has(> :last-child:nth-child(61)) > * { :has(> :last-child:nth-child(61)) > * {
--sibling-count: 61; --sibling-count: 61;
} }
:has(> :last-child:nth-child(62)) > * { :has(> :last-child:nth-child(62)) > * {
--sibling-count: 62; --sibling-count: 62;
} }
:has(> :last-child:nth-child(63)) > * { :has(> :last-child:nth-child(63)) > * {
--sibling-count: 63; --sibling-count: 63;
} }
:has(> :last-child:nth-child(64)) > * { :has(> :last-child:nth-child(64)) > * {
--sibling-count: 64; --sibling-count: 64;
} }
:has(> :last-child:nth-child(65)) > * { :has(> :last-child:nth-child(65)) > * {
--sibling-count: 65; --sibling-count: 65;
} }
:has(> :last-child:nth-child(66)) > * { :has(> :last-child:nth-child(66)) > * {
--sibling-count: 66; --sibling-count: 66;
} }
:has(> :last-child:nth-child(67)) > * { :has(> :last-child:nth-child(67)) > * {
--sibling-count: 67; --sibling-count: 67;
} }
:has(> :last-child:nth-child(68)) > * { :has(> :last-child:nth-child(68)) > * {
--sibling-count: 68; --sibling-count: 68;
} }
:has(> :last-child:nth-child(69)) > * { :has(> :last-child:nth-child(69)) > * {
--sibling-count: 69; --sibling-count: 69;
} }
} }

View file

@ -1,35 +1,35 @@
import { Component, createSignal, onCleanup, onMount } from "solid-js"; import { Component, createSignal, onCleanup, onMount } from "solid-js";
import { Entry } from "~/features/content"; import { Entry } from "~/features/content";
import css from "./details.module.css"; import css from "./details.module.css";
interface DetailsProps { interface DetailsProps {
entry: Entry; entry: Entry;
} }
export const Details: Component<DetailsProps> = (props) => { export const Details: Component<DetailsProps> = (props) => {
const [header, setHeader] = createSignal<HTMLElement>(); const [header, setHeader] = createSignal<HTMLElement>();
onMount(() => { onMount(() => {
const observer = new ResizeObserver(([entry]) => { const observer = new ResizeObserver(([entry]) => {
const { inlineSize, blockSize } = entry.contentBoxSize[0]; const { inlineSize, blockSize } = entry.contentBoxSize[0];
(entry.target as HTMLElement).style.setProperty( (entry.target as HTMLElement).style.setProperty(
"--ratio", "--ratio",
String((blockSize * 0.2) / inlineSize) String((blockSize * 0.2) / inlineSize)
); );
}); });
observer.observe(header()!); observer.observe(header()!);
onCleanup(() => observer.disconnect()); onCleanup(() => observer.disconnect());
}); });
return ( return (
<div class={css.container}> <div class={css.container}>
<header ref={setHeader} class={css.header}> <header ref={setHeader} class={css.header}>
<img class={css.background} src={props.entry.image} /> <img class={css.background} src={props.entry.image} />
<h1>{props.entry.title}</h1> <h1>{props.entry.title}</h1>
</header> </header>
</div> </div>
); );
}; };

View file

@ -1,88 +1,88 @@
import { import {
ContextProviderProps, ContextProviderProps,
createContextProvider, createContextProvider,
} from "@solid-primitives/context"; } from "@solid-primitives/context";
import { action, createAsyncStore, query, useAction } from "@solidjs/router"; import { action, createAsyncStore, query, useAction } from "@solidjs/router";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import { useSession } from "vinxi/http"; import { useSession } from "vinxi/http";
export enum ColorScheme { export enum ColorScheme {
Auto = "light dark", Auto = "light dark",
Light = "light", Light = "light",
Dark = "dark", Dark = "dark",
} }
export interface State { export interface State {
colorScheme: ColorScheme; colorScheme: ColorScheme;
hue: number; hue: number;
} }
const getSession = async () => { const getSession = async () => {
"use server"; "use server";
return useSession<State>({ return useSession<State>({
password: process.env.SESSION_SECRET!, password: process.env.SESSION_SECRET!,
}); });
}; };
export const getState = query(async () => { export const getState = query(async () => {
"use server"; "use server";
const session = await getSession(); const session = await getSession();
if (Object.getOwnPropertyNames(session.data).length === 0) { if (Object.getOwnPropertyNames(session.data).length === 0) {
await session.update({ await session.update({
colorScheme: ColorScheme.Auto, colorScheme: ColorScheme.Auto,
hue: 0, hue: 0,
}); });
} }
return session.data; return session.data;
}, "color-scheme"); }, "color-scheme");
const setState = action(async (state: State) => { const setState = action(async (state: State) => {
"use server"; "use server";
const session = await getSession(); const session = await getSession();
await session.update((prev) => ({ ...prev, ...state })); await session.update((prev) => ({ ...prev, ...state }));
}, "color-scheme"); }, "color-scheme");
interface ThemeContextType { interface ThemeContextType {
readonly theme: State; readonly theme: State;
setColorScheme(colorScheme: ColorScheme): void; setColorScheme(colorScheme: ColorScheme): void;
setHue(colorScheme: number): void; setHue(colorScheme: number): void;
} }
const [ThemeContextProvider, useTheme] = createContextProvider< const [ThemeContextProvider, useTheme] = createContextProvider<
ThemeContextType, ThemeContextType,
ContextProviderProps ContextProviderProps
>( >(
(props) => { (props) => {
const updateState = useAction(setState); const updateState = useAction(setState);
const state = createAsyncStore(() => getState()); const state = createAsyncStore(() => getState());
return { return {
get theme() { get theme() {
return state.latest ?? { colorScheme: null }; return state.latest ?? { colorScheme: null };
}, },
setColorScheme(colorScheme) { setColorScheme(colorScheme) {
// updateState({ colorScheme, hue: state.latest!.hue }); // updateState({ colorScheme, hue: state.latest!.hue });
}, },
setHue(hue) { setHue(hue) {
// updateState({ hue, colorScheme: state.latest!.colorScheme }); // updateState({ hue, colorScheme: state.latest!.colorScheme });
}, },
}; };
}, },
{ {
theme: { theme: {
colorScheme: ColorScheme.Auto, colorScheme: ColorScheme.Auto,
hue: 180, hue: 180,
}, },
setColorScheme(colorScheme) {}, setColorScheme(colorScheme) {},
setHue(hue) {}, setHue(hue) {},
}, },
); );
export { ThemeContextProvider, useTheme }; export { ThemeContextProvider, useTheme };

View file

@ -1,4 +1,4 @@
export { ThemeContextProvider, useTheme } from './context'; export { ThemeContextProvider, useTheme } from './context';
export { ColorSchemePicker } from './picker'; export { ColorSchemePicker } from './picker';

View file

@ -1,9 +1,9 @@
.picker { .picker {
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
} }
.hue { .hue {
display: flex; display: flex;
flex-flow: row; flex-flow: row;
align-items: center; align-items: center;
} }

View file

@ -1,69 +1,69 @@
import { import {
WiMoonAltFirstQuarter, WiMoonAltFirstQuarter,
WiMoonAltFull, WiMoonAltFull,
WiMoonAltNew, WiMoonAltNew,
} from "solid-icons/wi"; } from "solid-icons/wi";
import { import {
Component, Component,
createEffect, createEffect,
For, For,
Match, Match,
on, on,
Setter, Setter,
Switch, Switch,
} from "solid-js"; } from "solid-js";
import { ColorScheme, useTheme } from "./context"; import { ColorScheme, useTheme } from "./context";
import css from "./picker.module.css"; import css from "./picker.module.css";
import { Select } from "~/components/select"; import { Select } from "~/components/select";
const colorSchemes: Record<ColorScheme, keyof typeof ColorScheme> = const colorSchemes: Record<ColorScheme, keyof typeof ColorScheme> =
Object.fromEntries( Object.fromEntries(
Object.entries(ColorScheme).map(([k, v]) => [v, k]) Object.entries(ColorScheme).map(([k, v]) => [v, k])
) as any; ) as any;
export const ColorSchemePicker: Component = (props) => { export const ColorSchemePicker: Component = (props) => {
const themeContext = useTheme(); const themeContext = useTheme();
const setScheme: Setter<ColorScheme> = (next) => { const setScheme: Setter<ColorScheme> = (next) => {
if (typeof next === "function") { if (typeof next === "function") {
next = next(); next = next();
} }
themeContext.setColorScheme(next); themeContext.setColorScheme(next);
}; };
return ( return (
<> <>
<label aria-label="Color scheme picker"> <label aria-label="Color scheme picker">
<Select <Select
id="color-scheme-picker" id="color-scheme-picker"
class={css.picker} class={css.picker}
value={themeContext.theme.colorScheme} value={themeContext.theme.colorScheme}
setValue={setScheme} setValue={setScheme}
values={colorSchemes} values={colorSchemes}
> >
{(k, v) => ( {(k, v) => (
<> <>
<Switch> <Switch>
<Match when={k === ColorScheme.Auto}> <Match when={k === ColorScheme.Auto}>
<WiMoonAltFirstQuarter /> <WiMoonAltFirstQuarter />
</Match> </Match>
<Match when={k === ColorScheme.Light}> <Match when={k === ColorScheme.Light}>
<WiMoonAltNew /> <WiMoonAltNew />
</Match> </Match>
<Match when={k === ColorScheme.Dark}> <Match when={k === ColorScheme.Dark}>
<WiMoonAltFull /> <WiMoonAltFull />
</Match> </Match>
</Switch> </Switch>
{v} {v}
</> </>
)} )}
</Select> </Select>
</label> </label>
{/* <label class={css.hue} aria-label="Hue slider"> {/* <label class={css.hue} aria-label="Hue slider">
<input type="range" min="0" max="360" value={theme.hue} onInput={e => setHue(e.target.valueAsNumber)} /> <input type="range" min="0" max="360" value={theme.hue} onInput={e => setHue(e.target.valueAsNumber)} />
</label> */} </label> */}
</> </>
); );
}; };

View file

@ -1,41 +1,41 @@
import { Meta } from "@solidjs/meta"; import { Meta } from "@solidjs/meta";
import { query, createAsync } from "@solidjs/router"; import { query, createAsync } from "@solidjs/router";
import { ParentProps } from "solid-js"; import { ParentProps } from "solid-js";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import { auth } from "~/auth.server"; import { auth } from "~/auth.server";
import { Shell } from "~/features/shell"; import { Shell } from "~/features/shell";
import { useTheme } from "~/features/theme"; import { useTheme } from "~/features/theme";
import { User } from "~/features/user"; import { User } from "~/features/user";
const load = query(async (): Promise<User | undefined> => { const load = query(async (): Promise<User | undefined> => {
"use server"; "use server";
const session = await auth.api.getSession(getRequestEvent()!.request); const session = await auth.api.getSession(getRequestEvent()!.request);
if (session === null) { if (session === null) {
return undefined; return undefined;
} }
const { username, name, email, image = null } = session.user; const { username, name, email, image = null } = session.user;
return { username, name, email, image }; return { username, name, email, image };
}, "session"); }, "session");
export const route = { export const route = {
async preload() { async preload() {
return load(); return load();
}, },
}; };
export default function ShellPage(props: ParentProps) { export default function ShellPage(props: ParentProps) {
const user = createAsync(() => load()); const user = createAsync(() => load());
const themeContext = useTheme(); const themeContext = useTheme();
return ( return (
<Shell user={user()}> <Shell user={user()}>
<Meta name="color-scheme" content={themeContext.theme.colorScheme} /> <Meta name="color-scheme" content={themeContext.theme.colorScheme} />
{props.children} {props.children}
</Shell> </Shell>
); );
} }