mirror of
https://codeberg.org/vlw/vlw.se.git
synced 2025-09-14 13:23:41 +02:00
Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
e8a81b789b | |||
5e9317def5 | |||
8209ea5ecc | |||
85e8e00091 | |||
d4d73e9278 | |||
fafa8c5852 | |||
03492df615 | |||
37f2ac00c3 | |||
d5cc7fa82c | |||
17bd93dca9 | |||
6b5eee505a | |||
cd3139e778 | |||
afe450a6d2 | |||
b398354594 | |||
62ddd25f38 | |||
fef10a8ea8 | |||
a6c74f5c4f | |||
f60a27855d | |||
c5c7aaa919 | |||
f4279c0343 | |||
f76962e2ac | |||
5c7c9d2d3a | |||
041d175757 | |||
0bb7a3a8be | |||
0baa6f8d85 |
107 changed files with 1897 additions and 2879 deletions
|
@ -1,20 +1,15 @@
|
||||||
[client_api]
|
[mariadb]
|
||||||
base_url = ""
|
|
||||||
api_key = ""
|
|
||||||
verify_peer = true
|
|
||||||
|
|
||||||
[client_time_available]
|
|
||||||
time_zone = "Europe/Stockholm"
|
|
||||||
available_to_hour = 0;
|
|
||||||
reply_average_hours = 0;
|
|
||||||
available_from_hour = 0;
|
|
||||||
|
|
||||||
[server_database]
|
|
||||||
host = ""
|
host = ""
|
||||||
user = ""
|
user = ""
|
||||||
pass = ""
|
pass = ""
|
||||||
db = ""
|
db = ""
|
||||||
|
|
||||||
[server_forgejo]
|
[config_time_available]
|
||||||
base_url = ""
|
time_zone = "Europe/Stockholm"
|
||||||
scan_profiles = ""
|
available_to_hour = 0;
|
||||||
|
reply_average_hours = 0;
|
||||||
|
available_from_hour = 0;
|
||||||
|
|
||||||
|
[service_forgejo]
|
||||||
|
url = ""
|
||||||
|
profiles = ""
|
20
.gitignore
vendored
20
.gitignore
vendored
|
@ -1,22 +1,2 @@
|
||||||
# Public assets #
|
|
||||||
#################
|
|
||||||
public/.well-known
|
|
||||||
public/assets/js/modules/npm
|
|
||||||
|
|
||||||
# Bootstrapping #
|
|
||||||
#################
|
|
||||||
vendor
|
vendor
|
||||||
node_modules
|
|
||||||
.env.ini
|
.env.ini
|
||||||
|
|
||||||
# OS generated files #
|
|
||||||
######################
|
|
||||||
.DS_Store
|
|
||||||
.DS_Store?
|
|
||||||
._*
|
|
||||||
.Spotlight-V100
|
|
||||||
.Trashes
|
|
||||||
Icon?
|
|
||||||
ehthumbs.db
|
|
||||||
Thumbs.db
|
|
||||||
.directory
|
|
||||||
|
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "reflect"]
|
||||||
|
path = reflect
|
||||||
|
url = https://codeberg.org/reflect/reflect
|
||||||
|
[submodule "vegvisir"]
|
||||||
|
path = vegvisir
|
||||||
|
url = https://codeberg.org/vegvisir/vegvisir
|
69
README.md
69
README.md
|
@ -1,66 +1,27 @@
|
||||||
# vlw.se
|
# vlw.se
|
||||||
This is the source code behind [vlw.se](https://vlw.se) which is my personal website that I have written and designed from the ground up. The website is built on top of my own [web framework](https://vegvisir.vlw.se) and its API is also built on top of my own [API framework](https://reflect.vlw.se).
|
This is the source code behind [vlw.se](https://vlw.se) which is my personal website that I have written and designed from the ground up. The website is built on top of my own [Vegvisir web framework](https://vegvisir.vlw.se) and its optional REST API is built on top of my [Reflect API framework](https://reflect.vlw.se).
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
Here's how you get my website up and running on your own machine. Note, I have only tested this on Linux and the install script we will run later is written in bash.
|
Here's how you get my website up and running on your own machine. Note, I have only tested this on Linux and the install script we will run requires Bash with `coreutils` installed.
|
||||||
|
|
||||||
**Make sure you have both of these package managers installed before proceeding:**
|
## Prerequisites
|
||||||
- [Composer](https://getcomposer.org/)
|
- A web server
|
||||||
- [NPM](https://www.npmjs.com/)
|
- A MariaDB/MySQL server
|
||||||
|
- PHP 8.4 or newer with the following extensions enabled:
|
||||||
|
- - `php8.4-mysql`
|
||||||
|
- - `php8.4-mbstring`
|
||||||
|
- The composer package manager
|
||||||
|
- Bash with `coreutils` installed (for the install script)
|
||||||
|
|
||||||
## 1. Clone this repo
|
## 1. Clone this repo
|
||||||
Clone/download this repo to your machine. Preferably to a non-public directory - the frameworks will handle that.
|
Clone this repository with its submodules. Preferably to a non-public directory - the frameworks will handle that.
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://codeberg.org/vlw/vlw.se --depth 1
|
git clone https://codeberg.org/vlw/vlw.se --recurse-submodules --depth 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Install [Vegvisir](https://vegvisir.vlw.se) and [Reflect](https://reflect.vlw.se)
|
## 2. Run the install script
|
||||||
Follow the installation instructions for my web, and API framework. This site uses the default configuration for both frameworks so the only thing you need to do after you've installed both is to point the `root_path` and `endpoints` directory respectively to the directory where you cloned this repo.
|
Run the `install.sh` script from the root directory of this repository.
|
||||||
|
|
||||||
- [Vegvisir installation](https://vegvisir.vlw.se)
|
|
||||||
- [Reflect installation](https://reflect.vlw.se)
|
|
||||||
|
|
||||||
*Example:*
|
|
||||||
```sh
|
|
||||||
# Vegvisir
|
|
||||||
root_path = "/var/www/vlw.se"
|
|
||||||
# Reflect
|
|
||||||
endpoints = "/var/www/vlw.se"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. Run the install script
|
|
||||||
Run the `install.sh` script from the root of the repo directory. [Make sure you have the required package managers installed](#installation).
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```sh
|
|
||||||
# vlw@example:$
|
|
||||||
cd /var/www/vlw.se
|
|
||||||
# vlw@example:/var/www/vlw.se$
|
|
||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
This script will install and configure Vegvisir, Reflect, and the website through a few propmpted steps.
|
||||||
## 4. Import the database templates
|
|
||||||
There's are two SQL files that you can download from the releases page that has a snapshot of the MariaDB databases I use on my live website. The snapshot data for the website databse is not guaranteed to be up to date; but the database structure will be. Download and import these files into two existing databases. One for the website data, and the other has the Reflect API configurations.
|
|
||||||
|
|
||||||
- [Download SQL-snapshots](https://codeberg.org/vlw/vlw.se/releases)
|
|
||||||
|
|
||||||
## 5. Set environment variables
|
|
||||||
Make a copy of the `.env.example.ini` file called `.env.ini` from the root directory of the repo. There are a few parameters you can change here but the required ones are the following:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[client_api]
|
|
||||||
base_url = ""
|
|
||||||
api_key = ""
|
|
||||||
|
|
||||||
[server_database]
|
|
||||||
host = ""
|
|
||||||
user = ""
|
|
||||||
pass = ""
|
|
||||||
db = ""
|
|
||||||
```
|
|
||||||
|
|
||||||
Please refer to the comments in the ini file for more information about each field.
|
|
||||||
|
|
||||||
## Done!
|
|
||||||
That should be it. Navigate to your configured Vegvisir public host!
|
|
29
api/coffee/DELETE.php
Normal file
29
api/coffee/DELETE.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Reflect\{Response, Path};
|
||||||
|
use Reflect\Rules\{Ruleset, Rules, Type};
|
||||||
|
|
||||||
|
use VLW\API\API;
|
||||||
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Models\Coffee\Coffee;
|
||||||
|
use VLW\Database\Tables\Coffee\Coffee as CoffeeTable;
|
||||||
|
|
||||||
|
require_once Path::root("src/API/API.php");
|
||||||
|
require_once Path::root("src/Database/Models/Coffee/Coffee.php");
|
||||||
|
require_once Path::root("src/Database/Tables/Coffee/Coffee.php");
|
||||||
|
|
||||||
|
final class DELETE_Coffee extends API {
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(new Ruleset()->GET([
|
||||||
|
new Rules(CoffeeTable::ID->value)
|
||||||
|
->required()
|
||||||
|
->type(Type::STRING)
|
||||||
|
->min(UUID::LENGTH)
|
||||||
|
->max(UUID::LENGTH)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function main(): Response {
|
||||||
|
return new Response(new Coffee($_GET[CoffeeTable::ID->value])->delete());
|
||||||
|
}
|
||||||
|
}
|
19
api/coffee/GET.php
Normal file
19
api/coffee/GET.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Reflect\{Response, Path};
|
||||||
|
|
||||||
|
use VLW\API\API;
|
||||||
|
use VLW\Database\Models\Coffee\Coffee;
|
||||||
|
|
||||||
|
require_once Path::root("src/API/API.php");
|
||||||
|
require_once Path::root("src/Database/Models/Coffee/Coffee.php");
|
||||||
|
|
||||||
|
final class GET_Coffee extends API {
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function main(): Response {
|
||||||
|
return new Response(Coffee::all());
|
||||||
|
}
|
||||||
|
}
|
41
api/coffee/POST.php
Normal file
41
api/coffee/POST.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Reflect\{Response, Path};
|
||||||
|
use Reflect\Rules\{Ruleset, Rules, Type};
|
||||||
|
|
||||||
|
use VLW\API\API;
|
||||||
|
use VLW\Database\Models\Coffee\Coffee;
|
||||||
|
use VLW\Database\Tables\Coffee\Coffee as CoffeeTable;
|
||||||
|
|
||||||
|
require_once Path::root("src/API/API.php");
|
||||||
|
require_once Path::root("src/Database/Models/Coffee/Coffee.php");
|
||||||
|
require_once Path::root("src/Database/Tables/Coffee/Coffee.php");
|
||||||
|
|
||||||
|
final class POST_Coffee extends API {
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(new Ruleset(strict: true)->POST([
|
||||||
|
new Rules(CoffeeTable::DATE_CREATED->value)
|
||||||
|
->type(Type::STRING)
|
||||||
|
->type(Type::NUMBER)
|
||||||
|
->default(null)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function main(): Response {
|
||||||
|
$datetime = new DateTimeImmutable();
|
||||||
|
|
||||||
|
// Parse DateTime from POST string
|
||||||
|
if ($_POST[CoffeeTable::DATE_CREATED->value]) {
|
||||||
|
try {
|
||||||
|
// Create DateTimeImmutable from Unix timestamp or datetime string
|
||||||
|
$datetime = gettype($_POST[CoffeeTable::DATE_CREATED->value]) === "integer"
|
||||||
|
? DateTimeImmutable::createFromTimestamp($_POST[CoffeeTable::DATE_CREATED->value])
|
||||||
|
: new DateTimeImmutable($_POST[CoffeeTable::DATE_CREATED->value]);
|
||||||
|
} catch (DateMalformedStringException $error) {
|
||||||
|
return new Response($error->getMessage(), 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(Coffee::new($datetime));
|
||||||
|
}
|
||||||
|
}
|
19
api/languages/GET.php
Normal file
19
api/languages/GET.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Reflect\{Response, Path};
|
||||||
|
|
||||||
|
use VLW\API\API;
|
||||||
|
use VLW\Database\Models\Languages\Language;
|
||||||
|
|
||||||
|
require_once Path::root("src/API/API.php");
|
||||||
|
require_once Path::root("src/Database/Models/Languages/Language.php");
|
||||||
|
|
||||||
|
final class GET_Languages extends API {
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function main(): Response {
|
||||||
|
return new Response(Language::all());
|
||||||
|
}
|
||||||
|
}
|
71
api/update/GET.php
Normal file
71
api/update/GET.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use \vlw\xEnum;
|
||||||
|
use Reflect\{Response, Path};
|
||||||
|
use Reflect\Rules\{Ruleset, Rules, Type};
|
||||||
|
|
||||||
|
use VLW\API\API;
|
||||||
|
use VLW\Helpers\{
|
||||||
|
Forgejo,
|
||||||
|
GenerateSearch,
|
||||||
|
GenerateTimeline
|
||||||
|
};
|
||||||
|
|
||||||
|
require_once Path::root("src/API/API.php");
|
||||||
|
require_once Path::root("src/Helpers/Forgejo.php");
|
||||||
|
require_once Path::root("src/Helpers/GenerateSearch.php");
|
||||||
|
require_once Path::root("src/Helpers/GenerateTimeline.php");
|
||||||
|
|
||||||
|
enum ServiceEnum: string {
|
||||||
|
use xEnum;
|
||||||
|
|
||||||
|
case ALL = "all";
|
||||||
|
case SEARCH = "search";
|
||||||
|
case FORGEJO = "forgejo";
|
||||||
|
case TIMELINE = "timeline";
|
||||||
|
}
|
||||||
|
|
||||||
|
final class GET_Update extends API {
|
||||||
|
private const KEY_SERVICE = "service";
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(new Ruleset()->GET([
|
||||||
|
new Rules(self::KEY_SERVICE)
|
||||||
|
->type(Type::ENUM, ServiceEnum::values())
|
||||||
|
->default(ServiceEnum::ALL->value)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function main(): Response {
|
||||||
|
switch ($_GET[self::KEY_SERVICE]) {
|
||||||
|
case ServiceEnum::FORGEJO->value:
|
||||||
|
return new Response($this->update_forgejo());
|
||||||
|
|
||||||
|
case ServiceEnum::SEARCH->value:
|
||||||
|
return new Response($this->update_search());
|
||||||
|
|
||||||
|
case ServiceEnum::TIMELINE->value:
|
||||||
|
return new Response($this->update_timeline());
|
||||||
|
|
||||||
|
case ServiceEnum::ALL->value:
|
||||||
|
default:
|
||||||
|
return new Response(
|
||||||
|
$this->update_timeline() &&
|
||||||
|
$this->update_search() &&
|
||||||
|
$this->update_forgejo()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update_timeline(): bool {
|
||||||
|
return new GenerateTimeline()->generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update_search(): bool {
|
||||||
|
return new GenerateSearch()->generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update_forgejo(): bool {
|
||||||
|
return new Forgejo()->update();
|
||||||
|
}
|
||||||
|
}
|
28
api/work/GET.php
Normal file
28
api/work/GET.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Reflect\{Response, Path};
|
||||||
|
|
||||||
|
use VLW\API\API;
|
||||||
|
use VLW\Database\Models\Work\{Work, Tag};
|
||||||
|
|
||||||
|
require_once Path::root("src/API/API.php");
|
||||||
|
require_once Path::root("src/Database/Models/Work/Tag.php");
|
||||||
|
require_once Path::root("src/Database/Models/Work/Work.php");
|
||||||
|
|
||||||
|
final class GET_Work extends API {
|
||||||
|
private static function entity(Work $work): object {
|
||||||
|
return (object) [
|
||||||
|
"tags" => array_map(fn(Tag $tag): string => $tag->label->name, Tag::from($work)),
|
||||||
|
"actions" => [],
|
||||||
|
"details" => $work
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function main(): Response {
|
||||||
|
return new Response(array_map(fn(Work $work): object => self::entity($work), Work::all()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"reflect/client": "dev-master",
|
|
||||||
"reflect/plugin-rules": "dev-master",
|
|
||||||
"vlw/mysql": "dev-master",
|
"vlw/mysql": "dev-master",
|
||||||
"vlw/xenum": "dev-master"
|
"vlw/xenum": "dev-master"
|
||||||
},
|
},
|
||||||
|
|
68
composer.lock
generated
68
composer.lock
generated
|
@ -4,71 +4,15 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "cb70f9f3f538a72aa8bcf906fdc906bf",
|
"content-hash": "a7ce20d192550ef2d037220b593b5eb9",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
|
||||||
"name": "reflect/client",
|
|
||||||
"version": "dev-master",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://codeberg.org/reflect/client-php",
|
|
||||||
"reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06"
|
|
||||||
},
|
|
||||||
"default-branch": true,
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Reflect\\": "src/Reflect/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"GPL-2.0-only"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Victor Westerlund",
|
|
||||||
"email": "victor.vesterlund@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Extendable PHP interface for communicating with Reflect API over HTTP or UNIX sockets",
|
|
||||||
"time": "2024-04-06T14:55:04+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "reflect/plugin-rules",
|
|
||||||
"version": "dev-master",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://codeberg.org/reflect/rules-plugin",
|
|
||||||
"reference": "aa7d969350f50d00d7dce01b948276946fcc0e81"
|
|
||||||
},
|
|
||||||
"default-branch": true,
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"ReflectRules\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"GPL-3.0-only"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Victor Westerlund",
|
|
||||||
"email": "victor.vesterlund@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Add request search paramter and request body constraints to an API built with Reflect",
|
|
||||||
"time": "2024-11-28T17:05:16+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "vlw/mysql",
|
"name": "vlw/mysql",
|
||||||
"version": "dev-master",
|
"version": "dev-master",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://codeberg.org/vlw/php-mysql",
|
"url": "https://codeberg.org/vlw/php-mysql",
|
||||||
"reference": "c64eb96049907da60dc9f237d26aef0e531b0015"
|
"reference": "0e367f797fa9348408881ed758976f21e8c667e4"
|
||||||
},
|
},
|
||||||
"default-branch": true,
|
"default-branch": true,
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
@ -88,7 +32,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Abstraction library for common MySQL/MariaDB DML operations with php-mysqli",
|
"description": "Abstraction library for common MySQL/MariaDB DML operations with php-mysqli",
|
||||||
"time": "2025-01-30T09:33:10+00:00"
|
"time": "2025-07-29T07:46:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vlw/xenum",
|
"name": "vlw/xenum",
|
||||||
|
@ -96,7 +40,7 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://codeberg.org/vlw/php-xenum",
|
"url": "https://codeberg.org/vlw/php-xenum",
|
||||||
"reference": "1c997a5574656b88a62f5ee160ee5a6439932a2f"
|
"reference": "ba3f43a9e2787bf938cfbfcb85ea87e5062df294"
|
||||||
},
|
},
|
||||||
"default-branch": true,
|
"default-branch": true,
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
@ -116,15 +60,13 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "PHP eXtended Enums. The missing quality-of-life features from PHP 8+ Enums",
|
"description": "PHP eXtended Enums. The missing quality-of-life features from PHP 8+ Enums",
|
||||||
"time": "2024-12-02T10:36:32+00:00"
|
"time": "2025-05-10T11:28:03+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": {
|
"stability-flags": {
|
||||||
"reflect/client": 20,
|
|
||||||
"reflect/plugin-rules": 20,
|
|
||||||
"vlw/mysql": 20,
|
"vlw/mysql": 20,
|
||||||
"vlw/xenum": 20
|
"vlw/xenum": 20
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use ReflectRules\Ruleset;
|
|
||||||
use Reflect\{Response, Path, Call};
|
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\About\LanguagesTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/API/Endpoints.php");
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/About/Languages.php");
|
|
||||||
|
|
||||||
class DELETE_AboutLanguages extends Database {
|
|
||||||
protected readonly Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function languages(): array {
|
|
||||||
$resp = (new Call(Endpoints::ABOUT_LANGUAGES->value))->get();
|
|
||||||
|
|
||||||
return array_column($resp->output(), LanguagesTable::ID->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete languages cache file if it exists
|
|
||||||
public function main(): Response {
|
|
||||||
$this->db->for(LanguagesTable::NAME);
|
|
||||||
|
|
||||||
foreach ($this->languages() as $language){
|
|
||||||
$this->db->delete([LanguagesTable::ID->value => $language]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Order;
|
|
||||||
use Reflect\{Response, Path, Call};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use const VLW\FORGEJO_UPDATE_CACHE_PARAM;
|
|
||||||
use VLW\Database\Tables\About\LanguagesTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/Consts.php");
|
|
||||||
require_once Path::root("src/API/Endpoints.php");
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/About/Languages.php");
|
|
||||||
|
|
||||||
class GET_AboutLanguages extends Database {
|
|
||||||
protected readonly Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->GET([
|
|
||||||
(new Rules(LanguagesTable::ID->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_VARCHAR),
|
|
||||||
|
|
||||||
(new Rules(LanguagesTable::BYTES->value))
|
|
||||||
->type(Type::NUMBER)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_UINT32),
|
|
||||||
|
|
||||||
(new Rules(FORGEJO_UPDATE_CACHE_PARAM))
|
|
||||||
->type(Type::BOOLEAN)
|
|
||||||
->default(false)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
// Refresh the language cache if param is set
|
|
||||||
if ($_GET[FORGEJO_UPDATE_CACHE_PARAM]) {
|
|
||||||
(new Call(Endpoints::ABOUT_LANGUAGES->value))->post();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->list(LanguagesTable::NAME, LanguagesTable::values(), [
|
|
||||||
LanguagesTable::BYTES->value => Order::DESC
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use ReflectRules\Ruleset;
|
|
||||||
use Reflect\{Response, Path, Call};
|
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\About\LanguagesTable;
|
|
||||||
use const VLW\{FORGEJO_ENDPOINT_USER, FORGEJO_ENDPOINT_SEARCH};
|
|
||||||
|
|
||||||
require_once Path::root("src/Consts.php");
|
|
||||||
require_once Path::root("src/API/Endpoints.php");
|
|
||||||
require_once Path::root("src/Database/Tables/About/Languages.php");
|
|
||||||
|
|
||||||
class POST_AboutLanguages extends Database {
|
|
||||||
protected readonly Ruleset $ruleset;
|
|
||||||
|
|
||||||
// Tally of all languages used in all configured repositories
|
|
||||||
private array $languages = [];
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch JSON from URL
|
|
||||||
private static function fetch_json(string $url): array {
|
|
||||||
return json_decode(file_get_contents($url), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch JSON from a Forgejo endpoint
|
|
||||||
private static function fetch_endpoint(string $endpoint): array {
|
|
||||||
$url = $_ENV["server_forgejo"]["base_url"] . $endpoint;
|
|
||||||
return self::fetch_json($url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write $this->languages to a JSON file
|
|
||||||
private function cache_languages(): void {
|
|
||||||
// Delete existing cache
|
|
||||||
(new Call(Endpoints::ABOUT_LANGUAGES->value))->delete();
|
|
||||||
|
|
||||||
$this->db->for(LanguagesTable::NAME);
|
|
||||||
|
|
||||||
foreach ($this->languages as $language => $bytes) {
|
|
||||||
$this->db->insert([
|
|
||||||
LanguagesTable::ID->value => $language,
|
|
||||||
LanguagesTable::BYTES->value => $bytes
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch and add languages to total from a fully-qualified Forgejo URL
|
|
||||||
private function add_repository_languages(string $url): void {
|
|
||||||
foreach(self::fetch_json($url) as $language => $bytes) {
|
|
||||||
// Create key for language if it doesn't exist
|
|
||||||
if (!array_key_exists($language, $this->languages)) {
|
|
||||||
$this->languages[$language] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add bytes to language in total
|
|
||||||
$this->languages[$language] += $bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tally languages from public repositories for user id
|
|
||||||
private function add_public_repositores(int $uid): bool {
|
|
||||||
$resp = self::fetch_endpoint(sprintf(FORGEJO_ENDPOINT_SEARCH, $uid));
|
|
||||||
|
|
||||||
// Bail out if request failed or if response indicated a problem
|
|
||||||
if (!$resp or $resp["ok"] === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add langauges for each public repository
|
|
||||||
foreach ($resp["data"] as $repo) {
|
|
||||||
$this->add_repository_languages($repo["languages_url"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add languages from all public repositories for profiles in config
|
|
||||||
private function add_repositories_from_config_profiles(): void {
|
|
||||||
foreach(explode(",", $_ENV["server_forgejo"]["scan_profiles"]) as $profile) {
|
|
||||||
// Resolve user data from username
|
|
||||||
$user = self::fetch_endpoint(sprintf(FORGEJO_ENDPOINT_USER, $profile));
|
|
||||||
$this->add_public_repositores($user["id"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
$this->add_repositories_from_config_profiles();
|
|
||||||
|
|
||||||
// Sort langauges bytes tally by largest in descending order
|
|
||||||
arsort($this->languages);
|
|
||||||
|
|
||||||
$this->cache_languages();
|
|
||||||
return new Response($this->languages);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Reflect\{Response, Path};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\Messages\MessagesTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Messages/Messages.php");
|
|
||||||
|
|
||||||
class POST_Messages extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->POST([
|
|
||||||
(new Rules(MessagesTable::EMAIL->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->max(255)
|
|
||||||
->default(null),
|
|
||||||
|
|
||||||
(new Rules(MessagesTable::MESSAGE->value))
|
|
||||||
->required()
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_TEXT)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
$_POST[MessagesTable::TIMESTAMP_CREATED->value] = time();
|
|
||||||
|
|
||||||
return $this->db->for(MessagesTable::NAME)->insert($_POST) === true
|
|
||||||
? new Response(null, 201)
|
|
||||||
: new Response("Failed to send message", 500);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Operators;
|
|
||||||
use Reflect\{Response, Path};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\Search\SearchTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Search/Search.php");
|
|
||||||
|
|
||||||
class DELETE_Search extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->POST([
|
|
||||||
(new Rules(SearchTable::ID->value))
|
|
||||||
->required()
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(2)
|
|
||||||
->max(parent::SIZE_VARCHAR)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
return $this->db->for(SearchTable::NAME)->delete($_POST) === true ? new Response() : new Response("", 500);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Operators;
|
|
||||||
use Reflect\{Response, Path, Call};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use const VLW\SEARCH_UPDATE_CACHE_PARM;
|
|
||||||
use VLW\Database\Tables\Search\{SearchTable, SearchCategoryEnum};
|
|
||||||
|
|
||||||
require_once Path::root("src/Consts.php");
|
|
||||||
require_once Path::root("src/API/Endpoints.php");
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Search/Search.php");
|
|
||||||
|
|
||||||
class GET_Search extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->GET([
|
|
||||||
(new Rules(SearchTable::QUERY->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(2)
|
|
||||||
->max(parent::SIZE_VARCHAR)
|
|
||||||
->default(null),
|
|
||||||
|
|
||||||
(new Rules(SearchTable::ID->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(10)
|
|
||||||
->default(null),
|
|
||||||
|
|
||||||
(new Rules(SearchTable::CATEGORY->value))
|
|
||||||
->type(Type::ENUM, SearchCategoryEnum::names())
|
|
||||||
->default(null),
|
|
||||||
|
|
||||||
(new Rules(SEARCH_UPDATE_CACHE_PARM))
|
|
||||||
->type(Type::BOOLEAN)
|
|
||||||
->default(false)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function get_query(): string {
|
|
||||||
preg_match_all("/[a-zA-Z0-9]+/", $_GET[SearchTable::QUERY->value], $matches);
|
|
||||||
|
|
||||||
return strtolower(implode("", $matches[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
// Freshen cache if update flag is set
|
|
||||||
if ($_GET[SEARCH_UPDATE_CACHE_PARM]) {
|
|
||||||
(new Call(Endpoints::SEARCH->value))->post();
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $this->db->for(SearchTable::NAME);
|
|
||||||
|
|
||||||
if ($_GET[SearchTable::ID->value]) {
|
|
||||||
$result = $result->where([SearchTable::ID->value => $_GET[SearchTable::ID->value]]);
|
|
||||||
} else if ($_GET[SearchTable::QUERY->value]) {
|
|
||||||
$query = self::get_query();
|
|
||||||
|
|
||||||
$filter = [
|
|
||||||
SearchTable::QUERY->value => [
|
|
||||||
Operators::LIKE->value => "%{$query}%"
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($_GET[SearchTable::CATEGORY->value]) {
|
|
||||||
$filter[SearchTable::CATEGORY->value] = $_GET[SearchTable::CATEGORY->value];
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $result->where($filter);
|
|
||||||
} else {
|
|
||||||
new Response([], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $result->select([
|
|
||||||
SearchTable::ID->value,
|
|
||||||
SearchTable::TITLE->value,
|
|
||||||
SearchTable::SUMMARY->value,
|
|
||||||
SearchTable::CATEGORY->value,
|
|
||||||
SearchTable::HREF->value
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $result->num_rows > 0
|
|
||||||
? new Response($result->fetch_all(MYSQLI_ASSOC))
|
|
||||||
: new Response([], 404);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Operators;
|
|
||||||
use Reflect\{Response, Path, Call};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use const VLW\SEARCH_QUERY_MAX_LENGTH;
|
|
||||||
use VLW\Database\Tables\Search\{SearchTable, SearchCategoryEnum};
|
|
||||||
use VLW\Database\Tables\Work\{WorkTable, ActionsTable};
|
|
||||||
|
|
||||||
require_once Path::root("src/Consts.php");
|
|
||||||
require_once Path::root("src/API/Endpoints.php");
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Work/Work.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Work/Actions.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Search/Search.php");
|
|
||||||
|
|
||||||
class POST_Search extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function truncate_string(string $input): string {
|
|
||||||
return substr($input, 0, SEARCH_QUERY_MAX_LENGTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function create_query(string $input): string {
|
|
||||||
preg_match_all("/[a-zA-Z0-9]+/", $input, $matches);
|
|
||||||
|
|
||||||
return self::truncate_string(strtolower(implode("", $matches[0])));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function index_work(): void {
|
|
||||||
foreach ((new Call(Endpoints::WORK->value))->get()->output() as $result) {
|
|
||||||
$query = self::create_query(implode("", array_values($result)), 0, SEARCH_QUERY_MAX_LENGTH);
|
|
||||||
|
|
||||||
// Get actions related to current result
|
|
||||||
$actions = (new Call(Endpoints::WORK_ACTIONS->value))->params([
|
|
||||||
ActionsTable::REF_WORK_ID->value => $result[WorkTable::ID->value]
|
|
||||||
])->get()->output();
|
|
||||||
|
|
||||||
$this->db->for(SearchTable::NAME)->insert([
|
|
||||||
SearchTable::QUERY->value => $query,
|
|
||||||
SearchTable::ID->value => crc32($query),
|
|
||||||
SearchTable::TITLE->value => self::truncate_string($result[WorkTable::TITLE->value]),
|
|
||||||
SearchTable::SUMMARY->value => self::truncate_string($result[WorkTable::SUMMARY->value]),
|
|
||||||
SearchTable::CATEGORY->value => SearchCategoryEnum::WORK->name,
|
|
||||||
// Use first action as link for search result
|
|
||||||
SearchTable::HREF->value => $actions
|
|
||||||
? self::truncate_string($actions[0][ActionsTable::HREF->value])
|
|
||||||
: null
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
// Truncate existing cache
|
|
||||||
(new Call(Endpoints::SEARCH->value))->delete();
|
|
||||||
|
|
||||||
$this->index_work();
|
|
||||||
|
|
||||||
return $result->num_rows > 0
|
|
||||||
? new Response($result->fetch_all(MYSQLI_ASSOC))
|
|
||||||
: new Response([], 404);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Order;
|
|
||||||
use Reflect\{Response, Path};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\Work\WorkTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Work/Work.php");
|
|
||||||
|
|
||||||
class GET_Work extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->GET([
|
|
||||||
(new Rules(WorkTable::ID->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_VARCHAR),
|
|
||||||
|
|
||||||
(new Rules(WorkTable::TITLE->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->max(parent::SIZE_VARCHAR),
|
|
||||||
|
|
||||||
(new Rules(WorkTable::SUMMARY->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->max(parent::SIZE_TEXT),
|
|
||||||
|
|
||||||
(new Rules(WorkTable::CREATED->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_VARCHAR)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
return $this->list(WorkTable::NAME, WorkTable::values(), [
|
|
||||||
WorkTable::CREATED->value => Order::DESC
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Order;
|
|
||||||
use Reflect\{Response, Path};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\Work\ActionsTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Work/Actions.php");
|
|
||||||
|
|
||||||
class GET_WorkActions extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->GET([
|
|
||||||
(new Rules(ActionsTable::REF_WORK_ID->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_VARCHAR)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
return $this->list(ActionsTable::NAME, ActionsTable::values(), [
|
|
||||||
ActionsTable::ORDER_IDX->value => Order::DESC
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Reflect\{Response, Path};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\Work\{TagsTable, TagsLabelEnum};
|
|
||||||
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Work/Tags.php");
|
|
||||||
|
|
||||||
class GET_WorkTags extends Database {
|
|
||||||
private Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->GET([
|
|
||||||
(new Rules(TagsTable::REF_WORK_ID->value))
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_VARCHAR),
|
|
||||||
|
|
||||||
(new Rules(TagsTable::LABEL->value))
|
|
||||||
->type(Type::ENUM, TagsLabelEnum::names())
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
return $this->list(TagsTable::NAME, TagsTable::values());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use vlw\MySQL\Order;
|
|
||||||
use Reflect\{Response, Path};
|
|
||||||
use ReflectRules\{Ruleset, Rules, Type};
|
|
||||||
|
|
||||||
use VLW\Database\Database;
|
|
||||||
use VLW\Database\Tables\Work\TimelineTable;
|
|
||||||
|
|
||||||
require_once Path::root("src/Database/Database.php");
|
|
||||||
require_once Path::root("src/Database/Tables/Work/Timeline.php");
|
|
||||||
|
|
||||||
class GET_WorkTimeline extends Database {
|
|
||||||
protected Ruleset $ruleset;
|
|
||||||
|
|
||||||
public function __construct() {
|
|
||||||
$this->ruleset = new Ruleset(strict: true);
|
|
||||||
|
|
||||||
$this->ruleset->GET([
|
|
||||||
(new Rules(TimelineTable::REF_WORK_ID->value))
|
|
||||||
->type(Type::STRING)
|
|
||||||
->min(1)
|
|
||||||
->max(parent::SIZE_VARCHAR),
|
|
||||||
|
|
||||||
(new Rules(TimelineTable::YEAR->value))
|
|
||||||
->type(Type::NUMBER)
|
|
||||||
->min(0)
|
|
||||||
->max(parent::SIZE_UINT16),
|
|
||||||
|
|
||||||
(new Rules(TimelineTable::MONTH->value))
|
|
||||||
->type(Type::NUMBER)
|
|
||||||
->min(0)
|
|
||||||
->max(parent::SIZE_UINT8),
|
|
||||||
|
|
||||||
(new Rules(TimelineTable::DAY->value))
|
|
||||||
->type(Type::NUMBER)
|
|
||||||
->min(0)
|
|
||||||
->max(parent::SIZE_UINT8)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ruleset->validate_or_exit();
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function main(): Response {
|
|
||||||
return $this->list(TimelineTable::NAME, TimelineTable::values(), [
|
|
||||||
TimelineTable::YEAR->value => Order::DESC,
|
|
||||||
TimelineTable::MONTH->value => Order::DESC,
|
|
||||||
TimelineTable::DAY->value => Order::DESC
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
257
install.sh
257
install.sh
|
@ -1,10 +1,251 @@
|
||||||
# Install dependencies
|
#!/bin/bash
|
||||||
composer install --optimize-autoloader
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# (Re)create public NPM modules folder
|
# Define constants
|
||||||
rm -r public/assets/js/modules/npm
|
DB_VLW="vlw"
|
||||||
mkdir public/assets/js/modules/npm
|
DB_API="vlw_api"
|
||||||
|
|
||||||
# Create link to Elevent MJS from public JS modules folder
|
# Initialize variables
|
||||||
ln -sr node_modules/elevent/src/Elevent.mjs public/assets/js/modules/npm/Elevent.mjs
|
cwd=""
|
||||||
|
database_app_host=""
|
||||||
|
database_app_user=""
|
||||||
|
database_seed_host=""
|
||||||
|
database_seed_user=""
|
||||||
|
database_app_password=""
|
||||||
|
database_seed_password=""
|
||||||
|
|
||||||
|
echo_err() {
|
||||||
|
echo "!! -> $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure we have all the system packages we need to proceed with the installation
|
||||||
|
check_sys_depend() {
|
||||||
|
local valid=true
|
||||||
|
local packages=("git" "composer")
|
||||||
|
|
||||||
|
for package in "${packages[@]}" ; do
|
||||||
|
if ! dpkg -l | grep -qw "ii ${package}" ; then
|
||||||
|
echo_err "Package '${package}' is not installed."
|
||||||
|
valid=false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Bail out if any required package is missing
|
||||||
|
if [ "$valid" = false ] ; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_vegvisir() {
|
||||||
|
echo
|
||||||
|
echo "Installing Vegvisir into '$cwd/vegvisir'"
|
||||||
|
|
||||||
|
curl -fsSL https://codeberg.org/vegvisir/install/raw/branch/master/install.sh | bash -s -- --install=n --overwrite=y --example=n --dir="$cwd"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_reflect() {
|
||||||
|
echo
|
||||||
|
echo "Installing Reflect into '$cwd/reflect'"
|
||||||
|
|
||||||
|
curl -fsSL https://codeberg.org/reflect/install/raw/branch/master/install.sh | bash -s -- --install=n --overwrite=y --seed=n --dir="$cwd" --endpoints="api" --host="$database_app_host" --user="$database_app_user" --password="$database_app_password" --db="$DB_API"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_vlw() {
|
||||||
|
composer install --classmap-authoritative
|
||||||
|
}
|
||||||
|
|
||||||
|
seed_databases() {
|
||||||
|
echo
|
||||||
|
echo "-- Database seeding --"
|
||||||
|
echo "We're now going to seed the databases '$DB_VLW' and '$DB_API' with default data"
|
||||||
|
echo "- Make sure that both databases exist and are empty"
|
||||||
|
echo "- Your credentials for this user '$(whoami)' might be different from your app credentials (php-mysql)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Database seed hostname
|
||||||
|
echo "Enter the full hostname of your MySQL/MariaDB server to use in this script for seeding."
|
||||||
|
read -p "Press enter to use the same host as the app credentials [$database_app_host]: " database_seed_host </dev/tty
|
||||||
|
|
||||||
|
# Use the same database host as the app configuration
|
||||||
|
if [[ "$database_seed_host" == "" ]] ; then
|
||||||
|
database_seed_host=$database_app_host
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Database seed user
|
||||||
|
echo
|
||||||
|
echo "Enter the username for your MySQL/MariaDB server to use in this script for seeding."
|
||||||
|
read -p "Press enter to use the same host as the app credentials [$database_app_user]: " database_seed_user </dev/tty
|
||||||
|
|
||||||
|
# Use the same database user as the app configuration
|
||||||
|
if [[ "$database_seed_user" == "" ]] ; then
|
||||||
|
database_seed_user=$database_app_user
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Database seed password
|
||||||
|
echo
|
||||||
|
echo "Enter the password for your MySQL/MariaDB server to use in this script for seeding."
|
||||||
|
echo "Enter 'null' to disable password authentication"
|
||||||
|
read -p "Press enter to use the same password as the app credentials [<database_password>]: " database_seed_password </dev/tty
|
||||||
|
|
||||||
|
# Use the same database password as the app configuration
|
||||||
|
if [[ "$database_seed_password" == "" ]] ; then
|
||||||
|
database_seed_password=$database_app_password
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Seed the main database
|
||||||
|
mysql -h "$database_seed_host" -u "$database_seed_user" --password="$database_seed_password" $DB_VLW < src/Database/Seeds/vlw.sql
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo_err "Installation aborted: MySQL error"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Seed the API database
|
||||||
|
mysql -h "$database_seed_host" -u "$database_seed_user" --password="$database_seed_password" $DB_API < src/Database/Seeds/api.sql
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo_err "Installation aborted: MySQL error"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_vlw() {
|
||||||
|
local config_password=""
|
||||||
|
|
||||||
|
local config_available=""
|
||||||
|
local config_available_to=""
|
||||||
|
local config_available_from=""
|
||||||
|
local config_available_average=""
|
||||||
|
local config_available_timezone=""
|
||||||
|
|
||||||
|
local config_forgejo=""
|
||||||
|
local config_forgejo_url=""
|
||||||
|
local config_forgejo_profiles=""
|
||||||
|
|
||||||
|
# A configuration file already exists
|
||||||
|
if [[ -f ".env.ini" ]] ; then
|
||||||
|
echo
|
||||||
|
echo "A configuration file already exists at: ${cwd}/.env.ini"
|
||||||
|
read -p "Do you want to overwrite this file? (y/n): " overwrite </dev/tty
|
||||||
|
|
||||||
|
# Remove existing configuration file or abort
|
||||||
|
if [[ "$overwrite" == "y" || "$overwrite" == "Y" ]] ; then
|
||||||
|
echo "Removing existing configuration and proceeding with the installation in ${cwd}..."
|
||||||
|
rm .env.ini
|
||||||
|
else
|
||||||
|
echo_err "Installation aborted: Configuration file already exists"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "-- Database configuration --"
|
||||||
|
read -p "Enter the full hostname of your MySQL/MariaDB server (php-mysql): " database_app_host </dev/tty
|
||||||
|
read -p "Enter the app username for your MySQL/MariaDB server (php-mysql): " database_app_user </dev/tty
|
||||||
|
read -p "Enter the app password for your MySQL/MariaDB server (php-mysql): " config_password </dev/tty
|
||||||
|
|
||||||
|
# Coerce empty input as null keyword for later configurations
|
||||||
|
if [[ "$config_password" == "" ]] ; then
|
||||||
|
database_app_password="null"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
read -p "(Optional) Would you like to configure time available settings? (y/n): " config_available </dev/tty
|
||||||
|
|
||||||
|
# Check the user's response
|
||||||
|
if [[ "$config_available" == "n" || "$config_available" == "N" ]]; then
|
||||||
|
config_available_to=0
|
||||||
|
config_available_from=0
|
||||||
|
config_available_average=0
|
||||||
|
config_available_timezone="Europe/Stockholm"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ -n "$config_available_timezone" ]]; then
|
||||||
|
read -p "Enter your timezone in IANA Time Zone Database Format ('Europe/Stockholm' for example): " config_available_timezone </dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ -n "$config_available_from" ]]; then
|
||||||
|
read -p "Enter time available from hour (24-hour format): " config_available_from </dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ -n "$config_available_to" ]]; then
|
||||||
|
read -p "Enter time available to hour (24-hour format): " config_available_to </dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ -n "$config_available_average" ]]; then
|
||||||
|
read -p "Enter average reply time in hours: " config_available_average </dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
read -p "(Optional) Would you like to configure Forgejo language updates? (y/n): " config_forgejo </dev/tty
|
||||||
|
|
||||||
|
# Check the user's response
|
||||||
|
if [[ "$config_forgejo" == "n" || "$config_forgejo" == "N" ]]; then
|
||||||
|
config_forgejo_url="https://git.vlw.se"
|
||||||
|
config_forgejo_profiles="vlw,vegvisir,reflect"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ -n "$config_forgejo_url" ]]; then
|
||||||
|
read -p "Enter a hostname to a Forgejo instance ('https://git.vlw.se' for example): " config_forgejo_url </dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ -n "$config_forgejo_profiles" ]]; then
|
||||||
|
read -p "Enter a comma seperated list of Forgejo profiles to scan ('vlw,vegvisir,reflect' for example): " config_forgejo_profiles </dev/tty
|
||||||
|
fi
|
||||||
|
|
||||||
|
local config=(
|
||||||
|
"; This config file was generated automatically by ./install.sh"
|
||||||
|
"; Refer to '.env.example.ini' for more information"
|
||||||
|
"[mariadb]"
|
||||||
|
"host = '$database_app_host'"
|
||||||
|
"user = '$database_app_user'"
|
||||||
|
"pass = '$config_password'"
|
||||||
|
"db = '$DB_VLW'"
|
||||||
|
"[config_time_available]"
|
||||||
|
"time_zone = '$config_available_timezone'"
|
||||||
|
"available_to_hour = '$config_available_to'"
|
||||||
|
"reply_average_hours = '$config_available_average'"
|
||||||
|
"available_from_hour = '$config_available_from'"
|
||||||
|
"[service_forgejo]"
|
||||||
|
"url = '$config_forgejo_url'"
|
||||||
|
"profiles = '$config_forgejo_profiles'"
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in "${config[@]}" ; do
|
||||||
|
echo "${line}" >> .env.ini
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# Get the current working directory
|
||||||
|
cwd=$(pwd)
|
||||||
|
|
||||||
|
check_sys_depend
|
||||||
|
|
||||||
|
configure_vlw
|
||||||
|
seed_databases
|
||||||
|
|
||||||
|
install_vlw
|
||||||
|
install_vegvisir
|
||||||
|
install_reflect
|
||||||
|
|
||||||
|
echo "-- Success --"
|
||||||
|
echo "vlw.se has been installed! :)"
|
||||||
|
echo "- Point all traffic to your web server to '${cwd}/vegvisir/public/index.php'"
|
||||||
|
echo "- Point all traffic to your REST API server to '${cwd}/reflect/public/index.php'"
|
||||||
|
echo "-------------"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt the user for confirmation
|
||||||
|
echo
|
||||||
|
echo "-- Installing vlw.se --"
|
||||||
|
echo "You are currently in: $(pwd)"
|
||||||
|
read -p "Do you want to proceed with the installation in this directory? (y/n): " choice </dev/tty
|
||||||
|
|
||||||
|
# Check the user's response
|
||||||
|
if [[ "$choice" == "y" || "$choice" == "Y" ]] ; then
|
||||||
|
echo "Proceeding with the installation in $(pwd)..."
|
||||||
|
main
|
||||||
|
else
|
||||||
|
echo "Installation aborted."
|
||||||
|
fi
|
||||||
|
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"name": "vlw.se",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"elevent": "^1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/elevent": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/elevent/-/elevent-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-ks5LBUBTg4Bpfmj99OcFAzuDGzBRDEZhTyxmq/Y3RbsdBQ4JCaIUYB0M15OBvBWgIn1BnCo4WCSmw0/YbCJliw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"elevent": "^1.0.2"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use VLW\Database\Models\About\Language;
|
use VLW\Database\Models\Coffee\Coffee;
|
||||||
use const VLW\{
|
use VLW\Database\Models\Languages\Language;
|
||||||
FORGEJO_HREF,
|
|
||||||
FORGEJO_SI_BYTE_MULTIPLE,
|
|
||||||
DEFAULT_BUTTON_ICON
|
|
||||||
};
|
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
require_once VV::root("src/Database/Models/Coffee/Coffee.php");
|
||||||
require_once VV::root("src/Database/Models/About/Language.php");
|
require_once VV::root("src/Database/Models/Languages/Language.php");
|
||||||
|
|
||||||
|
const FORGEJO = "https://git.vlw.se/explore/repos?language=";
|
||||||
|
const SI_BYTE_MULTIPLE = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
|
||||||
$languages = new class extends Language {
|
$languages = new class extends Language {
|
||||||
private readonly int $total_bytes;
|
private readonly int $total_bytes;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->total_bytes = array_sum(array_map(fn(Language $language): int => $language->bytes(), parent::all()));
|
$this->total_bytes = array_sum(array_map(fn(Language $language): int => $language->bytes, parent::all()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function percent(Language $language, int $mode = PHP_ROUND_HALF_UP): int {
|
public function percent(Language $language, int $mode = PHP_ROUND_HALF_UP): int {
|
||||||
return round(($language->bytes() / $this->total_bytes) * 100, 0, $mode);
|
return round(($language->bytes / $this->total_bytes) * 100, 0, $mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function percent_string(Language $language): string {
|
public function percent_string(Language $language): string {
|
||||||
|
@ -27,31 +26,52 @@
|
||||||
|
|
||||||
public function bytes_si_string(Language $language): string {
|
public function bytes_si_string(Language $language): string {
|
||||||
// Calculate factor for unit
|
// Calculate factor for unit
|
||||||
$factor = floor((strlen($language->bytes()) - 1) / 3);
|
$factor = floor((strlen($language->bytes) - 1) / 3);
|
||||||
// Divide by radix 10
|
// Divide by radix 10
|
||||||
$format = $language->bytes() / pow(1000, $factor);
|
$format = $language->bytes / pow(1000, $factor);
|
||||||
|
|
||||||
return round($format) . " " . FORGEJO_SI_BYTE_MULTIPLE[$factor];
|
return round($format) . " " . SI_BYTE_MULTIPLE[$factor];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
$coffee = new class extends Coffee {
|
||||||
|
public readonly int $count_week;
|
||||||
|
public readonly int $count_week_average;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->count_week = parent::count_week();
|
||||||
|
$this->count_week_average = parent::count_week_average();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function week_average_string(): string {
|
||||||
|
$diff = $this->count_week - $this->count_week_average;
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
$diff < 0 => "less than",
|
||||||
|
$diff === 0 => "the same as",
|
||||||
|
$diff > 0 => "more than"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<style><?= VV::css("public/assets/css/pages/about") ?></style>
|
<style><?= VV::css("public/assets/css/pages/about") ?></style>
|
||||||
<section class="intro">
|
<section class="intro">
|
||||||
<h2 aria-hidden="true">Hi, I"m</h2>
|
<h2 aria-hidden="true">Hi, I'm</h2>
|
||||||
<h1>Victor Westerlund</h1>
|
<h1>Victor Westerlund</h1>
|
||||||
</section>
|
</section>
|
||||||
<hr aria-hidden="true">
|
<hr aria-hidden="true">
|
||||||
<section class="about">
|
<section class="about">
|
||||||
<p>I​'m a full-stack web developer from Sweden.</p>
|
<p>I​'m a full-stack web developer from Sweden, and welcome to my little personal corner of the Internet!</p>
|
||||||
<p>I used to list the <programming/markup/command/whatever>-languages here that I use the most and order them by guesstimating how much I use each one. But then I thought it would be better to just show you instead using this chart that <a href="https://git.vlw.se/config/vlw.se">automatically pulls the total bytes</a> for each language from my public mirrors and sources on <a href="https://git.vlw.se/vlw">Forgejo</a>.</p>
|
<p>My coding happens almost exclusivly in <a href="https://github.com/coder/code-server">code-server</a>, which is a fork of VSCode that runs entirely in the browser. I keep my development environment tucked away in a lightweight Debian VA that I can tote around to whatever host machine I happen to work on. I also keep an ephemeral Debian Live ISO ready which boots into a VM RAM disk where I can mess around without fear or breaking things or try new software.</p>
|
||||||
|
<p>I used to list the <programming/markup/command/whatever>-languages here that I use the most and order them by guesstimating how much I use each one. But then I thought it would be better to just show you instead using this chart that automatically pulls the total bytes for each language from my <a href="https://git.vlw.se/explore/repos">public repos on Forgejo</a>.</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="languages">
|
<section class="languages">
|
||||||
<stacked-bar-chart>
|
<stacked-bar-chart>
|
||||||
|
|
||||||
<?php foreach ($languages::all() as $language): ?>
|
<?php foreach ($languages::all() as $language): ?>
|
||||||
<a href="<?= FORGEJO_HREF . $language->id ?>" target="_blank"><chart-segment style="--size:<?= $languages->percent($language) ?>%;" data-lang="<?= $language->id ?>" data-bytes="<?= $language->bytes() ?>">
|
<a href="<?= FORGEJO . $language->name ?>" target="_blank"><chart-segment style="--size:<?= $languages->percent($language) ?>%;" data-lang="<?= $language->name ?>" data-bytes="<?= $language->bytes ?>">
|
||||||
<span data-hover><strong><?= $languages->percent_string($language) ?> <?= $language->id ?></strong><br>(<?= $language->bytes() ?> bytes)</span>
|
<span data-hover><strong><?= $languages->percent_string($language) ?> <?= $language->name ?></strong><br>(<?= $language->bytes ?> bytes)</span>
|
||||||
</chart-segment></a>
|
</chart-segment></a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
@ -59,11 +79,11 @@
|
||||||
<languages-list>
|
<languages-list>
|
||||||
|
|
||||||
<?php foreach ($languages::all() as $language): ?>
|
<?php foreach ($languages::all() as $language): ?>
|
||||||
<a href="<?= FORGEJO_HREF . $language->id ?>"><button data-lang="<?= $language->id ?>" class="inline">
|
<a href="<?= FORGEJO . $language->name ?>"><button data-lang="<?= $language->name ?>" class="inline">
|
||||||
<p><?= $languages->percent_string($language) ?></p>
|
<p><?= $languages->percent_string($language) ?></p>
|
||||||
<p class="lang"><?= $language->id ?></p>
|
<p class="lang"><?= $language->name ?></p>
|
||||||
<p><?= $languages->bytes_si_string($language) ?></p>
|
<p><?= $languages->bytes_si_string($language) ?></p>
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
@ -71,8 +91,8 @@
|
||||||
<stacked-bar-chart>
|
<stacked-bar-chart>
|
||||||
|
|
||||||
<?php foreach ($languages::all() as $language): ?>
|
<?php foreach ($languages::all() as $language): ?>
|
||||||
<a href="<?= FORGEJO_HREF . $language->id ?>" target="_blank"><chart-segment style="--size:<?= $languages->percent($language) ?>%;" data-lang="<?= $language->id ?>" data-bytes="<?= $language->bytes() ?>">
|
<a href="<?= FORGEJO . $language->name ?>" target="_blank"><chart-segment style="--size:<?= $languages->percent($language) ?>%;" data-lang="<?= $language->name ?>" data-bytes="<?= $language->bytes ?>">
|
||||||
<span data-hover><strong><?= $languages->percent_string($language) ?> <?= $language->id ?></strong><br>(<?= $language->bytes() ?> bytes)</span>
|
<span data-hover><strong><?= $languages->percent_string($language) ?> <?= $language->name ?></strong><br>(<?= $language->bytes ?> bytes)</span>
|
||||||
</chart-segment></a>
|
</chart-segment></a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
@ -81,30 +101,19 @@
|
||||||
</section>
|
</section>
|
||||||
<section class="about">
|
<section class="about">
|
||||||
<h2>This website</h2>
|
<h2>This website</h2>
|
||||||
<p>This site and all of its components are <a href="https://codeberg.org/vlw/vlw.se">100% free and open source software</a>. The website is designed and built by me from the ground up using my <a href="https://vegvisir.vlw.se">web</a> and <a href="https://reflect.vlw.se">API</a> frameworks as the foundation. You will find no cookies or trackers here. The only information I have about you is your public IP-address and which resources on this site your browser requests. None of this data is used for any kind of analytics.</p>
|
<p>This site and all of its components, including texts and graphics have been created by me and are all <a href="https://codeberg.org/vlw/vlw.se">100% free and open source</a>. Feel free to use anything you see on this website in your own projects as long as it's under the same GNU GPLv3-or-later license. The website is designed by me on top of my own <a href="https://vegvisir.vlw.se">web framework</a> and <a href="https://reflect.vlw.se">API framework</a>.</p>
|
||||||
<p><a href="https://srv.vlw.se"><i>See detailed information about all servers/services on this domain</i></a></p>
|
<p>You won't find any cookies or trackers on this site! The only information I have about you are in the standard NGINX access and error logs, which get overwritten automatically after some time.</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="about">
|
<section class="about">
|
||||||
<h2>Personal</h2>
|
<h2>Personal</h2>
|
||||||
<p>Coffee, of course.. and..</p>
|
<p>One thing that most people know about me is that I like coffee.. lots of coffee. In fact, I've had <?= $coffee->count_week ?> cup<?= $coffee->count_week === 1 ? "" : "s" ?> of coffee in the last 7 days! That's <?= $coffee->week_average_string() ?> my average of <?= $coffee->count_week_average ?> per week, impressive! Even though you just read that.. I don't consider myself <i>too much</i> of a coffee snob! As long as it's dark roast and warm, I'm probably happy to have it.</p>
|
||||||
<p>At times, I become a true, amateur, armchair detective for a <span class="interests">variety of your typical-nerdy topics that I find interesting</span>. And will spend a disproportionate to real-world-personal-use amount of time reading about that stuff too.</p>
|
<p>At times, I become a true, amateur, armchair detective for a <span class="interests">variety of your typical-nerdy topics that I find interesting</span> and you can bet I spend way more time reading about those things than I will ever have use for in life.</p>
|
||||||
<p>Another silent passion of mine that comes out every few years is building computers and fiddling with weird networking stuff.</p>
|
<p>Another silent passion of mine that comes out every few years is building computers and fiddling with networking stuff.</p>
|
||||||
<p>And then of course I don't mind some occational gaming, and watching movies and TV-series.</p>
|
|
||||||
</section>
|
|
||||||
<section class="about">
|
|
||||||
<h2>Projects</h2>
|
|
||||||
<p>Here are some projects I'm working on right now:</p>
|
|
||||||
<p>* <a href="https://vegvisir.vlw.se">Vegvisir</a>: A web navigation framework for PHP.</p>
|
|
||||||
<p>* <a href="https://reflect.vlw.se">Reflect</a>: A REST API framework for PHP developers.</p>
|
|
||||||
<p>There is more stuff on my <a href="work">works page</a> and even more stuff on <a href="https://codeberg.org/vlw">my Codeberg profile</a>.</p>
|
|
||||||
<p><a href="https://git.vlw.se/vlw"><i>and even EVEN more stuff on my Forgejo</i></a></p>
|
|
||||||
</section>
|
</section>
|
||||||
<hr>
|
<hr>
|
||||||
<section class="about">
|
<section class="about">
|
||||||
<h3>GitHub</h3>
|
<h3>GitHub</h3>
|
||||||
<p>I have <a href="https://giveupgithub.com" target="_blank" rel="noopener noreferer">given up GitHub</a> for their increasing number of injustices againts its users, last betrayal being GitHub's for-profit "Copilot" product which was illegaly trained on copylefted software on its platform.</p>
|
<p>I have <a href="https://giveupgithub.com" target="_blank" rel="noopener noreferer">given up GitHub</a> and moved most of my free software to <a href="https://codeberg.org/vlw">Codeberg</a>. You can still find my <a href="https://github.com/VictorWesterlund">GitHub profile here</a> but I don't use it for source control of my projects anymore.</p>
|
||||||
<p>I signed up and started using GitHub before I became aware of how opressive to to its users and deceptive their business model is. I wasn't aware of the situation.</p>
|
|
||||||
<p>While I am a bit skeptical to do this in case history repeats itself; [most of] <a href="https://codeberg.org/vlw">my work is now on Codeberg</a> instead. Unfortunately some things like old pull-requests, issues, and branch archives can not be migrated completely.</p>
|
|
||||||
</section>
|
</section>
|
||||||
<hr>
|
<hr>
|
||||||
<section>
|
<section>
|
||||||
|
@ -125,6 +134,7 @@
|
||||||
<p>engineering</p>
|
<p>engineering</p>
|
||||||
<p>photography</p>
|
<p>photography</p>
|
||||||
<p>videography</p>
|
<p>videography</p>
|
||||||
<p>ISO 8601</p>
|
<p>RFC 3339</p>
|
||||||
|
<p>digital archiving</p>
|
||||||
</div>
|
</div>
|
||||||
<script type="module"><?= VV::js("public/assets/js/pages/about") ?></script>
|
<script><?= VV::js("public/assets/js/pages/about") ?></script>
|
|
@ -1,36 +0,0 @@
|
||||||
<style><?= VV::css("public/assets/css/pages/about/battlestation-retired") ?></style>
|
|
||||||
<section class="title">
|
|
||||||
<h1>Retired components</h1>
|
|
||||||
<p>I'd be happy to send you any component that you find here for "free". The only thing I ask in return is that you pay for shipping.</p>
|
|
||||||
<p>This page is still a work-in-progress. You can use my API to get a list of retired components by hardware category for now.</p>
|
|
||||||
</section>
|
|
||||||
<section class="actions">
|
|
||||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/chassis?is_retired=true" target="_blank">
|
|
||||||
<button class="inline">Cases (API)</button>
|
|
||||||
</a>
|
|
||||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/cpu?is_retired=true" target="_blank">
|
|
||||||
<button class="inline">CPUs (API)</button>
|
|
||||||
</a>
|
|
||||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/gpu?is_retired=true" target="_blank">
|
|
||||||
<button class="inline">GPUs (API)</button>
|
|
||||||
</a>
|
|
||||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/mb?is_retired=true" target="_blank">
|
|
||||||
<button class="inline">Motherboards (API)</button>
|
|
||||||
</a>
|
|
||||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/psu?is_retired=true" target="_blank">
|
|
||||||
<button class="inline">PSUs (API)</button>
|
|
||||||
</a>
|
|
||||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/storage?is_retired=true" target="_blank">
|
|
||||||
<button class="inline">Storage (API)</button>
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
<section class="title">
|
|
||||||
<h2>Found something you like?</h2>
|
|
||||||
<p>Please note; I can't guarantee the thing you want will work as expected, or work at all! But I will test the compontent for you if I still have means at hand to do so.</p>
|
|
||||||
</section>
|
|
||||||
<section class="actions">
|
|
||||||
<a href="/contact">
|
|
||||||
<button class="inline solid">Contact me</button>
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
<script><?= VV::js("public/assets/js/pages/about/battlestation-retired") ?></script>
|
|
|
@ -1,496 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Vegvisir\Path;
|
|
||||||
|
|
||||||
use VLW\Client\API;
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
|
|
||||||
use VLW\Database\Tables\Battlestation\{
|
|
||||||
MbTable,
|
|
||||||
CpuTable,
|
|
||||||
GpuTable,
|
|
||||||
PsuTable,
|
|
||||||
DramTable,
|
|
||||||
StorageTable,
|
|
||||||
ChassisTable
|
|
||||||
};
|
|
||||||
use VLW\Database\Tables\Battlestation\Config\{
|
|
||||||
MbPsuTable,
|
|
||||||
MbGpuTable,
|
|
||||||
MbDramTable,
|
|
||||||
ConfigModel,
|
|
||||||
MbStorageTable,
|
|
||||||
ChassisMbTable,
|
|
||||||
MbCpuCoolerModel,
|
|
||||||
MbStorageSlotFormfactorEnum
|
|
||||||
};
|
|
||||||
|
|
||||||
require_once VV::root("src/client/API.php");
|
|
||||||
require_once VV::root("api/src/Endpoints.php");
|
|
||||||
|
|
||||||
// Load hardware database models
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Mb.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Cpu.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Gpu.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Psu.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Dram.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Storage.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Chassis.php");
|
|
||||||
|
|
||||||
// Load hardware config database models
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbPsu.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbGpu.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbDram.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/Config.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbStorage.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/ChassisMb.php");
|
|
||||||
require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbCpuCooler.php");
|
|
||||||
|
|
||||||
const GIGA = 0x3B9ACA00;
|
|
||||||
const MEGA = 0xF4240;
|
|
||||||
|
|
||||||
// Connect to VLW API
|
|
||||||
$api = new API();
|
|
||||||
|
|
||||||
$config = $api->call(Endpoints::BATTLESTATION->value)->get();
|
|
||||||
|
|
||||||
?>
|
|
||||||
<style><?= VV::css("public/assets/css/pages/about/battlestation") ?></style>
|
|
||||||
<?php if ($config->ok): ?>
|
|
||||||
<section class="title">
|
|
||||||
<h1>Battle­stations</h1>
|
|
||||||
<p>I'd be happy to send you, dear reader, any component that you find here for "free" that hasn't been retired yet. The only thing I ask in return is that you pay for shipping.</p>
|
|
||||||
<p>I can't guarantee the thing you want will work as expected, or work at all! But I will test the compontent for you if I still have means at hand to do so.</p>
|
|
||||||
<div>
|
|
||||||
<a href="/about/battlestation-retired"><button class="inline solid">Retired components</button></a>
|
|
||||||
<a href="/contact"><button class="inline">Contact me</button></a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<?php foreach ($config->json() as $config): ?>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
|
|
||||||
// Get motherboard details by ref_mb_id from config
|
|
||||||
$motherboard = $api->call(Endpoints::BATTLESTATION_MB->value)->params([
|
|
||||||
MbTable::ID->value => $config[ChassisMbTable::REF_MB_ID->value]
|
|
||||||
])->get()->json()[0];
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<section class="heading">
|
|
||||||
<h1><?= $config[ConfigModel::FRIENDLY_NAME->value] ?? "Lucious" ?></h1>
|
|
||||||
<p>This rig was built: <?= date(API::DATE_FORMAT, $config[ConfigModel::DATE_BUILT->value]) ?></p>
|
|
||||||
</section>
|
|
||||||
<section class="config"
|
|
||||||
data-mb="1"
|
|
||||||
data-cpu="<?= count($motherboard["cpus"]) ?>"
|
|
||||||
data-psu="<?= count($motherboard["psus"]) ?>"
|
|
||||||
data-gpu="<?= count($motherboard["gpus"]) ?>"
|
|
||||||
data-dram="<?= count($motherboard["dram"]) ?>"
|
|
||||||
data-case="<?= count($motherboard["chassis"]) ?>"
|
|
||||||
data-drives-mdottwo="<?= count(array_keys(array_column($motherboard["storage"], MbStorageTable::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::MDOTTWO->value)) ?>"
|
|
||||||
data-drives-twodotfive="<?= count(array_keys(array_column($motherboard["storage"], MbStorageTable::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::TWODOTFIVE->value)) ?>"
|
|
||||||
data-drives-threedotfive="<?= count(array_keys(array_column($motherboard["storage"], MbStorageTable::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::THREEDOTFIVE->value)) ?>"
|
|
||||||
>
|
|
||||||
<?= VV::embed("public/assets/media/battlestation.svg") ?>
|
|
||||||
<div class="specs">
|
|
||||||
|
|
||||||
<?php // Show motherboard details ?>
|
|
||||||
<?php if ($motherboard): ?>
|
|
||||||
<div data-target="mb" class="spec">
|
|
||||||
<p>Motherboard</p>
|
|
||||||
<h3><?= $motherboard[MbTable::VENDOR_NAME->value] ?> <span><?= $motherboard[MbTable::VENDOR_MODEL->value] ?></span></h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Formfactor</label>
|
|
||||||
<p><?= $motherboard[MbTable::FORMFACTOR->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $motherboard[MbTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $motherboard[MbTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>LAN</label>
|
|
||||||
<p><?= $motherboard[MbTable::NETWORK_ETHERNET->value] ?? "No LAN" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>WLAN</label>
|
|
||||||
<p><?= $motherboard[MbTable::NETWORK_WLAN->value] ?? "No WLAN" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Bluetooth</label>
|
|
||||||
<p><?= $motherboard[MbTable::NETWORK_BLUETOOTH->value] ?? "No Bluetooth" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $motherboard[MbTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($motherboard[MbTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php // List all cases (lol) ?>
|
|
||||||
<?php foreach ($motherboard["chassis"] as $mb_chassis): ?>
|
|
||||||
|
|
||||||
<?php // Get case details from endpoint by id ?>
|
|
||||||
<?php $case = $api->call(Endpoints::BATTLESTATION_CHASSIS->value)->params([
|
|
||||||
ChassisTable::ID->value => $mb_chassis[ChassisMbTable::REF_CHASSIS_ID->value]
|
|
||||||
])->get()->json()[0]; ?>
|
|
||||||
|
|
||||||
<div data-target="case" class="spec">
|
|
||||||
<p>Case</p>
|
|
||||||
<h3><?= $case[ChassisTable::VENDOR_NAME->value] ?> <span><?= $case[ChassisTable::VENDOR_MODEL->value] ?></span></h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $case[ChassisTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $case[ChassisTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Nº 2.5" slots</label>
|
|
||||||
<p><?= $case[ChassisTable::STORAGE_TWOINCHFIVE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Nº 3.5" slots</label>
|
|
||||||
<p><?= $case[ChassisTable::STORAGE_THREEINCHFIVE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $case[ChassisTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($case[ChassisTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<?php // List all CPUs ?>
|
|
||||||
<?php foreach ($motherboard["cpus"] as $mb_cpu): ?>
|
|
||||||
|
|
||||||
<?php // Get case details from endpoint by id ?>
|
|
||||||
<?php $cpu = $api->call(Endpoints::BATTLESTATION_CPU->value)->params([
|
|
||||||
CpuTable::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value]
|
|
||||||
])->get()->json()[0]; ?>
|
|
||||||
|
|
||||||
<div data-target="cpu" class="spec">
|
|
||||||
<p>CPU</p>
|
|
||||||
<h3><?= $cpu[CpuTable::VENDOR_NAME->value] ?> <span><?= $cpu[CpuTable::VENDOR_MODEL->value] ?></span></h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $cpu[CpuTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $cpu[CpuTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Class</label>
|
|
||||||
<p><?= $cpu[CpuTable::CPU_CLASS->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Base Clockspeed</label>
|
|
||||||
<p><?= $cpu[CpuTable::CLOCK_BASE->value] / GIGA ?>GHz</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Turbo Clockspeed</label>
|
|
||||||
<p><?= $cpu[CpuTable::CLOCK_TURBO->value] / GIGA ?>GHz</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Nº cores (P/E)</label>
|
|
||||||
<p><?= $cpu[CpuTable::CORE_COUNT_PERFORMANCE->value] + $cpu[CpuTable::CORE_COUNT_EFFICIENCY->value] ?> (<?= $cpu[CpuTable::CORE_COUNT_PERFORMANCE->value] ?>/<?= $cpu[CpuTable::CORE_COUNT_EFFICIENCY->value] ?>)</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Nº total threads</label>
|
|
||||||
<p><?= $cpu[CpuTable::CORE_THREADS->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $cpu[CpuTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($cpu[CpuTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>In motherboard slot number</label>
|
|
||||||
<p><?= $mb_cpu[MbCpuCoolerModel::SOCKET->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Motherboard slot type</label>
|
|
||||||
<p><?= $mb_cpu[MbCpuCoolerModel::SOCKET_TYPE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<?php // List all GPUs ?>
|
|
||||||
<?php foreach ($motherboard["gpus"] as $mb_gpu): ?>
|
|
||||||
|
|
||||||
<?php // Get case details from endpoint by id ?>
|
|
||||||
<?php $gpu = $api->call(Endpoints::BATTLESTATION_GPU->value)->params([
|
|
||||||
GpuTable::ID->value => $mb_gpu[MbGpuTable::REF_GPU_ID->value]
|
|
||||||
])->get()->json()[0]; ?>
|
|
||||||
|
|
||||||
<div data-target="gpu" class="spec">
|
|
||||||
<p>GPU</p>
|
|
||||||
<h3><?= $gpu[GpuTable::VENDOR_NAME->value] ?> <span><?= $gpu[GpuTable::VENDOR_CHIP_MODEL->value] ?></span></h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Chip brand name</label>
|
|
||||||
<p><?= $gpu[GpuTable::VENDOR_CHIP_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Chip brand model</label>
|
|
||||||
<p><?= $gpu[GpuTable::VENDOR_CHIP_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>VRAM</label>
|
|
||||||
<p><?= $gpu[GpuTable::MEMORY->value] / GIGA ?>GB</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $gpu[GpuTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $gpu[GpuTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $gpu[GpuTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($gpu[GpuTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<?php // List all PSUs ?>
|
|
||||||
<?php foreach ($motherboard["psus"] as $mb_psu): ?>
|
|
||||||
|
|
||||||
<?php // Get case details from endpoint by id ?>
|
|
||||||
<?php $psu = $api->call(Endpoints::BATTLESTATION_PSU->value)->params([
|
|
||||||
PsuTable::ID->value => $mb_psu[MbPsuTable::REF_PSU_ID->value]
|
|
||||||
])->get()->json()[0]; ?>
|
|
||||||
|
|
||||||
<div data-target="psu" class="spec">
|
|
||||||
<p>PSU</p>
|
|
||||||
<h3><?= $psu[PsuTable::VENDOR_NAME->value] ?> <span><?= $psu[PsuTable::VENDOR_MODEL->value] ?></span> <span><?= $psu[PsuTable::POWER->value] ?>W</span></h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Power</label>
|
|
||||||
<p><?= $psu[PsuTable::POWER->value] ?>W</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $psu[PsuTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $psu[PsuTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Is modular?</label>
|
|
||||||
<p><?= $psu[PsuTable::TYPE_MODULAR->value] === "TRUE" ? "Yes" : "No" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>80+ Rating</label>
|
|
||||||
<p><?= $psu[PsuTable::EIGHTYPLUS_RATING->value] ?? "None" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $psu[PsuTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($psu[PsuTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<p>DRAM</p>
|
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="collection">
|
|
||||||
<?php // List all DRAM ?>
|
|
||||||
<?php foreach ($motherboard["dram"] as $mb_dram): ?>
|
|
||||||
|
|
||||||
<?php // Get case details from endpoint by id ?>
|
|
||||||
<?php $dram = $api->call(Endpoints::BATTLESTATION_DRAM->value)->params([
|
|
||||||
DramTable::ID->value => $mb_dram[MbDramTable::REF_DRAM_ID->value]
|
|
||||||
])->get()->json()[0]; ?>
|
|
||||||
|
|
||||||
<div data-target="dram" class="spec">
|
|
||||||
<p>DRAM - <?= $dram[DramTable::TECHNOLOGY->value] ?></p>
|
|
||||||
<h3><?= $dram[DramTable::VENDOR_NAME->value] ?>
|
|
||||||
<span><?= $dram[DramTable::CAPACITY->value] / GIGA ?>GB</span>
|
|
||||||
<span><?= $dram[DramTable::SPEED->value] / MEGA ?>MHz</span>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Capacity</label>
|
|
||||||
<p><?= $dram[DramTable::CAPACITY->value] / GIGA ?>GB</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Speed</label>
|
|
||||||
<p><?= $dram[DramTable::SPEED->value] / MEGA ?>MHz</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $dram[DramTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $dram[DramTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Formfactor</label>
|
|
||||||
<p><?= $dram[DramTable::FORMFACTOR->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Technology</label>
|
|
||||||
<p><?= $dram[DramTable::TECHNOLOGY->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Is ECC?</label>
|
|
||||||
<p><?= $dram[DramTable::ECC->value] === "TRUE" ? "Yes" : "No" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Is buffered?</label>
|
|
||||||
<p><?= $dram[DramTable::BUFFERED->value] === "TRUE" ? "Yes" : "No" ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $dram[DramTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($dram[DramTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>In motherboard slot number</label>
|
|
||||||
<p><?= $mb_dram[MbDramTable::SOCKET->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Motherboard slot type</label>
|
|
||||||
<p><?= $mb_dram[MbDramTable::SOCKET_TYPE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<p>Storage</p>
|
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="collection">
|
|
||||||
<?php // List all storage ?>
|
|
||||||
<?php foreach ($motherboard["storage"] as $mb_storage): ?>
|
|
||||||
|
|
||||||
<?php // Get case details from endpoint by id ?>
|
|
||||||
<?php $storage = $api->call(Endpoints::BATTLESTATION_STORAGE->value)->params([
|
|
||||||
StorageTable::ID->value => $mb_storage[MbStorageTable::REF_STORAGE_ID->value]
|
|
||||||
])->get()->json()[0]; ?>
|
|
||||||
|
|
||||||
<div data-target="drive" class="spec">
|
|
||||||
<p><?= $storage[StorageTable::DISK_FORMFACTOR->value] ?> <?= $storage[StorageTable::DISK_TYPE->value] ?></p>
|
|
||||||
<h3>
|
|
||||||
<?= $storage[StorageTable::VENDOR_NAME->value] ?>
|
|
||||||
<span><?= floor($storage[StorageTable::DISK_SIZE->value] / GIGA) ?>GB</span>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Type</label>
|
|
||||||
<p><?= $storage[StorageTable::DISK_TYPE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Capacity</label>
|
|
||||||
<p><?= floor($storage[StorageTable::DISK_SIZE->value] / GIGA) ?>GB</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Interface</label>
|
|
||||||
<p><?= $storage[StorageTable::DISK_INTERFACE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Formfactor</label>
|
|
||||||
<p><?= $storage[StorageTable::DISK_FORMFACTOR->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand name</label>
|
|
||||||
<p><?= $storage[StorageTable::VENDOR_NAME->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Brand model</label>
|
|
||||||
<p><?= $storage[StorageTable::VENDOR_MODEL->value] ?></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Aquired</label>
|
|
||||||
<p><?= date(API::DATE_FORMAT, $storage[StorageTable::DATE_AQUIRED->value]) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($storage[StorageTable::IS_RETIRED->value]): ?>
|
|
||||||
<div>
|
|
||||||
<label>Retired</label>
|
|
||||||
<p>Yes</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<label>Attatched via interface</label>
|
|
||||||
<p><?= $mb_storage[MbStorageTable::INTERFACE->value] ?></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
<script type="module"><?= VV::js("public/assets/js/pages/about/battlestation") ?></script>
|
|
|
@ -60,10 +60,6 @@ section.about span.interests {
|
||||||
animation: interests-hue 5s infinite linear;
|
animation: interests-hue 5s infinite linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.about p i:not(:hover) {
|
|
||||||
opacity: .3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ## Languages */
|
/* ## Languages */
|
||||||
|
|
||||||
section.languages {
|
section.languages {
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
/* # Overrides */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--primer-color-accent: 148, 255, 21;
|
|
||||||
--color-accent: rgb(var(--primer-color-accent));
|
|
||||||
}
|
|
||||||
|
|
||||||
vv-shell {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* # Content */
|
|
||||||
|
|
||||||
/* ## Title */
|
|
||||||
|
|
||||||
section.title {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ## Actions */
|
|
||||||
|
|
||||||
section.actions {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: var(--padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* # Size quries */
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
section.actions {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
section.actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,290 +0,0 @@
|
||||||
/* # Overrides */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--primer-color-accent: 148, 255, 21;
|
|
||||||
--color-accent: rgb(var(--primer-color-accent));
|
|
||||||
}
|
|
||||||
|
|
||||||
vv-shell {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* # Content */
|
|
||||||
|
|
||||||
/* ## Title */
|
|
||||||
|
|
||||||
section.title {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
padding: calc(var(--padding) * 1.5);
|
|
||||||
background-color: rgba(var(--primer-color-accent), .1);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.title > div {
|
|
||||||
margin-top: calc(var(--padding) / 2);
|
|
||||||
display: flex;
|
|
||||||
gap: var(--padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ## Heading */
|
|
||||||
|
|
||||||
section.heading h1::before,
|
|
||||||
section.heading h1::after {
|
|
||||||
opacity: .4;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.heading h1::before {
|
|
||||||
content: "“";
|
|
||||||
}
|
|
||||||
|
|
||||||
section.heading h1::after {
|
|
||||||
content: "”";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ## Config */
|
|
||||||
|
|
||||||
section.config {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 300px 1fr;
|
|
||||||
gap: calc(var(--padding) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config:nth-child(4n+2) {
|
|
||||||
grid-template-columns: 1fr 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config:nth-child(4n+2) > svg {
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ### PC */
|
|
||||||
|
|
||||||
section.config > svg {
|
|
||||||
position: sticky;
|
|
||||||
top: calc(var(--running-size) + var(--padding));
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config > svg :is(rect, path) {
|
|
||||||
transition: 300ms;
|
|
||||||
stroke: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config > svg.active :is(rect, path),
|
|
||||||
section.config > svg:hover :is(rect, path) {
|
|
||||||
opacity: .4;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config > svg g.active rect,
|
|
||||||
section.config > svg g.active path,
|
|
||||||
section.config > svg g:not(.group):hover rect,
|
|
||||||
section.config > svg g:not(.group):hover path {
|
|
||||||
opacity: 1;
|
|
||||||
stroke: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config > svg g.active rect,
|
|
||||||
section.config > svg g:not(.group):hover rect {
|
|
||||||
filter: drop-shadow(0 0 10px rgba(var(--primer-color-accent), .4));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #### Case */
|
|
||||||
|
|
||||||
section.config g.case:not(:hover, .active) :is(rect, path) {
|
|
||||||
opacity: .2;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config > svg g.active path,
|
|
||||||
section.config > svg g:not(.group):hover path {
|
|
||||||
fill: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #### Motherboard */
|
|
||||||
|
|
||||||
section.config > svg .mb .chips {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #### Active states */
|
|
||||||
|
|
||||||
section.config > svg g:not(.group) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config[data-dram="1"] > svg g.drams g.dram:nth-child(1),
|
|
||||||
section.config[data-dram="2"] > svg g.drams g.dram:nth-child(3n+1),
|
|
||||||
section.config[data-dram="3"] > svg g.drams g.dram:nth-child(-n+3),
|
|
||||||
section.config[data-dram="4"] > svg g.drams g.dram,
|
|
||||||
|
|
||||||
section.config[data-drives-mdottwo="1"] > svg g.mdottwo g.drive:nth-child(1),
|
|
||||||
section.config[data-drives-mdottwo="2"] > svg g.mdottwo g.drive:nth-child(-n+2),
|
|
||||||
section.config[data-drives-mdottwo="3"] > svg g.mdottwo g.drive:nth-child(-n+3),
|
|
||||||
|
|
||||||
section.config[data-drives-twodotfive="1"] > svg g.twodotfive g.drive:nth-child(1),
|
|
||||||
section.config[data-drives-twodotfive="2"] > svg g.twodotfive g.drive:nth-child(-n+2),
|
|
||||||
section.config[data-drives-twodotfive="3"] > svg g.twodotfive g.drive:nth-child(-n+3),
|
|
||||||
|
|
||||||
section.config[data-drives-threedotfive="1"] > svg g.threedotfive g.drive:nth-child(1),
|
|
||||||
section.config[data-drives-threedotfive="2"] > svg g.threedotfive g.drive:nth-child(-n+2),
|
|
||||||
section.config[data-drives-threedotfive="3"] > svg g.threedotfive g.drive:nth-child(-n+3),
|
|
||||||
|
|
||||||
section.config[data-mb="1"] > svg g.mb,
|
|
||||||
section.config[data-psu="1"] > svg g.psu,
|
|
||||||
section.config[data-gpu="1"] > svg g.gpu,
|
|
||||||
section.config[data-cpu="1"] > svg g.cpu,
|
|
||||||
section.config[data-case="1"] > svg g.case {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ## Specs */
|
|
||||||
|
|
||||||
section.config .specs {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: calc(var(--padding) / 2);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs :is(.spec, .group) {
|
|
||||||
--border-width: 4px;
|
|
||||||
|
|
||||||
transition: 300ms background-color, 300ms border-color, 500ms box-shadow;
|
|
||||||
padding: calc(var(--padding) - var(--border-width));
|
|
||||||
border: solid var(--border-width) transparent;
|
|
||||||
background-color: rgba(255, 255, 255, .03);
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs :is(.spec, .group) * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ### Active state */
|
|
||||||
|
|
||||||
section.config .specs.active {
|
|
||||||
background-color: rgba(255, 255, 255, .03);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs.active :is(.group, .spec:not(.active)) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ### Spec */
|
|
||||||
|
|
||||||
section.config .specs .spec {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec:hover {
|
|
||||||
border-color: rgba(255, 255, 255, .05);
|
|
||||||
background-color: rgba(255, 255, 255, .1);
|
|
||||||
box-shadow: 0 0 30px 10px rgba(255, 255, 255, .05);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec.active {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background-color: rgba(var(--primer-color-accent), .1);
|
|
||||||
box-shadow: 0 0 30px 10px rgba(var(--primer-color-accent), .05);
|
|
||||||
cursor: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs.active .spec.active {
|
|
||||||
position: sticky;
|
|
||||||
top: calc(var(--running-size) + var(--padding));
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec h3 {
|
|
||||||
color: rgba(255, 255, 255, .3);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec span {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec > div {
|
|
||||||
display: none;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: calc(var(--padding) / 2);
|
|
||||||
margin-top: var(--padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec.active > div {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec > div label {
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .spec > svg {
|
|
||||||
display: none;
|
|
||||||
height: calc(var(--padding) / 2);
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-top: calc(var(--padding) / 2);
|
|
||||||
fill: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ### Group */
|
|
||||||
|
|
||||||
section.config .specs .group {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .group.active {
|
|
||||||
background-color: rgba(255, 255, 255, .2);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .group:hover {
|
|
||||||
background-color: rgba(255, 255, 255, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .group.active:hover {
|
|
||||||
background-color: rgba(255, 255, 255, .3);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .group > svg {
|
|
||||||
transition: 300ms transform;
|
|
||||||
fill: var(--color-accent);
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .group.active > svg {
|
|
||||||
transform: rotateX(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #### Collection */
|
|
||||||
|
|
||||||
section.config .specs .collection {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config .specs .group.active + .collection {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* # Size quries */
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
section.title > div {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config,
|
|
||||||
section.config:nth-child(4n+2) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.config > svg {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -106,6 +106,19 @@ section.pgp .buttons {
|
||||||
gap: var(--padding);
|
gap: var(--padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ## Blockquote */
|
||||||
|
|
||||||
|
cite {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
text-decoration-color: rgba(var(--primer-color-accent), .5);
|
||||||
|
text-decoration-thickness: .1em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ## Contact form */
|
/* ## Contact form */
|
||||||
|
|
||||||
section.form :is(input, textarea) {
|
section.form :is(input, textarea) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ vv-shell img {
|
||||||
padding: unset;
|
padding: unset;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: clamp(20px, 8vh, 60px);
|
font-size: clamp(20px, 8vh, 60px);
|
||||||
font-weight: 900;
|
font-weight: bold;
|
||||||
line-height: clamp(20px, 8vh, 60px);
|
line-height: clamp(20px, 8vh, 60px);
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,11 @@ vv-shell img {
|
||||||
|
|
||||||
.menu svg {
|
.menu svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
animation: dash 1500ms linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dash {
|
||||||
|
to { stroke-dashoffset: 32; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ### Copy email button */
|
/* ### Copy email button */
|
||||||
|
|
16
public/assets/css/pages/work/archive.css
Normal file
16
public/assets/css/pages/work/archive.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/* # Overrides */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primer-color-accent: 3, 255, 219;
|
||||||
|
--color-accent: rgb(var(--primer-color-accent));
|
||||||
|
--hue-accent: 90deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
vv-shell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--padding);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
overflow-x: initial;
|
||||||
|
}
|
|
@ -128,19 +128,28 @@ section.featured featured-item .title svg {
|
||||||
fill: var(--color-accent);
|
fill: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ### Languages */
|
section.featured featured-item img {
|
||||||
|
width: 100%;
|
||||||
/* ### Actions */
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
section.featured featured-item .actions {
|
section.featured featured-item .actions {
|
||||||
gap: 5px;
|
gap: var(--padding);
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
padding-top: var(--padding);
|
padding-top: var(--padding);
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* # Size queries */
|
/* # Size queries */
|
||||||
|
|
||||||
|
@media (min-width: 400px) {
|
||||||
|
section.featured featured-item .actions {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
section.hero {
|
section.hero {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
@ -151,4 +160,8 @@ section.featured featured-item .actions {
|
||||||
section.featured {
|
section.featured {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.featured featured-item .actions button.collapse p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -286,6 +286,10 @@ header searchbox input {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header searchbox input::placeholder {
|
||||||
|
color: rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
/* #### Active */
|
/* #### Active */
|
||||||
|
|
||||||
header.searchboxActive > * {
|
header.searchboxActive > * {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { Hoverpop } from "/assets/js/modules/Hoverpop.mjs";
|
|
||||||
|
|
||||||
const randomIntFromInterval = (min, max) => {
|
const randomIntFromInterval = (min, max) => {
|
||||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||||
}
|
}
|
||||||
|
@ -58,15 +56,23 @@ const implodeInterests = () => {
|
||||||
// Bind mouse or touch events depending on pointer type of device
|
// Bind mouse or touch events depending on pointer type of device
|
||||||
const canHover = window.matchMedia("(pointer: fine)").matches;
|
const canHover = window.matchMedia("(pointer: fine)").matches;
|
||||||
|
|
||||||
interestsElement.addEventListener(canHover ? "mouseenter" : "touchstart", () => {
|
// Explode interests when mouse hovers or touch hold starts
|
||||||
// Get absolute position of the trigger element
|
interestsElement.addEventListener(canHover ? "mouseenter" : "touchstart", (event) => explodeInterests(event.x, event.y));
|
||||||
const size = interestsElement.getBoundingClientRect();
|
// Implode interests when mouse leaves or touch hold ends
|
||||||
|
|
||||||
explodeInterests(size.x, size.y);
|
|
||||||
});
|
|
||||||
|
|
||||||
interestsElement.addEventListener(canHover ? "mouseleave" : "touchend", () => implodeInterests());
|
interestsElement.addEventListener(canHover ? "mouseleave" : "touchend", () => implodeInterests());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Languages stacking bar chart hoverpop
|
// Language bar chart hover tooltip
|
||||||
new Hoverpop(document.querySelectorAll("stacked-bar-chart chart-segment"));
|
document.querySelectorAll("stacked-bar-chart chart-segment").forEach(element => {
|
||||||
|
const tooltipElement = element.querySelector("[data-hover]");
|
||||||
|
|
||||||
|
element.addEventListener("mouseenter", () => tooltipElement.classList.add("hovering"));
|
||||||
|
element.addEventListener("mouseleave", () => tooltipElement.classList.remove("hovering"));
|
||||||
|
|
||||||
|
element.addEventListener("mousemove", (event) => {
|
||||||
|
const x = event.layerX - (tooltipElement.clientWidth / 2);
|
||||||
|
const y = event.layerY + (tooltipElement.clientHeight - 30);
|
||||||
|
|
||||||
|
tooltipElement.style.setProperty("transform", `translate(${x}px, ${y}px)`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,72 +0,0 @@
|
||||||
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
|
|
||||||
|
|
||||||
new Elevent("click", document.querySelectorAll(".group"), (event) => {
|
|
||||||
// Collapse self if already active and current target
|
|
||||||
if (event.target.classList.contains("active")) {
|
|
||||||
return event.target.classList.remove("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapse all and open current target
|
|
||||||
[...event.target.closest(".specs").querySelectorAll(".group")].forEach(element => element.classList.remove("active"));
|
|
||||||
event.target.classList.add("active");
|
|
||||||
});
|
|
||||||
|
|
||||||
new Elevent("click", document.querySelectorAll(".spec"), (event) => {
|
|
||||||
event.target.classList.add("active");
|
|
||||||
event.target.addEventListener("mouseleave", () => event.target.classList.remove("active"));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind hover listeners for components in the SVGs
|
|
||||||
[...document.querySelectorAll("section.config g:not(.group)")].forEach(element => {
|
|
||||||
element.addEventListener("mouseenter", () => {
|
|
||||||
// Find an element in the most adjacent speclist and highlighit it
|
|
||||||
const target = element.closest("section.config").querySelector(`.spec[data-target="${element.dataset.target}"]`);
|
|
||||||
// Get closest specs wrapper element
|
|
||||||
const specsElement = target.closest(".specs");
|
|
||||||
// Spec item is part of a collection, we need to expand the group if that is the case
|
|
||||||
const collectionElement = target.closest(".collection") ?? null;
|
|
||||||
// Don't close the group after hove ends
|
|
||||||
let closeGroupOnLeave = false;
|
|
||||||
|
|
||||||
// Set fixed height on .specs wrapper to prevent glitchy page jumping when scrolled
|
|
||||||
specsElement.style.setProperty("height", `${specsElement.offsetHeight}px`);
|
|
||||||
target.classList.add("active");
|
|
||||||
specsElement.classList.add("active");
|
|
||||||
|
|
||||||
if (collectionElement) {
|
|
||||||
// Close the group on leave if the group wasn't active before hovering
|
|
||||||
closeGroupOnLeave = !collectionElement.previousElementSibling.classList.contains("active");
|
|
||||||
|
|
||||||
collectionElement.previousElementSibling.classList.add("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind hover leave listener
|
|
||||||
element.addEventListener("mouseleave", () => {
|
|
||||||
// Reset to initial states
|
|
||||||
target.classList.remove("active");
|
|
||||||
specsElement.classList.remove("active");
|
|
||||||
specsElement.style.removeProperty("height");
|
|
||||||
|
|
||||||
// Group was closed prior to hover, let's close it on hover leave
|
|
||||||
if (closeGroupOnLeave) {
|
|
||||||
collectionElement.previousElementSibling.classList.remove("active");
|
|
||||||
}
|
|
||||||
}, { once: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind event listeners for components in the spec lists
|
|
||||||
[...document.querySelectorAll("section.config .spec:not(.group)")].forEach(element => {
|
|
||||||
element.addEventListener("mouseenter", () => {
|
|
||||||
const svgTarget = element.closest("section.config").querySelector(`svg`);
|
|
||||||
const target = svgTarget.querySelector(`svg g[data-target="${element.dataset.target}"]`);
|
|
||||||
|
|
||||||
svgTarget.classList.add("active");
|
|
||||||
target.classList.add("active");
|
|
||||||
|
|
||||||
element.addEventListener("mouseleave", () => {
|
|
||||||
svgTarget.classList.remove("active");
|
|
||||||
target.classList.remove("active");
|
|
||||||
}, { once: true });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { Hoverpop } from "/assets/js/modules/Hoverpop.mjs";
|
|
||||||
|
|
||||||
class ContactForm {
|
class ContactForm {
|
||||||
static STORAGE_KEY = "contact_form_message";
|
static STORAGE_KEY = "contact_form_message";
|
||||||
|
|
||||||
|
@ -62,7 +60,16 @@ class ContactForm {
|
||||||
form ? (new ContactForm(form)) : ContactForm.removeSavedMessage();
|
form ? (new ContactForm(form)) : ContactForm.removeSavedMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Social links hoverpop
|
document.querySelectorAll("social").forEach(element => {
|
||||||
{
|
const tooltipElement = element.querySelector("[data-hover]");
|
||||||
new Hoverpop(document.querySelectorAll("social"));
|
|
||||||
}
|
element.addEventListener("mouseenter", () => tooltipElement.classList.add("hovering"));
|
||||||
|
element.addEventListener("mouseleave", () => tooltipElement.classList.remove("hovering"));
|
||||||
|
|
||||||
|
element.addEventListener("mousemove", (event) => {
|
||||||
|
const x = event.layerX - (tooltipElement.clientWidth / 2);
|
||||||
|
const y = event.layerY + tooltipElement.clientHeight;
|
||||||
|
|
||||||
|
tooltipElement.style.setProperty("transform", `translate(${x}px, ${y}px)`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,3 @@
|
||||||
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
|
|
||||||
|
|
||||||
// Click to copy email button
|
// Click to copy email button
|
||||||
{
|
{
|
||||||
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
|
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
|
||||||
|
@ -55,7 +53,7 @@ import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
|
||||||
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
|
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
new Elevent("click", document.querySelector(".email"), async () => {
|
document.querySelector(".email").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText("victor@vlw.se");
|
await navigator.clipboard.writeText("victor@vlw.se");
|
||||||
|
|
||||||
|
@ -103,6 +101,6 @@ import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset color on navigation
|
// Reset color on navigation
|
||||||
vv.Navigation.shellElement.addEventListener(vv.Navigation.EVENTS.STARTED, () => updateColor(), { once: true });
|
VV.shell.addEventListener(VV.EVENT.START, () => updateColor(), { once: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
4
public/assets/js/pages/work/archive.js
Normal file
4
public/assets/js/pages/work/archive.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// Redirect to work page if no href is defined
|
||||||
|
if (!new URLSearchParams(window.location.search).has("href")) {
|
||||||
|
new VV().navigate("/work");
|
||||||
|
}
|
|
@ -1,56 +1,45 @@
|
||||||
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
|
const DEBOUNCE_TIMEOUT_MS = 100;
|
||||||
|
|
||||||
const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive";
|
const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive";
|
||||||
|
|
||||||
// Set global Vegvisir naviation delay for page transition effect
|
// Navigate to the start page if the logo in the header is clicked
|
||||||
globalThis.vegvisir.globalNavigationDelayMs = 100;
|
document.querySelector("header .logo").addEventListener("click", () => new VV().navigate("/"));
|
||||||
|
|
||||||
// Handle search box open/close buttons
|
// Navigate to the start page if the logo in the header is clicked
|
||||||
{
|
document.querySelector("header .logo").addEventListener("click", () => new VV().navigate("/"));
|
||||||
// Open search box
|
|
||||||
new Elevent("click", document.querySelector(".searchbox-open"), () => {
|
|
||||||
document.querySelector("header").classList.add(CLASSNAME_SEARCHBOX_ACTIVE);
|
|
||||||
// Select searchbox inner input element
|
|
||||||
document.querySelector("searchbox input").focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close searchbox
|
|
||||||
new Elevent("click", document.querySelector(".searchbox-close"), () => {
|
|
||||||
// Disable search button interaction while animation is running
|
|
||||||
// This is required to prevent conflicts with the :hover "peak" transformation
|
|
||||||
const searchButtonElement = document.querySelector("header button.search");
|
|
||||||
const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
|
|
||||||
searchButtonElement.style.setProperty("pointer-events", "none");
|
|
||||||
|
|
||||||
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
|
|
||||||
|
|
||||||
// Wait for the transform animation to finish
|
|
||||||
setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root shell navigation event handlers
|
|
||||||
{
|
|
||||||
// On all top shell navigations
|
|
||||||
new Elevent(vv.Navigation.EVENTS.STARTED, vv.Navigation.rootShellElement, () => {
|
|
||||||
// Close searchbox on top shell navigations
|
|
||||||
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle search logic
|
|
||||||
{
|
|
||||||
const searchResultsElement = document.querySelector("search-results");
|
|
||||||
|
|
||||||
document.querySelector("header input[type='search']").addEventListener("input", (event) => {
|
|
||||||
// Debounce user input
|
|
||||||
clearTimeout(event.target._throttle);
|
|
||||||
event.target._throttle = setTimeout(() => {
|
|
||||||
// Navigate search-results element on user input
|
|
||||||
new vv.Navigation(`/search?query=${event.target.value}`).navigate(searchResultsElement);
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll page to top on navigation
|
// Scroll page to top on navigation
|
||||||
document.addEventListener(vegvisir.Navigation.EVENTS.FINISHED, () => window.scrollTo({ top: 0 }));
|
VV.shell.addEventListener(VV.EVENT.FINISH, () => window.scrollTo({ top: 0 }));
|
||||||
|
|
||||||
|
// Open search box
|
||||||
|
document.querySelector(".searchbox-open").addEventListener("click", () => {
|
||||||
|
document.querySelector("header").classList.add(CLASSNAME_SEARCHBOX_ACTIVE);
|
||||||
|
// Select searchbox inner input element
|
||||||
|
document.querySelector("searchbox input").focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close searchbox
|
||||||
|
document.querySelector(".searchbox-close").addEventListener("click", () => {
|
||||||
|
// Disable search button interaction while animation is running
|
||||||
|
// This is required to prevent conflicts with the :hover "peak" transformation
|
||||||
|
const searchButtonElement = document.querySelector("header button.search");
|
||||||
|
const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
|
||||||
|
searchButtonElement.style.setProperty("pointer-events", "none");
|
||||||
|
|
||||||
|
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
|
||||||
|
|
||||||
|
// Wait for the transform animation to finish
|
||||||
|
setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close searchbox on top shell navigations
|
||||||
|
VV.shell.addEventListener(VV.EVENT.START, () => document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE));
|
||||||
|
|
||||||
|
// Handle search logic
|
||||||
|
document.querySelector("header input[type='search']").addEventListener("input", (event) => {
|
||||||
|
// Debounce user input
|
||||||
|
clearTimeout(event.target._throttle);
|
||||||
|
event.target._throttle = setTimeout(() => {
|
||||||
|
// Navigate search-results element on user input
|
||||||
|
new VV(document.querySelector("search-results")).navigate(`/search?q=${event.target.value}`);
|
||||||
|
}, DEBOUNCE_TIMEOUT_MS);
|
||||||
|
});
|
||||||
|
|
|
@ -1,173 +0,0 @@
|
||||||
<svg viewBox="0 0 236 288" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<mask fill="#fff" id="a"><path d="M19 269a1 1 0 0 1-1-1v-11a1 1 0 0 1 1-1v13Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="b"><path d="M87 241a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1v-7Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="c"><path d="M87 251a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1v-7Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="d"><path d="M179 272.051c0-.58.471-1.051 1.051-1.051h8.898c.58 0 1.051.471 1.051 1.051h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="e"><path d="M191 272.051c0-.58.471-1.051 1.051-1.051h2.898c.58 0 1.051.471 1.051 1.051h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="f"><path d="M179 257.333c0-.58.471-1.051 1.051-1.051h8.898c.58 0 1.051.471 1.051 1.051h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="g"><path d="M191 257.333c0-.58.471-1.051 1.051-1.051h2.898c.58 0 1.051.471 1.051 1.051h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="h"><path d="M179 242.615c0-.58.471-1.051 1.051-1.051h8.898c.58 0 1.051.471 1.051 1.051h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="i"><path d="M191 242.615c0-.58.471-1.051 1.051-1.051h2.898c.58 0 1.051.471 1.051 1.051h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="j"><path d="M133 257.333c0-.58.471-1.051 1.051-1.051h8.898c.58 0 1.051.471 1.051 1.051h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="k"><path d="M145 257.333c0-.58.471-1.051 1.051-1.051h2.898c.58 0 1.051.471 1.051 1.051h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="l"><path d="M133 242.615c0-.58.471-1.051 1.051-1.051h8.898c.58 0 1.051.471 1.051 1.051h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="m"><path d="M145 242.615c0-.58.471-1.051 1.051-1.051h2.898c.58 0 1.051.471 1.051 1.051h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="n"><path d="M133 272.051c0-.58.471-1.051 1.051-1.051h8.898c.58 0 1.051.471 1.051 1.051h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="o"><path d="M145 272.051c0-.58.471-1.051 1.051-1.051h2.898c.58 0 1.051.471 1.051 1.051h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="p"><path d="M186 40a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="q"><path d="M198 40a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="r"><path d="M186 51a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="s"><path d="M198 51a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="t"><path d="M186 62a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="u"><path d="M198 62a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="v"><path d="M186 73a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1h-11Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="w"><path d="M198 73a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h-5Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="x"><path d="M129.535 123.781h-1.082a.453.453 0 0 1 0-.906h1.082v.906Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="y"><path d="M129.535 115.625h-1.082a.453.453 0 0 1 0-.906h1.082v.906Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="z"><path d="M129.535 121.969H129a1 1 0 0 1-1-1v-3.438a1 1 0 0 1 1-1h.535v5.438Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="A"><path d="M92.535 123.781h-1.082a.453.453 0 0 1 0-.906h1.082v.906Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="B"><path d="M92.535 115.625h-1.082a.453.453 0 0 1 0-.906h1.082v.906Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="C"><path d="M92.535 121.969H92a1 1 0 0 1-1-1v-3.438a1 1 0 0 1 1-1h.535v5.438Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="D"><path d="M68 143v-2a1 1 0 0 1 1-1h29a1 1 0 0 1 1 1v2H68Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="E"><path d="M101 143v-2a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v2h-3Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="F"><path d="M64 143v-2a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v2h-3Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="G"><path d="M55.535 123.781h-1.082a.453.453 0 0 1 0-.906h1.082v.906Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="H"><path d="M55.535 115.625h-1.082a.453.453 0 0 1 0-.906h1.082v.906Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="I"><path d="M55.535 121.969H55a1 1 0 0 1-1-1v-3.438a1 1 0 0 1 1-1h.535v5.438Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="J"><path d="M76 38h1a1 1 0 0 1 1 1v33a1 1 0 0 1-1 1h-1V38Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="K"><path d="M76 76h1a1 1 0 0 1 1 1v22a1 1 0 0 1-1 1h-1V76Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="L"><path d="M60 38h1a1 1 0 0 1 1 1v33a1 1 0 0 1-1 1h-1V38Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="M"><path d="M60 76h1a1 1 0 0 1 1 1v22a1 1 0 0 1-1 1h-1V76Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="N"><path d="M137 100h-1a1 1 0 0 1-1-1V66a1 1 0 0 1 1-1h1v35Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="O"><path d="M137 62h-1a1 1 0 0 1-1-1V39a1 1 0 0 1 1-1h1v24Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="P"><path d="M155 100h-1a1 1 0 0 1-1-1V66a1 1 0 0 1 1-1h1v35Z"></path></mask>
|
|
||||||
<mask fill="#fff" id="Q"><path d="M155 62h-1a1 1 0 0 1-1-1V39a1 1 0 0 1 1-1h1v24Z"></path></mask>
|
|
||||||
<g data-target="case" data-index="1" class="case">
|
|
||||||
<rect height="278" rx="3" stroke="#fff" stroke-width="6" width="230" x="3" y="7"></rect>
|
|
||||||
<path d="M20.639 216.906a2 2 0 0 1 1.674-.906h192.14a2 2 0 0 1 1.756 1.043l2.18 4c.726 1.333-.239 2.957-1.756 2.957H19.697c-1.589 0-2.543-1.764-1.674-3.094l2.616-4ZM5.451.793A2 2 0 0 1 7.046 0h222.808a2 2 0 0 1 1.692.934l2.521 4c.84 1.331-.118 3.066-1.692 3.066H4.021C2.369 8 1.429 6.11 2.425 4.793l3.026-4Z" fill="#fff"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="psu" class="psu">
|
|
||||||
<path d="M20 268v-11h-4v11h4Zm-3-12v13h4v-13h-4Zm3 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 10a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#a)"></path>
|
|
||||||
<path d="M86 242v5h4v-5h-4Zm3 6v-7h-4v7h4Zm-3-1a1 1 0 0 1 1-1v4a3 3 0 0 0 3-3h-4Zm1-4a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Z" fill="#fff" mask="url(#b)"></path>
|
|
||||||
<path d="M86 252v5h4v-5h-4Zm3 6v-7h-4v7h4Zm-3-1a1 1 0 0 1 1-1v4a3 3 0 0 0 3-3h-4Zm1-4a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Z" fill="#fff" mask="url(#c)"></path>
|
|
||||||
<rect height="38" rx="3.5" stroke="#fff" stroke-width="3" width="65" x="20.5" y="234.5"></rect>
|
|
||||||
</g>
|
|
||||||
<g class="group drives threedotfive">
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="9.564" rx="2" stroke="#fff" stroke-width="2" width="41" x="176" y="263.436"></rect>
|
|
||||||
<path d="M180.051 273h8.898v-4h-8.898v4Zm9.949-2.949h-11v4h11v-4ZM188.949 273a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-8.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#d)"></path>
|
|
||||||
<path d="M192.051 273h2.898v-4h-2.898v4Zm3.949-2.949h-5v4h5v-4ZM194.949 273a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-2.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#e)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="9.564" rx="2" stroke="#fff" stroke-width="2" width="41" x="176" y="248.718"></rect>
|
|
||||||
<path d="M192.051 258.282h2.898v-4h-2.898v4Zm3.949-2.949h-5v4h5v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-2.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#g)"></path>
|
|
||||||
<path d="M180.051 258.282h8.898v-4h-8.898v4Zm9.949-2.949h-11v4h11v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-8.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#f)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="9.564" rx="2" stroke="#fff" stroke-width="2" width="41" x="176" y="234"></rect>
|
|
||||||
<path d="M192.051 243.564h2.898v-4h-2.898v4Zm3.949-2.949h-5v4h5v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-2.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#i)"></path>
|
|
||||||
<path d="M180.051 243.564h8.898v-4h-8.898v4Zm9.949-2.949h-11v4h11v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-8.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#h)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="9.564" rx="2" stroke="#fff" stroke-width="2" width="41" x="130" y="248.718"></rect>
|
|
||||||
<path d="M134.051 258.282h8.898v-4h-8.898v4Zm9.949-2.949h-11v4h11v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-8.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#j)"></path>
|
|
||||||
<path d="M146.051 258.282h2.898v-4h-2.898v4Zm3.949-2.949h-5v4h5v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-2.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#k)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="9.564" rx="2" stroke="#fff" stroke-width="2" width="41" x="130" y="234"></rect>
|
|
||||||
<path d="M134.051 243.564h8.898v-4h-8.898v4Zm9.949-2.949h-11v4h11v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-8.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#l)"></path>
|
|
||||||
<path d="M146.051 243.564h2.898v-4h-2.898v4Zm3.949-2.949h-5v4h5v-4Zm-1.051 2.949a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-2.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#m)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="9.564" rx="2" stroke="#fff" stroke-width="2" width="41" x="130" y="263.436"></rect>
|
|
||||||
<path d="M134.051 273h8.898v-4h-8.898v4Zm9.949-2.949h-11v4h11v-4ZM142.949 273a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-8.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#n)"></path>
|
|
||||||
<path d="M146.051 273h2.898v-4h-2.898v4Zm3.949-2.949h-5v4h5v-4ZM148.949 273a.95.95 0 0 1-.949-.949h4a3.051 3.051 0 0 0-3.051-3.051v4Zm-2.898-4a3.051 3.051 0 0 0-3.051 3.051h4a.95.95 0 0 1-.949.949v-4Z" fill="#fff" mask="url(#o)"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g class="group drives twodotfive">
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="5" rx="2" stroke="#fff" stroke-width="2" width="41" x="183" y="36"></rect>
|
|
||||||
<path d="M187 41h9v-4h-9v4Zm10-3h-11v4h11v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-9-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#p)"></path>
|
|
||||||
<path d="M199 41h3v-4h-3v4Zm4-3h-5v4h5v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-3-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#q)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="5" rx="2" stroke="#fff" stroke-width="2" width="41" x="183" y="47"></rect>
|
|
||||||
<path d="M187 52h9v-4h-9v4Zm10-3h-11v4h11v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-9-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#r)"></path>
|
|
||||||
<path d="M199 52h3v-4h-3v4Zm4-3h-5v4h5v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-3-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#s)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="5" rx="2" stroke="#fff" stroke-width="2" width="41" x="183" y="58"></rect>
|
|
||||||
<path d="M187 63h9v-4h-9v4Zm10-3h-11v4h11v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-9-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#t)"></path>
|
|
||||||
<path d="M199 63h3v-4h-3v4Zm4-3h-5v4h5v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-3-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#u)"></path>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<rect height="5" rx="2" stroke="#fff" stroke-width="2" width="41" x="183" y="69"></rect>
|
|
||||||
<path d="M187 74h9v-4h-9v4Zm10-3h-11v4h11v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-9-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#v)"></path>
|
|
||||||
<path d="M199 74h3v-4h-3v4Zm4-3h-5v4h5v-4Zm-1 3a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Zm-3-4a3 3 0 0 0-3 3h4a1 1 0 0 1-1 1v-4Z" fill="#fff" mask="url(#w)"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g data-target="mb" class="mb">
|
|
||||||
<rect height="176" rx="3.5" stroke="#fff" stroke-width="3" width="152" x="22.5" y="25.5"></rect>
|
|
||||||
<path d="m31.19 123.309-.01.007-7.32 5.782c-1.968 1.554-4.86.153-4.86-2.354V42a3 3 0 0 1 3-3h15a3 3 0 0 1 3 3v73.048c0 .931-.432 1.81-1.17 2.377l-7.64 5.884Z" fill="#fff"></path>
|
|
||||||
<path d="m31.19 123.309-.01.007-7.32 5.782c-1.968 1.554-4.86.153-4.86-2.354V42a3 3 0 0 1 3-3h15a3 3 0 0 1 3 3v73.048c0 .931-.432 1.81-1.17 2.377l-7.64 5.884Z" stroke="#fff" stroke-width="2"></path>
|
|
||||||
<g class="chips">
|
|
||||||
<rect fill="#fff" height="13" rx="2" width="13" x="23" y="44"></rect>
|
|
||||||
<rect fill="#fff" height="13" rx="2" width="13" x="23" y="61"></rect>
|
|
||||||
<rect fill="#fff" height="13" rx="2" width="13" x="23" y="78"></rect>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g class="group drives mdottwo">
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<path d="M129.535 123.781v2h2v-2h-2Zm0-.906h2v-2h-2v2Zm0-1.094h-1.082v4h1.082v-4Zm-1.082 3.094h1.082v-4h-1.082v4Zm-.918-2v.906h4v-.906h-4Zm2.465.453c0 .854-.693 1.547-1.547 1.547v-4a2.453 2.453 0 0 0-2.453 2.453h4Zm-1.547-1.547c.854 0 1.547.693 1.547 1.547h-4a2.453 2.453 0 0 0 2.453 2.453v-4Z" fill="#fff" mask="url(#x)"></path>
|
|
||||||
<path d="M129.535 115.625v2h2v-2h-2Zm0-.906h2v-2h-2v2Zm0-1.094h-1.082v4h1.082v-4Zm-1.082 3.094h1.082v-4h-1.082v4Zm-.918-2v.906h4v-.906h-4Zm2.465.453c0 .854-.693 1.547-1.547 1.547v-4a2.453 2.453 0 0 0-2.453 2.453h4Zm-1.547-1.547c.854 0 1.547.693 1.547 1.547h-4a2.453 2.453 0 0 0 2.453 2.453v-4Z" fill="#fff" mask="url(#y)"></path>
|
|
||||||
<path d="M129.535 121.969v2h2v-2h-2Zm0-5.438h2v-2h-2v2Zm0 3.438H129v4h.535v-4Zm.465 1v-3.438h-4v3.438h4Zm-1-2.438h.535v-4H129v4Zm-1.465-2v5.438h4v-5.438h-4Zm2.465 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 2.438a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#z)"></path>
|
|
||||||
<rect height="12.5" rx="2" stroke="#fff" stroke-width="2" width="29.465" x="130.535" y="113"></rect>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<path d="M92.535 123.781v2h2v-2h-2Zm0-.906h2v-2h-2v2Zm0-1.094h-1.082v4h1.082v-4Zm-1.082 3.094h1.082v-4h-1.082v4Zm-.918-2v.906h4v-.906h-4Zm2.465.453c0 .854-.693 1.547-1.547 1.547v-4A2.453 2.453 0 0 0 89 123.328h4Zm-1.547-1.547c.854 0 1.547.693 1.547 1.547h-4a2.453 2.453 0 0 0 2.453 2.453v-4Z" fill="#fff" mask="url(#A)"></path>
|
|
||||||
<path d="M92.535 115.625v2h2v-2h-2Zm0-.906h2v-2h-2v2Zm0-1.094h-1.082v4h1.082v-4Zm-1.082 3.094h1.082v-4h-1.082v4Zm-.918-2v.906h4v-.906h-4Zm2.465.453c0 .854-.693 1.547-1.547 1.547v-4A2.453 2.453 0 0 0 89 115.172h4Zm-1.547-1.547c.854 0 1.547.693 1.547 1.547h-4a2.453 2.453 0 0 0 2.453 2.453v-4Z" fill="#fff" mask="url(#B)"></path>
|
|
||||||
<path d="M92.535 121.969v2h2v-2h-2Zm0-5.438h2v-2h-2v2Zm0 3.438H92v4h.535v-4Zm.465 1v-3.438h-4v3.438h4Zm-1-2.438h.535v-4H92v4Zm-1.465-2v5.438h4v-5.438h-4Zm2.465 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 2.438a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#C)"></path>
|
|
||||||
<rect height="12.5" rx="2" stroke="#fff" stroke-width="2" width="29.465" x="93.535" y="113"></rect>
|
|
||||||
</g>
|
|
||||||
<g data-target="drive" class="drive">
|
|
||||||
<path d="M55.535 123.781v2h2v-2h-2Zm0-.906h2v-2h-2v2Zm0-1.094h-1.082v4h1.082v-4Zm-1.082 3.094h1.082v-4h-1.082v4Zm-.918-2v.906h4v-.906h-4Zm2.465.453c0 .854-.693 1.547-1.547 1.547v-4A2.453 2.453 0 0 0 52 123.328h4Zm-1.547-1.547c.854 0 1.547.693 1.547 1.547h-4a2.453 2.453 0 0 0 2.453 2.453v-4Z" fill="#fff" mask="url(#G)"></path>
|
|
||||||
<path d="M55.535 115.625v2h2v-2h-2Zm0-.906h2v-2h-2v2Zm0-1.094h-1.082v4h1.082v-4Zm-1.082 3.094h1.082v-4h-1.082v4Zm-.918-2v.906h4v-.906h-4Zm2.465.453c0 .854-.693 1.547-1.547 1.547v-4A2.453 2.453 0 0 0 52 115.172h4Zm-1.547-1.547c.854 0 1.547.693 1.547 1.547h-4a2.453 2.453 0 0 0 2.453 2.453v-4Z" fill="#fff" mask="url(#H)"></path>
|
|
||||||
<path d="M55.535 121.969v2h2v-2h-2Zm0-5.438h2v-2h-2v2Zm0 3.438H55v4h.535v-4Zm.465 1v-3.438h-4v3.438h4Zm-1-2.438h.535v-4H55v4Zm-1.465-2v5.438h4v-5.438h-4Zm2.465 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 2.438a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#I)"></path>
|
|
||||||
<rect height="12.5" rx="2" stroke="#fff" stroke-width="2" width="29.465" x="56.535" y="113"></rect>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g data-target="gpu" class="gpu">
|
|
||||||
<path d="M59 148h86" stroke="#fff" stroke-linecap="round"></path>
|
|
||||||
<path d="M59.5 136a1.5 1.5 0 0 0-3 0h3Zm0 8v-8h-3v8h3Z" fill="#fff"></path>
|
|
||||||
<path d="M68 143v-2a1 1 0 0 1 1-1h29a1 1 0 0 1 1 1v2H68Z" mask="url(#D)" stroke="#fff" stroke-width="3"></path>
|
|
||||||
<path d="M101 143v-2a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v2h-3Z" mask="url(#E)" stroke="#fff" stroke-width="3"></path>
|
|
||||||
<path d="M64 143v-2a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v2h-3Z" mask="url(#F)" stroke="#fff" stroke-width="3"></path>
|
|
||||||
<path d="M55 147a3 3 0 0 1 3-3h88a3 3 0 0 1 3 3v.525c0 .237-.028.473-.083.703l-2.764 11.474a3.001 3.001 0 0 1-2.917 2.298H58a3 3 0 0 1-3-3v-12Z" stroke="#fff" stroke-width="2"></path>
|
|
||||||
</g>
|
|
||||||
<g class="group drams">
|
|
||||||
<g data-target="dram" class="dram">
|
|
||||||
<path d="M60 38v-2h-2v2h2Zm0 35h-2v2h2v-2Zm0-33h1v-4h-1v4Zm0-1v33h4V39h-4Zm1 32h-1v4h1v-4Zm1 2V38h-4v35h4Zm-2-1a1 1 0 0 1 1-1v4a3 3 0 0 0 3-3h-4Zm1-32a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Z" fill="#fff" mask="url(#L)"></path>
|
|
||||||
<path d="M60 76v-2h-2v2h2Zm0 24h-2v2h2v-2Zm0-22h1v-4h-1v4Zm0-1v22h4V77h-4Zm1 21h-1v4h1v-4Zm1 2V76h-4v24h4Zm-2-1a1 1 0 0 1 1-1v4a3 3 0 0 0 3-3h-4Zm1-21a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Z" fill="#fff" mask="url(#M)"></path>
|
|
||||||
<rect height="66" rx="2" stroke="#fff" stroke-width="2" width="4" x="55" y="36"></rect>
|
|
||||||
</g>
|
|
||||||
<g data-target="dram" class="dram">
|
|
||||||
<path d="M76 38v-2h-2v2h2Zm0 35h-2v2h2v-2Zm0-33h1v-4h-1v4Zm0-1v33h4V39h-4Zm1 32h-1v4h1v-4Zm1 2V38h-4v35h4Zm-2-1a1 1 0 0 1 1-1v4a3 3 0 0 0 3-3h-4Zm1-32a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Z" fill="#fff" mask="url(#J)"></path>
|
|
||||||
<path d="M76 76v-2h-2v2h2Zm0 24h-2v2h2v-2Zm0-22h1v-4h-1v4Zm0-1v22h4V77h-4Zm1 21h-1v4h1v-4Zm1 2V76h-4v24h4Zm-2-1a1 1 0 0 1 1-1v4a3 3 0 0 0 3-3h-4Zm1-21a1 1 0 0 1-1-1h4a3 3 0 0 0-3-3v4Z" fill="#fff" mask="url(#K)"></path>
|
|
||||||
<rect height="66" rx="2" stroke="#fff" stroke-width="2" width="4" x="71" y="36"></rect>
|
|
||||||
</g>
|
|
||||||
<g data-target="dram" class="dram">
|
|
||||||
<path d="M137 100v2h2v-2h-2Zm0-35h2v-2h-2v2Zm0 33h-1v4h1v-4Zm0 1V66h-4v33h4Zm-1-32h1v-4h-1v4Zm-1-2v35h4V65h-4Zm2 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 32a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#N)"></path>
|
|
||||||
<path d="M137 62v2h2v-2h-2Zm0-24h2v-2h-2v2Zm0 22h-1v4h1v-4Zm0 1V39h-4v22h4Zm-1-21h1v-4h-1v4Zm-1-2v24h4V38h-4Zm2 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 21a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#O)"></path>
|
|
||||||
<rect height="66" rx="2" stroke="#fff" stroke-width="2" transform="rotate(180 142 102)" width="4" x="142" y="102"></rect>
|
|
||||||
</g>
|
|
||||||
<g data-target="dram" class="dram">
|
|
||||||
<path d="M155 100v2h2v-2h-2Zm0-35h2v-2h-2v2Zm0 33h-1v4h1v-4Zm0 1V66h-4v33h4Zm-1-32h1v-4h-1v4Zm-1-2v35h4V65h-4Zm2 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 32a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#P)"></path>
|
|
||||||
<path d="M155 62v2h2v-2h-2Zm0-24h2v-2h-2v2Zm0 22h-1v4h1v-4Zm0 1V39h-4v22h4Zm-1-21h1v-4h-1v4Zm-1-2v24h4V38h-4Zm2 1a1 1 0 0 1-1 1v-4a3 3 0 0 0-3 3h4Zm-1 21a1 1 0 0 1 1 1h-4a3 3 0 0 0 3 3v-4Z" fill="#fff" mask="url(#Q)"></path>
|
|
||||||
<rect height="66" rx="2" stroke="#fff" stroke-width="2" transform="rotate(180 160 102)" width="4" x="160" y="102"></rect>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g data-target="cpu" class="cpu">
|
|
||||||
<path d="M98 56a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1h-3ZM113 56a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1h-3ZM103 56a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1h-3ZM108 56a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1h-3ZM116 82a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1h3ZM101 82a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1h3ZM111 82a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1h3ZM106 82a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1h3ZM120 60a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1v-3ZM120 75a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1v-3ZM120 65a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1v-3ZM120 70a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1v-3ZM94 78a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1v3ZM94 63a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1v3ZM94 73a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1v3ZM94 68a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1v3Z" fill="#fff"></path>
|
|
||||||
<rect height="24" rx="3" stroke="#fff" stroke-width="2" width="24" x="95" y="57"></rect>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 19 KiB |
BIN
public/assets/media/img/preview-deltaco.avif
Normal file
BIN
public/assets/media/img/preview-deltaco.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/media/img/preview-genemate.avif
Normal file
BIN
public/assets/media/img/preview-genemate.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/media/img/preview-icellate.avif
Normal file
BIN
public/assets/media/img/preview-icellate.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
|
@ -1,18 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Vegvisir\Path;
|
use VLW\Database\Models\Messages\Message;
|
||||||
|
use VLW\Database\Tables\Messages\Messages;
|
||||||
|
|
||||||
use VLW\API\{Client, Endpoints};
|
require_once VV::root("src/Database/Models/Messages/Message.php");
|
||||||
use VLW\Database\Tables\Messages\MessagesTable;
|
|
||||||
|
|
||||||
require_once VV::root("src/API/API.php");
|
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
|
||||||
require_once VV::root("src/Database/Tables/Messages/Messages.php");
|
require_once VV::root("src/Database/Tables/Messages/Messages.php");
|
||||||
|
|
||||||
$date = new class extends DateTimeImmutable {
|
$date = new class extends DateTimeImmutable {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
// Set DateTime for configured timezone
|
// Set DateTime for configured timezone
|
||||||
parent::__construct("now", new DateTimeZone($_ENV["client_time_available"]["time_zone"]));
|
parent::__construct("now", new DateTimeZone($_ENV["config_time_available"]["time_zone"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return current hour in 24-hour format
|
// Return current hour in 24-hour format
|
||||||
|
@ -22,20 +19,20 @@
|
||||||
|
|
||||||
// Returns true if current time is between available from and to hours
|
// Returns true if current time is between available from and to hours
|
||||||
public function is_available(): bool {
|
public function is_available(): bool {
|
||||||
return $this->hour() >= $_ENV["client_time_available"]["available_from_hour"] && $this->hour() < $_ENV["client_time_available"]["available_to_hour"];
|
return $this->hour() >= $_ENV["config_time_available"]["available_from_hour"] && $this->hour() < $_ENV["config_time_available"]["available_to_hour"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_estimated_reply_hours(): int {
|
public function get_estimated_reply_hours(): int {
|
||||||
// I'm available! Return the estimated reply time for that
|
// I'm available! Return the estimated reply time for that
|
||||||
if ($this->is_available()) {
|
if ($this->is_available()) {
|
||||||
return $_ENV["client_time_available"]["reply_average_hours"];
|
return $_ENV["config_time_available"]["reply_average_hours"];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->hour() < $_ENV["client_time_available"]["available_from_hour"]
|
return $this->hour() < $_ENV["config_time_available"]["available_from_hour"]
|
||||||
// Return hours past midnight until I become available (clamped to estimated reply hours)
|
// Return hours past midnight until I become available (clamped to estimated reply hours)
|
||||||
? max($_ENV["client_time_available"]["available_from_hour"] - $this->hour(), $_ENV["client_time_available"]["reply_average_hours"])
|
? max($_ENV["config_time_available"]["available_from_hour"] - $this->hour(), $_ENV["config_time_available"]["reply_average_hours"])
|
||||||
// Return hours before midnight until I become available (clamped to estimated reply hours)
|
// Return hours before midnight until I become available (clamped to estimated reply hours)
|
||||||
: max($_ENV["client_time_available"]["available_from_hour"] + (24 - $this->hour()), $_ENV["client_time_available"]["reply_average_hours"]);
|
: max($_ENV["config_time_available"]["available_from_hour"] + (24 - $this->hour()), $_ENV["config_time_available"]["reply_average_hours"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,9 +59,9 @@
|
||||||
<?= VV::embed("public/assets/media/line.svg") ?>
|
<?= VV::embed("public/assets/media/line.svg") ?>
|
||||||
<section class="pgp">
|
<section class="pgp">
|
||||||
<?= VV::embed("public/assets/media/icons/pin.svg") ?>
|
<?= VV::embed("public/assets/media/icons/pin.svg") ?>
|
||||||
<h3>encrypt your message with PGP</h3>
|
<h3>a note about encryption..</h3>
|
||||||
<p>Encryption is great, don't let certain <a href="https://web.archive.org/web/https://www.chatcontrol.eu">short-sighted politicians</a> fool you into thinking encryption is only for criminals. I encourage you to encrypt your message with PGP so we can aid normalization of platform-agnostic encryption for everyone. My key is available via <a href="https://wiki.gnupg.org/WKD" target="_blank" rel="noopener noreferer">WKD</a>, and it's also listed on the <a href="https://keys.openpgp.org/search?q=victor%40vlw.se" target="_blank" rel="noopener noreferer">openPGP key server</a>.</p>
|
<p>Please don't let certain <a href="https://web.archive.org/web/20250514040615/https://www.patrick-breyer.de/en/posts/chat-control/">short-sighted politicians</a> fool you into thinking encrypted text messages are only for <a href="https://en.wikipedia.org/wiki/Nothing_to_hide_argument">"those with nothing to hide"</a>. I encourage you to encrypt your message with PGP so we can aid normalization of <i>platform-agnostic</i> encryption for everyone.</p>
|
||||||
<p>Here's the fingerprint for victor@vlw.se<br><strong class="fingerprint">DCE987311CB5D2A252F58951D0AD730E1057DFC6</strong></p>
|
<p>PGP fingerprint for victor@vlw.se:<br><strong class="fingerprint">DCE987311CB5D2A252F58951D0AD730E1057DFC6</strong></p>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a href="https://keys.openpgp.org/vks/v1/by-fingerprint/DCE987311CB5D2A252F58951D0AD730E1057DFC6"><button class="inline solid download">
|
<a href="https://keys.openpgp.org/vks/v1/by-fingerprint/DCE987311CB5D2A252F58951D0AD730E1057DFC6"><button class="inline solid download">
|
||||||
<p>download ASC</p>
|
<p>download ASC</p>
|
||||||
|
@ -77,25 +74,16 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="center fade">
|
<section class="center fade">
|
||||||
<p>And for reference, here is the fingerprint for my "non-personal" address info@vlw.se</p>
|
<blockquote>"Arguing that you don't care about the right to privacy because you have nothing to hide is no different than saying you don't care about free speech because you have nothing to say."</blockquote>
|
||||||
<p><strong class="fingerprint">DC603DA049903D707B7F1DB39AF727FB576F5A00</strong></p>
|
<p>- Edward Snowden, <cite><a href="https://en.wikiquote.org/wiki/Edward_Snowden#2015">Reddit (May 21, 2015)</a></cite></p>
|
||||||
</section>
|
</section>
|
||||||
<?= VV::embed("public/assets/media/line.svg") ?>
|
<?= VV::embed("public/assets/media/line.svg") ?>
|
||||||
|
|
||||||
<?php // Send message on POST request ?>
|
<?php // Send message on POST request ?>
|
||||||
<?php if ($_SERVER["REQUEST_METHOD"] === "POST"): ?>
|
<?php if ($_SERVER["REQUEST_METHOD"] === "POST"): ?>
|
||||||
|
<?php $message = Message::new($_POST[Messages::MESSAGE->name] ?? "", $_POST[Messages::EMAIL->name] ?? null); ?>
|
||||||
|
|
||||||
<?php
|
<?php if ($message->date_created): ?>
|
||||||
|
|
||||||
// Send message via API
|
|
||||||
$send = (new Client())->call(Endpoints::MESSAGES->value)->post([
|
|
||||||
MessagesTable::EMAIL->value => $_POST[MessagesTable::EMAIL->value],
|
|
||||||
MessagesTable::MESSAGE->value => $_POST[MessagesTable::MESSAGE->value]
|
|
||||||
]);
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<?php if ($send->ok): ?>
|
|
||||||
<section class="form-message sent">
|
<section class="form-message sent">
|
||||||
<h3>🙏 Message sent!</h3>
|
<h3>🙏 Message sent!</h3>
|
||||||
</section>
|
</section>
|
||||||
|
@ -103,8 +91,6 @@
|
||||||
<?php // Show response body from endpoint as error if request failed ?>
|
<?php // Show response body from endpoint as error if request failed ?>
|
||||||
<section class="form-message error">
|
<section class="form-message error">
|
||||||
<h3>😟 Oh no, something went wrong</h3>
|
<h3>😟 Oh no, something went wrong</h3>
|
||||||
<p>Response from API:</p>
|
|
||||||
<pre><?= $send->output() ?></pre>
|
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
@ -113,11 +99,11 @@
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input-group>
|
<input-group>
|
||||||
<label>your email (optional)</label>
|
<label>your email (optional)</label>
|
||||||
<input type="email" name="<?= MessagesTable::EMAIL->value ?>" placeholder="nissehult@example.com" autocomplete="off"></input>
|
<input type="email" name="<?= Messages::EMAIL->name ?>" placeholder="nissehult@example.com" autocomplete="off"></input>
|
||||||
</input-group>
|
</input-group>
|
||||||
<input-group>
|
<input-group>
|
||||||
<label title="this field is required">your message (required)</label>
|
<label title="this field is required">your message (required)</label>
|
||||||
<textarea name="<?= MessagesTable::MESSAGE->value ?>" required placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed molestie dignissim mauris vel dignissim. Sed et aliquet odio, id egestas libero. Vestibulum ut dui a turpis aliquam hendrerit id et dui. Morbi eu tristique quam, sit amet dictum felis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ac nibh a ex accumsan ullamcorper non quis eros. Nam at suscipit lacus. Nullam placerat semper sapien, vitae aliquet nisl elementum a. Duis viverra quam eros, eu vestibulum quam egestas sit amet. Duis lobortis varius malesuada. Mauris in fringilla mi. "></textarea>
|
<textarea name="<?= Messages::MESSAGE->name ?>" required placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed molestie dignissim mauris vel dignissim. Sed et aliquet odio, id egestas libero. Vestibulum ut dui a turpis aliquam hendrerit id et dui. Morbi eu tristique quam, sit amet dictum felis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ac nibh a ex accumsan ullamcorper non quis eros. Nam at suscipit lacus. Nullam placerat semper sapien, vitae aliquet nisl elementum a. Duis viverra quam eros, eu vestibulum quam egestas sit amet. Duis lobortis varius malesuada. Mauris in fringilla mi. "></textarea>
|
||||||
</input-group>
|
</input-group>
|
||||||
<button class="inline solid">
|
<button class="inline solid">
|
||||||
<?= VV::embed("public/assets/media/icons/email.svg") ?>
|
<?= VV::embed("public/assets/media/icons/email.svg") ?>
|
||||||
|
@ -126,5 +112,4 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
<script ><?= VV::js("public/assets/js/pages/contact") ?></script>
|
||||||
<script type="module"><?= VV::js("public/assets/js/pages/contact") ?></script>
|
|
|
@ -1,2 +1,2 @@
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Allow: /
|
|
@ -1,26 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use VLW\Database\Models\Search\Search;
|
use VLW\Database\Models\Search\Search;
|
||||||
use const VLW\{ICONS_DIR, DEFAULT_BUTTON_ICON};
|
use VLW\Database\Tables\Search\{Search as SearchTable, SearchTypeEnum};
|
||||||
use VLW\Database\Tables\Search\{SearchTable, SearchCategoryEnum};
|
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
require_once VV::root("src/Consts.php");
|
||||||
require_once VV::root("src/Database/Tables/Search/Search.php");
|
require_once VV::root("src/Database/Tables/Search/Search.php");
|
||||||
require_once VV::root("src/Database/Models/Search/Search.php");
|
require_once VV::root("src/Database/Models/Search/Search.php");
|
||||||
|
|
||||||
|
const GET_KEY_QUERY = "q";
|
||||||
|
const LIMIT_RESULTS = 10;
|
||||||
|
const MIN_QUERY_LENGTH = 2;
|
||||||
|
|
||||||
$search = new class extends Search {
|
$search = new class extends Search {
|
||||||
public function __construct() {}
|
public readonly string $query;
|
||||||
|
public readonly array $results;
|
||||||
|
|
||||||
public static function get_query(): ?string {
|
public function __construct() {
|
||||||
return $_GET[SearchTable::QUERY->value] ?? null;
|
$this->query = $_GET[GET_KEY_QUERY] ?? "";
|
||||||
}
|
$this->results = strlen($this->query) >= MIN_QUERY_LENGTH ? parent::query($this->query, limit: LIMIT_RESULTS) : [];
|
||||||
|
|
||||||
public static function get_category(): ?SearchCategoryEnum {
|
|
||||||
return SearchCategoryEnum::tryFromName($_GET[SearchTable::CATEGORY->value] ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function search(): array {
|
|
||||||
return parent::all([SearchTable::QUERY->value => self::get_query()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,54 +25,54 @@
|
||||||
<style><?= VV::css("public/assets/css/pages/search") ?></style>
|
<style><?= VV::css("public/assets/css/pages/search") ?></style>
|
||||||
<section class="search">
|
<section class="search">
|
||||||
<form>
|
<form>
|
||||||
<input name="<?= SearchTable::QUERY->value ?>" type="search" placeholder="search vlw.se..." value="<?= $search::get_query() ?>">
|
<input name="<?= GET_KEY_QUERY ?>" type="search" placeholder="search vlw.se..." value="<?= $search->query ?>">
|
||||||
<select name="<?= SearchTable::CATEGORY->value ?>">
|
<select name="<?= SearchTable::TYPE->value ?>">
|
||||||
<option value="null">All</option>
|
<option value="null">All</option>
|
||||||
<optgroup label="Categories">
|
<optgroup label="Types">
|
||||||
|
|
||||||
<?php foreach (SearchCategoryEnum::names() as $category): ?>
|
<?php foreach (SearchTypeEnum::names() as $type): ?>
|
||||||
<?php $category = SearchCategoryEnum::fromName($category); ?>
|
<?php $type = SearchTypeEnum::fromName($type); ?>
|
||||||
<option value="<?= $category->name ?>" <?= $search::get_category() === $category ? "selected" : "" ?>><?= ucfirst(strtolower($category->name)) ?></option>
|
<option value="<?= $type->name ?>"><?= ucfirst(strtolower($type->name)) ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
<button type="submit" class="inline solid"><?= VV::embed(ICONS_DIR . "search.svg") ?></button>
|
<button type="submit" class="inline solid"><?= VV::embed("public/assets/media/icons/search.svg") ?></button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if (array_key_exists(SearchTable::QUERY->value, $_GET)): ?>
|
<?php if (isset($_GET[GET_KEY_QUERY])): ?>
|
||||||
|
|
||||||
<?php if ($search->search()): ?>
|
<?php if ($search->results): ?>
|
||||||
<section class="stats">
|
<section class="stats">
|
||||||
<p><?= count($search->search()) ?> result(s)</p>
|
<p><?= count($search->results) ?> result(s)</p>
|
||||||
<a href="/search?query=<?= $search::get_query() ?>"><button class="inline solid">
|
<a href="/search?query=<?= $search->query ?>"><button class="inline solid">
|
||||||
<?= VV::embed(ICONS_DIR . "search.svg") ?>
|
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
||||||
<p>Advanced search</p>
|
<p>Advanced search</p>
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php foreach ($search->search() as $result): ?>
|
<?php foreach ($search->results as $result): ?>
|
||||||
<section class="result" data-id="<?= $result->id ?>">
|
<section class="result" data-id="<?= $result->id ?>">
|
||||||
<a href="<?= $result->href() ?>"><button class="inline">
|
<a href="<?= $result->href ?>"><button class="inline">
|
||||||
<div>
|
<div>
|
||||||
<h2><?= $result->title() ?></h2>
|
<h3><?= $result->title ?></h3>
|
||||||
<p><?= $result->summary() ?></p>
|
<p><?= $result->text ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</section>
|
</section>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php switch (strlen($search::get_query())): default: ?>
|
<?php switch (strlen($search->query)): default: ?>
|
||||||
<section class="stats">
|
<section class="stats">
|
||||||
<p>0 result(s)</p>
|
<p>0 result(s)</p>
|
||||||
<a href="/search?query=<?= $search::get_query() ?>"><button class="inline solid">
|
<a href="/search?query=<?= $search->query ?>"><button class="inline solid">
|
||||||
<?= VV::embed(ICONS_DIR . "search.svg") ?>
|
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
||||||
<p>Advanced search</p>
|
<p>Advanced search</p>
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</section>
|
</section>
|
||||||
<section class="center">
|
<section class="center">
|
||||||
|
@ -86,15 +83,15 @@
|
||||||
|
|
||||||
<?php case 0: ?>
|
<?php case 0: ?>
|
||||||
<section class="center">
|
<section class="center">
|
||||||
<?= VV::embed(ICONS_DIR . "search.svg") ?>
|
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
||||||
<p>Start typing to search</p>
|
<p>Start typing to search</p>
|
||||||
</section>
|
</section>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
|
|
||||||
<?php case 1: ?>
|
<?php case (MIN_QUERY_LENGTH - 1): ?>
|
||||||
<section class="center">
|
<section class="center">
|
||||||
<?= VV::embed(ICONS_DIR . "search.svg") ?>
|
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
||||||
<p>Almost, type at least two letters to search</p>
|
<p>Almost there, type at least two letters to search</p>
|
||||||
</section>
|
</section>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
|
|
||||||
|
@ -104,7 +101,7 @@
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<section class="center">
|
<section class="center">
|
||||||
<?= VV::embed(ICONS_DIR . "search.svg") ?>
|
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
||||||
<p>Start typing to search</p>
|
<p>Start typing to search</p>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
|
@ -1,4 +1,26 @@
|
||||||
# If you would like to report a security issue
|
-----BEGIN PGP SIGNED MESSAGE-----
|
||||||
# you may report it to me via email or via Matrix
|
Hash: SHA512
|
||||||
|
|
||||||
|
Contact: mailto:victor@vlw.se
|
||||||
Contact: https://vlw.se/contact
|
Contact: https://vlw.se/contact
|
||||||
|
Expires: 2026-02-21T00:00:00.000Z
|
||||||
Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/DCE987311CB5D2A252F58951D0AD730E1057DFC6
|
Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/DCE987311CB5D2A252F58951D0AD730E1057DFC6
|
||||||
|
Preferred-Languages: en, se
|
||||||
|
Canonical: https://vlw.se/.well-known/security.txt
|
||||||
|
Canonical: https://vlw.se/security.txt
|
||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQIzBAEBCgAdFiEE3OmHMRy10qJS9YlR0K1zDhBX38YFAmfIO0EACgkQ0K1zDhBX
|
||||||
|
38bcng//awdqQc28s9bPxeLf7nl1acWDJkywu5UwRoyVrSIhCKUfri1E9VseWf2p
|
||||||
|
T/SzipVgxfpahOdmA9Cf1o5zTHeHpynocw77AwHvPurjSUw9/75vh+AlATEb8qrQ
|
||||||
|
Aerk5f3nY2E+LFn7HC/CXdYq1ywoHn7ZEmxjIXNdrKyYlhpw3m75ZTOtc2iWjskE
|
||||||
|
xzv5zKIb5E6+4bjJwmSuRgEKqxytHX2RxwsNknp/I/YtrP2zfkyabNkFzFyqiUfZ
|
||||||
|
BqzSWHlZqqeNAuNBtg0XS31NetOdl43YEF9jt0uNj+En/n4ozqiRhJwT85Xn6F9F
|
||||||
|
tUpwKb2VTmEgwyN99dVQ/FgMpLN5gvjFvUcwGHkoYe8R1uPRmY9bwkTzmv1pNX56
|
||||||
|
NDWUacqz1LddV4Ll55UTTJQ+l43BS/cB5wv/HY5Ts/gMdqIRevHy5QoJL40pebE0
|
||||||
|
me9ItLzTyMSNaJJsrWUoHpF46cpha+ZtO2hXz2BraDymZoSUhdqjlFVplrlMOlwa
|
||||||
|
KpxRHqT6XHdGEP4fcOkKNRHh5MCI0PcO1zFiP1m4iuXUdyeAZFDP/V65s/yLCGfT
|
||||||
|
qyFokyTGpeSSOj5eIVKSZPl25QUkPqJ2JrEnavkfPhIKq5PNr68wk6eU8O10qA/C
|
||||||
|
AqtM/Sp254+h615KPKUt2GSIBdAPcpRWD7ScBHO03M5sads0eSM=
|
||||||
|
=h09S
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
//--><!]]>
|
//--><!]]>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<?php // Bootstrapping ?>
|
|
||||||
<style><?= VV::css("public/assets/css/fonts") ?></style>
|
<style><?= VV::css("public/assets/css/fonts") ?></style>
|
||||||
<style><?= VV::css("public/assets/css/shell") ?></style>
|
<style><?= VV::css("public/assets/css/shell") ?></style>
|
||||||
|
|
||||||
|
@ -53,7 +52,7 @@
|
||||||
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
||||||
<p>search vlw.se...</p>
|
<p>search vlw.se...</p>
|
||||||
</button>
|
</button>
|
||||||
<button class="logo" vv="/"><?= VV::embed("public/assets/media/vw.svg") ?></button>
|
<button class="logo"><?= VV::embed("public/assets/media/vw.svg") ?></button>
|
||||||
<searchbox>
|
<searchbox>
|
||||||
<input type="search" autocomplete="off" placeholder="search vlw.se...">
|
<input type="search" autocomplete="off" placeholder="search vlw.se...">
|
||||||
<button class="close searchbox-close"><?= VV::embed("public/assets/media/icons/close.svg") ?></button>
|
<button class="close searchbox-close"><?= VV::embed("public/assets/media/icons/close.svg") ?></button>
|
||||||
|
@ -69,8 +68,7 @@
|
||||||
</div>
|
</div>
|
||||||
</search-results>
|
</search-results>
|
||||||
|
|
||||||
<?php // Bootstrapping ?>
|
|
||||||
<?= VV::init() ?>
|
<?= VV::init() ?>
|
||||||
<script type="module"><?= VV::js("public/assets/js/shell") ?></script>
|
<script><?= VV::js("public/assets/js/shell") ?></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
12
public/work/archive.php
Normal file
12
public/work/archive.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<style><?= VV::css("public/assets/css/pages/work/archive") ?></style>
|
||||||
|
<section>
|
||||||
|
<h1>This is an archived website!</h1>
|
||||||
|
<p>You're about to view an archived version of this website on my domain. Everything you see, and all features that are available on the archived website have been recreated to simulate the real behavior as closely as possible. Some features can unfortunately not be simulated properly and have been disabled completely. No actions you take on this website have any real effects.</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<a href="<?= $_GET["href"] ?? "" ?>" target="_blank"><button class="inline solid">
|
||||||
|
<p>Proceed to website</p>
|
||||||
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
|
</button></a>
|
||||||
|
</section>
|
||||||
|
<script><?= VV::js("public/assets/js/pages/work/archive") ?></script>
|
|
@ -1,13 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use VLW\Database\Models\Work\Work;
|
use VLW\Database\Models\Work\Work;
|
||||||
use const VLW\{TIMELINE_PREVIEW_LIMIT_PARAM, TIMELINE_PREVIEW_LIMIT_COUNT};
|
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
|
||||||
require_once VV::root("src/Database/Models/Work/Work.php");
|
require_once VV::root("src/Database/Models/Work/Work.php");
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<style><?= VV::css("public/assets/css/pages/work") ?></style>
|
<style><?= VV::css("public/assets/css/pages/work/index") ?></style>
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="item vegvisir">
|
<div class="item vegvisir">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
@ -15,7 +13,7 @@
|
||||||
<?= VV::embed("public/assets/media/icons/vegvisir.svg") ?>
|
<?= VV::embed("public/assets/media/icons/vegvisir.svg") ?>
|
||||||
<h1>vegvisir</h1>
|
<h1>vegvisir</h1>
|
||||||
</div>
|
</div>
|
||||||
<p><?= (new Work("vlw/vegvisir"))->summary() ?></p>
|
<p><?= Work::from("vlw/vegvisir")->summary ?></p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="https://vegvisir.vlw.se"><button class="inline solid">
|
<a href="https://vegvisir.vlw.se"><button class="inline solid">
|
||||||
<p>read more</p>
|
<p>read more</p>
|
||||||
|
@ -30,7 +28,7 @@
|
||||||
<?= VV::embed("public/assets/media/icons/reflect.svg") ?>
|
<?= VV::embed("public/assets/media/icons/reflect.svg") ?>
|
||||||
<h1>reflect</h1>
|
<h1>reflect</h1>
|
||||||
</div>
|
</div>
|
||||||
<p><?= (new Work("vlw/reflect"))->summary() ?></p>
|
<p><?= Work::from("vlw/reflect")->summary ?></p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="https://reflect.vlw.se"><button class="inline solid">
|
<a href="https://reflect.vlw.se"><button class="inline solid">
|
||||||
<p>read more</p>
|
<p>read more</p>
|
||||||
|
@ -49,20 +47,8 @@
|
||||||
<p>Can I put my own website here, is that cheating? Maybe, but I think this site counts as the most important thing I've personally created. I've only used my own libraries and frameworks to create this website, so it kind of works as a live demonstration of many of my web projects bundled together.</p>
|
<p>Can I put my own website here, is that cheating? Maybe, but I think this site counts as the most important thing I've personally created. I've only used my own libraries and frameworks to create this website, so it kind of works as a live demonstration of many of my web projects bundled together.</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="https://codeberg.org/vlw/vlw.se"><button class="inline">
|
<a href="https://codeberg.org/vlw/vlw.se"><button class="inline">
|
||||||
<p>read more</p>
|
<?= VV::embed("public/assets/media/icons/codeberg.svg") ?>
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
<p>view source</p>
|
||||||
</button></a>
|
|
||||||
</div>
|
|
||||||
</featured-item>
|
|
||||||
<featured-item>
|
|
||||||
<div class="title">
|
|
||||||
<?= VV::embed("public/assets/media/icons/vw.svg") ?>
|
|
||||||
</div>
|
|
||||||
<h3>Silly dabbles</h3>
|
|
||||||
<p>I create silly things for fun to challenge myself sometimes, and putting them all on the timeline is not right. So I made an appropriately-themed and named page to highlight most of my "what if I could" projects.</p>
|
|
||||||
<div class="actions">
|
|
||||||
<a href="/playground"><button class="inline">
|
|
||||||
<p>playground</p>
|
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +58,7 @@
|
||||||
<?= VV::embed("public/assets/media/icons/repo.svg") ?>
|
<?= VV::embed("public/assets/media/icons/repo.svg") ?>
|
||||||
</div>
|
</div>
|
||||||
<h3>vlw/php-mysql</h3>
|
<h3>vlw/php-mysql</h3>
|
||||||
<p><?= (new Work("vlw/php-mysql"))->summary() ?></p>
|
<p><?= Work::from("vlw/php-mysql")->summary ?></p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="https://codeberg.org/vlw/php-mysql"><button class="inline">
|
<a href="https://codeberg.org/vlw/php-mysql"><button class="inline">
|
||||||
<?= VV::embed("public/assets/media/icons/codeberg.svg") ?>
|
<?= VV::embed("public/assets/media/icons/codeberg.svg") ?>
|
||||||
|
@ -83,36 +69,61 @@
|
||||||
</featured-item>
|
</featured-item>
|
||||||
<featured-item>
|
<featured-item>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<?= VV::embed("public/assets/media/icons/star.svg") ?>
|
<h3>🍰</h3>
|
||||||
|
</div>
|
||||||
|
<h3>Still Alive</h3>
|
||||||
|
<p>I recreated the end credits from the video game Portal using pure JavaScript and browser windows. It was created using my old [abandoned] animation library and some patience. It's not perfect, it notably has a few time-drifting issues.</p>
|
||||||
|
<div class="actions">
|
||||||
|
<a href="https://blob.vlw.se/0195b948-8cd3-7d7f-8b21-e992a621a4c1.webm" target="_blank"><button class="inline">
|
||||||
|
<?= VV::embed("public/assets/media/icons/star.svg") ?>
|
||||||
|
<p>demo video</p>
|
||||||
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
|
</button></a>
|
||||||
|
<a href="https://codeberg.org/vlw/still-alive" target="_blank"><button class="inline collapse">
|
||||||
|
<p>view source</p>
|
||||||
|
<?= VV::embed("public/assets/media/icons/codeberg.svg") ?>
|
||||||
|
</button></a>
|
||||||
|
</div>
|
||||||
|
</featured-item>
|
||||||
|
</section>
|
||||||
|
<section class="heading">
|
||||||
|
<h1>web highligts</h1>
|
||||||
|
</section>
|
||||||
|
<section class="featured">
|
||||||
|
<featured-item>
|
||||||
|
<div class="title">
|
||||||
|
<a href="/work/archive?href=https://icellate.srv.vlw.se"><img src="/assets/media/img/preview-icellate.avif"></a>
|
||||||
</div>
|
</div>
|
||||||
<h3>Website for iCellate Medical</h3>
|
<h3>Website for iCellate Medical</h3>
|
||||||
<p><?= (new Work("icellate/website"))->summary() ?></p>
|
<p><?= Work::from("icellate/website")->summary ?></p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/work/icellate/website"><button class="inline">
|
<a href="/work/archive?href=https://icellate.srv.vlw.se"><button class="inline">
|
||||||
<p>read more</p>
|
<?= VV::embed("public/assets/media/icons/star.svg") ?>
|
||||||
|
<p>preview</p>
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</div>
|
</div>
|
||||||
</featured-item>
|
</featured-item>
|
||||||
<featured-item>
|
<featured-item>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<?= VV::embed("public/assets/media/icons/star.svg") ?>
|
<a href="/work/archive?href=https://genemate.srv.vlw.se"><img src="/assets/media/img/preview-genemate.avif"></a>
|
||||||
</div>
|
</div>
|
||||||
<h3>Modernizing GeneMate by iCellate</h3>
|
<h3>Website for GeneMate by iCellate</h3>
|
||||||
<p><?= (new Work("icellate/genemate"))->summary() ?></p>
|
<p><?= Work::from("icellate/genemate")->summary ?></p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/work/icellate/genemate"><button class="inline">
|
<a href="/work/archive?href=https://genemate.srv.vlw.se"><button class="inline">
|
||||||
<p>read more</p>
|
<?= VV::embed("public/assets/media/icons/star.svg") ?>
|
||||||
|
<p>preview</p>
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</div>
|
</div>
|
||||||
</featured-item>
|
</featured-item>
|
||||||
<featured-item>
|
<featured-item>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<?= VV::embed("public/assets/media/icons/star.svg") ?>
|
<a href="/work/deltaco/asyncapp"><img src="/assets/media/img/preview-deltaco.avif"></a>
|
||||||
</div>
|
</div>
|
||||||
<h3>Custom pages for Deltaco AB</h3>
|
<h3>Campaign pages for Deltaco AB</h3>
|
||||||
<p><?= (new Work("deltaco/asyncapp"))->summary() ?></p>
|
<p><?= Work::from("deltaco/asyncapp")->summary ?></p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/work/deltaco/asyncapp"><button class="inline">
|
<a href="/work/deltaco/asyncapp"><button class="inline">
|
||||||
<p>read more</p>
|
<p>read more</p>
|
||||||
|
@ -124,7 +135,7 @@
|
||||||
<section class="heading">
|
<section class="heading">
|
||||||
<h1>latest projects</h1>
|
<h1>latest projects</h1>
|
||||||
</section>
|
</section>
|
||||||
<?= VV::include("public/work/timeline?" . http_build_query([TIMELINE_PREVIEW_LIMIT_PARAM => TIMELINE_PREVIEW_LIMIT_COUNT])) ?>
|
<?= VV::include("public/work/timeline", limit: 4) ?>
|
||||||
<section class="heading">
|
<section class="heading">
|
||||||
<a href="/work/timeline"><button class="inline solid">
|
<a href="/work/timeline"><button class="inline solid">
|
||||||
<p>view full timeline</p>
|
<p>view full timeline</p>
|
|
@ -1,42 +1,35 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use VLW\Database\Models\Work\Timeline;
|
use VLW\Database\Models\Work\Timeline;
|
||||||
use const VLW\{
|
|
||||||
ICONS_DIR,
|
|
||||||
DEFAULT_BUTTON_ICON,
|
|
||||||
TIMELINE_PREVIEW_LIMIT_PARAM
|
|
||||||
};
|
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
|
||||||
require_once VV::root("src/Database/Models/Work/Timeline.php");
|
require_once VV::root("src/Database/Models/Work/Timeline.php");
|
||||||
|
|
||||||
|
const ARG_KEY_LIMIT = "limit";
|
||||||
|
|
||||||
$timeline = new class extends Timeline {
|
$timeline = new class extends Timeline {
|
||||||
public function __construct() {}
|
public function __construct() {}
|
||||||
|
|
||||||
public static function ordered(): array {
|
public static function ordered(?int $limit = null): array {
|
||||||
// Get timeline list limit from search param if set
|
|
||||||
$limit = array_key_exists(TIMELINE_PREVIEW_LIMIT_PARAM, $_GET) ? (int) $_GET[TIMELINE_PREVIEW_LIMIT_PARAM] : null;
|
|
||||||
|
|
||||||
$timeline = [];
|
$timeline = [];
|
||||||
|
|
||||||
foreach (parent::all() as $idx => $item) {
|
foreach (parent::all() as $idx => $item) {
|
||||||
// Use year as the first dimension
|
// Use year as the first dimension
|
||||||
if (!array_key_exists($item->year(), $timeline)) {
|
if (!array_key_exists($item->year, $timeline)) {
|
||||||
$timeline[$item->year()] = [];
|
$timeline[$item->year] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// And month as the second dimension
|
// And month as the second dimension
|
||||||
if (!array_key_exists($item->month(), $timeline[$item->year()])) {
|
if (!array_key_exists($item->month, $timeline[$item->year])) {
|
||||||
$timeline[$item->year()][$item->month()] = [];
|
$timeline[$item->year][$item->month] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lastly, day as the third dimension
|
// Lastly, day as the third dimension
|
||||||
if (!array_key_exists($item->day(), $timeline[$item->year()][$item->month()])) {
|
if (!array_key_exists($item->day, $timeline[$item->year][$item->month])) {
|
||||||
$timeline[$item->year()][$item->month()][$item->day()] = [];
|
$timeline[$item->year][$item->month][$item->day] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append Work instance on Timeline object to the output array by year->month->day
|
// Append Work instance on Timeline object to the output array by year->month->day
|
||||||
$timeline[$item->year()][$item->month()][$item->day()][] = $item->work();
|
$timeline[$item->year][$item->month][$item->day][] = $item->work;
|
||||||
|
|
||||||
// Bail out here if we've reached the theshold for items to display
|
// Bail out here if we've reached the theshold for items to display
|
||||||
if ($limit && $idx === $limit) {
|
if ($limit && $idx === $limit) {
|
||||||
|
@ -58,7 +51,7 @@
|
||||||
<p>Codeberg</p>
|
<p>Codeberg</p>
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
<a href="https://git.vlw.se"><button class="inline">
|
<a href="https://git.vlw.se/explore/repos"><button class="inline">
|
||||||
<p>Forgejo</p>
|
<p>Forgejo</p>
|
||||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
|
@ -67,7 +60,7 @@
|
||||||
<section class="timeline">
|
<section class="timeline">
|
||||||
|
|
||||||
<?php // Get year int from key and array of months for current year ?>
|
<?php // Get year int from key and array of months for current year ?>
|
||||||
<?php foreach ($timeline::ordered() as $year => $months): ?>
|
<?php foreach ($timeline::ordered($args[ARG_KEY_LIMIT] ?? null) as $year => $months): ?>
|
||||||
<div class="year">
|
<div class="year">
|
||||||
<div class="track">
|
<div class="track">
|
||||||
<p><?= $year ?></p>
|
<p><?= $year ?></p>
|
||||||
|
@ -100,44 +93,39 @@
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
|
|
||||||
<?php foreach ($work->tags() as $tag): ?>
|
<?php foreach ($work->tags() as $tag): ?>
|
||||||
<p class="tag <?= $tag->label()->name ?>"><?= $tag->label()->name ?></p>
|
<p class="tag <?= $tag->label->name ?>"><?= $tag->label->name ?></p>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($work->title()): ?>
|
<?php if ($work->title): ?>
|
||||||
<h2><?= $work->title() ?></h2>
|
<h2><?= $work->title ?></h2>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<p><?= $work->summary() ?></p>
|
<p><?= $work->summary ?></p>
|
||||||
|
|
||||||
<div class="actions">
|
<?php if ($work->actions()): ?>
|
||||||
|
<div class="actions">
|
||||||
|
|
||||||
<?php if ($work->actions()): ?>
|
|
||||||
<?php foreach ($work->actions() as $action): ?>
|
<?php foreach ($work->actions() as $action): ?>
|
||||||
<a href="<?= $action->href() ?? "/work/{$work->id}" ?>"><button class="inline <?= implode(" ", $action->classes()) ?>">
|
<a href="<?= $action->href ?? "/work/{$work->id}" ?>"><button class="inline <?= $action->classlist ?>">
|
||||||
<?php if ($action->icon_prepended()): ?>
|
<?php if ($action->icon_prepend): ?>
|
||||||
<?= VV::embed(ICONS_DIR . $action->icon_prepended()) ?>
|
<?= VV::embed("public/assets/media/icons/" . $action->icon_prepend) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<p><?= $action->display_text() ?></p>
|
<p><?= $action->text ?></p>
|
||||||
|
|
||||||
<?php if ($action->icon_appended()): ?>
|
<?php if ($action->icon_append): ?>
|
||||||
<?= VV::embed(ICONS_DIR . $action->icon_appended()) ?>
|
<?= VV::embed("public/assets/media/icons/" . $action->icon_append) ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
|
||||||
<a href="<?= "/work/{$work->id}" ?>"><button class="inline">
|
|
||||||
<p>read more</p>
|
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
|
||||||
</button></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
@ -155,4 +143,3 @@
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<script><?= VV::js("assets/js/pages/work/timeline") ?></script>
|
|
|
@ -1,10 +1,3 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use const VLW\DEFAULT_BUTTON_ICON;
|
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
|
||||||
|
|
||||||
?>
|
|
||||||
<style><?= VV::css("public/assets/css/pages/work/wip") ?></style>
|
<style><?= VV::css("public/assets/css/pages/work/wip") ?></style>
|
||||||
<section class="disclaimer">
|
<section class="disclaimer">
|
||||||
<h1>Soon, very soon!</h1>
|
<h1>Soon, very soon!</h1>
|
||||||
|
@ -13,6 +6,6 @@
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
<a href="/work"><button class="inline">
|
<a href="/work"><button class="inline">
|
||||||
<p>to featured work</p>
|
<p>to featured work</p>
|
||||||
<?= VV::embed(DEFAULT_BUTTON_ICON) ?>
|
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||||
</button></a>
|
</button></a>
|
||||||
</section>
|
</section>
|
1
reflect
Submodule
1
reflect
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f8d45950d78a16adc64db920b5dbf59dabce5bca
|
|
@ -2,17 +2,14 @@
|
||||||
|
|
||||||
namespace VLW\API;
|
namespace VLW\API;
|
||||||
|
|
||||||
use Reflect\Client as ReflectClient;
|
use Reflect\Path;
|
||||||
|
use Reflect\Rules\Ruleset;
|
||||||
|
|
||||||
class Client extends ReflectClient {
|
require_once Path::root("vegvisir/src/kernel/Init.php");
|
||||||
// ISO 8601: YYYY-MM-DD
|
require_once Path::root("vegvisir/src/request/VV.php");
|
||||||
public const DATE_FORMAT = "Y-m-d";
|
|
||||||
|
|
||||||
public function __construct() {
|
class API {
|
||||||
parent::__construct(
|
public function __construct(?Ruleset $ruleset = null) {
|
||||||
$_ENV["client_api"]["base_url"],
|
$ruleset and $ruleset->validate_or_exit();
|
||||||
$_ENV["client_api"]["api_key"],
|
|
||||||
$_ENV["client_api"]["verify_peer"]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\API;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
// Enum of all available VLW endpoints grouped by category
|
|
||||||
enum Endpoints: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case WORK = "/work";
|
|
||||||
case SEARCH = "/search";
|
|
||||||
case MESSAGES = "/messages";
|
|
||||||
case WORK_TAGS = "/work/tags";
|
|
||||||
case WORK_ACTIONS = "/work/actions";
|
|
||||||
case WORK_TIMELINE = "/work/timeline";
|
|
||||||
case BATTLESTATION = "/battlestation";
|
|
||||||
case ABOUT_LANGUAGES = "/about/languages";
|
|
||||||
case BATTLESTATION_MB = "/battlestation/mb";
|
|
||||||
case BATTLESTATION_CPU = "/battlestation/cpu";
|
|
||||||
case BATTLESTATION_GPU = "/battlestation/gpu";
|
|
||||||
case BATTLESTATION_PSU = "/battlestation/psu";
|
|
||||||
case BATTLESTATION_DRAM = "/battlestation/dram";
|
|
||||||
case BATTLESTATION_STORAGE = "/battlestation/storage";
|
|
||||||
case BATTLESTATION_COOLERS = "/battlestation/coolers";
|
|
||||||
case BATTLESTATION_CHASSIS = "/battlestation/chassis";
|
|
||||||
}
|
|
|
@ -2,21 +2,6 @@
|
||||||
|
|
||||||
namespace VLW;
|
namespace VLW;
|
||||||
|
|
||||||
/**
|
|
||||||
* # Media
|
|
||||||
* Constants related to media files
|
|
||||||
*/
|
|
||||||
const MEDIA_DIR = "/public/assets/media/";
|
|
||||||
const ICONS_DIR = MEDIA_DIR . "icons/";
|
|
||||||
const DEFAULT_BUTTON_ICON = ICONS_DIR . "chevron.svg";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* # Search
|
|
||||||
* Constants for the search API endpoint
|
|
||||||
*/
|
|
||||||
const SEARCH_QUERY_MAX_LENGTH = 2048;
|
|
||||||
const SEARCH_UPDATE_CACHE_PARM = "update";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Timeline
|
* # Timeline
|
||||||
* Constants related to the work timeline
|
* Constants related to the work timeline
|
||||||
|
@ -28,8 +13,4 @@
|
||||||
* # Forgejo
|
* # Forgejo
|
||||||
* Constants related to the fetching and caching of real-time prog. language use on Forgejo
|
* Constants related to the fetching and caching of real-time prog. language use on Forgejo
|
||||||
*/
|
*/
|
||||||
const FORGEJO_HREF = "https://git.vlw.se/explore/repos?q=&sort=recentupdate&language=";
|
|
||||||
const FORGEJO_ENDPOINT_USER = "/api/v1/users/%s";
|
|
||||||
const FORGEJO_ENDPOINT_SEARCH = "/api/v1/repos/search?uid=%s";
|
|
||||||
const FORGEJO_SI_BYTE_MULTIPLE = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
const FORGEJO_SI_BYTE_MULTIPLE = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
const FORGEJO_UPDATE_CACHE_PARAM = "update";
|
|
|
@ -2,44 +2,17 @@
|
||||||
|
|
||||||
namespace VLW\Database;
|
namespace VLW\Database;
|
||||||
|
|
||||||
use Reflect\Response;
|
|
||||||
|
|
||||||
use vlw\MySQL\MySQL;
|
use vlw\MySQL\MySQL;
|
||||||
|
|
||||||
class Database {
|
class Database extends MySQL {
|
||||||
public const SIZE_UUID = 36;
|
public const DATE_FORMAT = "Y-m-d H:i:s"; // RFC 3339
|
||||||
public const SIZE_TEXT = 65535;
|
|
||||||
public const SIZE_UINT8 = 2 ** 8 -1;
|
|
||||||
public const SIZE_UINT16 = 2 ** 16 -1;
|
|
||||||
public const SIZE_UINT32 = 2 ** 32 -1;
|
|
||||||
public const SIZE_UINT64 = 2 ** 64 -1;
|
|
||||||
public const SIZE_VARCHAR = 255;
|
|
||||||
|
|
||||||
protected readonly MySQL $db;
|
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
// Create new MariaDB connection
|
parent::__construct(
|
||||||
$this->db = new MySQL(
|
$_ENV["mariadb"]["host"],
|
||||||
$_ENV["server_database"]["host"],
|
$_ENV["mariadb"]["user"],
|
||||||
$_ENV["server_database"]["user"],
|
$_ENV["mariadb"]["pass"],
|
||||||
$_ENV["server_database"]["pass"],
|
$_ENV["mariadb"]["db"],
|
||||||
$_ENV["server_database"]["db"],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return all rows from a table using $_GET paramters as the WHERE clause as a Reflect\Response
|
|
||||||
public function list(string $table, array $columns, array $order = null): Response {
|
|
||||||
$resp = $this->db->for($table)->where(array_intersect_key($_GET, array_flip($columns)));
|
|
||||||
|
|
||||||
// Optionally order rows by columns
|
|
||||||
if ($order) {
|
|
||||||
$resp = $resp->order($order);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all matched rows or a 404 with an empty array if there were not results
|
|
||||||
$resp = $resp->select($columns);
|
|
||||||
return $resp && $resp->num_rows > 0
|
|
||||||
? new Response($resp->fetch_all(MYSQLI_ASSOC))
|
|
||||||
: new Response([], 404);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Models\About;
|
|
||||||
|
|
||||||
use \VV;
|
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
|
||||||
use VLW\Database\Models\Model;
|
|
||||||
use VLW\Database\Tables\About\LanguagesTable;
|
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
|
||||||
require_once VV::root("src/Database/Models/Model.php");
|
|
||||||
require_once VV::root("src/Database/Tables/About/Languages.php");
|
|
||||||
|
|
||||||
class Language extends Model {
|
|
||||||
public function __construct(public readonly string $id) {
|
|
||||||
parent::__construct(Endpoints::ABOUT_LANGUAGES, [
|
|
||||||
LanguagesTable::ID->value => $this->id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function all(array $params = []): array {
|
|
||||||
return array_map(fn(array $item): Language => new Language($item[LanguagesTable::ID->value]), parent::list(Endpoints::ABOUT_LANGUAGES, $params));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function bytes(): int {
|
|
||||||
return $this->get(LanguagesTable::BYTES->value);
|
|
||||||
}
|
|
||||||
}
|
|
76
src/Database/Models/Coffee/Coffee.php
Normal file
76
src/Database/Models/Coffee/Coffee.php
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VLW\Database\Models\Coffee;
|
||||||
|
|
||||||
|
use \VV;
|
||||||
|
use \Exception;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
use \DateTimeImmutable;
|
||||||
|
|
||||||
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
|
use VLW\Database\Models\Model;
|
||||||
|
use VLW\Database\Tables\Coffee\{Stats, Coffee as CoffeeTable};
|
||||||
|
|
||||||
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
|
require_once VV::root("src/Database/Tables/Coffee/Stats.php");
|
||||||
|
require_once VV::root("src/Database/Tables/Coffee/Coffee.php");
|
||||||
|
|
||||||
|
class Coffee extends Model {
|
||||||
|
final public static function new(?DateTimeImmutable $datetime = null): self {
|
||||||
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(CoffeeTable::TABLE, [
|
||||||
|
CoffeeTable::ID->value => $id,
|
||||||
|
CoffeeTable::DATE_CREATED->value => $datetime ? $datetime->format(parent::DATE_FORMAT) : date(parent::DATE_FORMAT)
|
||||||
|
])) { throw new Exception("Failed to create Work entity"); }
|
||||||
|
|
||||||
|
return new Coffee($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function all(): array {
|
||||||
|
return array_map(fn(array $work): Coffee => new Coffee($work[CoffeeTable::ID->value]), new Database()
|
||||||
|
->from(CoffeeTable::TABLE)
|
||||||
|
->order([CoffeeTable::DATE_CREATED->value => Order::DESC])
|
||||||
|
->select(CoffeeTable::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function count_week(): int {
|
||||||
|
return new Database()
|
||||||
|
->from(Stats::TABLE)
|
||||||
|
->limit(1)
|
||||||
|
->select(Stats::COUNT_WEEK->value)
|
||||||
|
->fetch_assoc()[Stats::COUNT_WEEK->value] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function count_week_average(): int {
|
||||||
|
return new Database()
|
||||||
|
->from(Stats::TABLE)
|
||||||
|
->limit(1)
|
||||||
|
->select(Stats::COUNT_WEEK_AVERAGE->value)
|
||||||
|
->fetch_assoc()[Stats::COUNT_WEEK_AVERAGE->value] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public readonly string $id) {
|
||||||
|
parent::__construct(CoffeeTable::TABLE, CoffeeTable::values(), [
|
||||||
|
CoffeeTable::ID->value => $this->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): bool {
|
||||||
|
try {
|
||||||
|
return $this->db->from(CoffeeTable::TABLE)->delete([CoffeeTable::ID->value => $this->id]);
|
||||||
|
} catch (Exception $error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final public DateTimeImmutable $date_created {
|
||||||
|
get => new DateTimeImmutable($this->get(CoffeeTable::DATE_CREATED->value));
|
||||||
|
set (DateTimeImmutable $date_created) => $this->set(CoffeeTable::DATE_CREATED->value, $date_created->format(parent::DATE_FORMAT));
|
||||||
|
}
|
||||||
|
}
|
65
src/Database/Models/Languages/Language.php
Normal file
65
src/Database/Models/Languages/Language.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VLW\Database\Models\Languages;
|
||||||
|
|
||||||
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
|
||||||
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
|
use VLW\Database\Models\Model;
|
||||||
|
use VLW\Database\Tables\Languages\Languages;
|
||||||
|
|
||||||
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
|
require_once VV::root("src/Database/Tables/Languages/Languages.php");
|
||||||
|
|
||||||
|
class Language extends Model {
|
||||||
|
final public static function new(string $name, ?int $bytes = 0): self {
|
||||||
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(Languages::TABLE, [
|
||||||
|
Languages::ID->value => $id,
|
||||||
|
Languages::NAME->value => $name,
|
||||||
|
Languages::BYTES->value => $bytes
|
||||||
|
])) { throw new Exception("Failed to create Language entity"); }
|
||||||
|
|
||||||
|
return new Language($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function all(): array {
|
||||||
|
return array_map(fn(array $language): Language => new Language($language[Languages::ID->value]), new Database()
|
||||||
|
->from(Languages::TABLE)
|
||||||
|
->order([Languages::BYTES->value => Order::DESC])
|
||||||
|
->select(Languages::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function from(string $name): ?self {
|
||||||
|
return array_map(fn(array $language): Language => new Language($language[Languages::ID->value]), new Database()
|
||||||
|
->from(Languages::TABLE)
|
||||||
|
->where([Languages::NAME->value => $name])
|
||||||
|
->limit(1)
|
||||||
|
->select(Languages::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public readonly string $id) {
|
||||||
|
parent::__construct(Languages::TABLE, Languages::values(), [
|
||||||
|
Languages::ID->value => $this->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public string $name {
|
||||||
|
get => $this->get(Languages::NAME->value);
|
||||||
|
set (string $name) => $this->set(Languages::NAME->value, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public int $bytes {
|
||||||
|
get => $this->get(Languages::BYTES->value);
|
||||||
|
set (int $bytes) => $this->set(Languages::BYTES->value, $bytes);
|
||||||
|
}
|
||||||
|
}
|
62
src/Database/Models/Messages/Message.php
Normal file
62
src/Database/Models/Messages/Message.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VLW\Database\Models\Messages;
|
||||||
|
|
||||||
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
use \DateTimeImmutable;
|
||||||
|
|
||||||
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
|
use VLW\Database\Models\Model;
|
||||||
|
use VLW\Database\Tables\Messages\Messages;
|
||||||
|
|
||||||
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
|
require_once VV::root("src/Database/Tables/Messages/Messages.php");
|
||||||
|
|
||||||
|
class Message extends Model {
|
||||||
|
final public static function new(string $message, ?string $email = null): self {
|
||||||
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(Messages::TABLE, [
|
||||||
|
Messages::ID->value => $id,
|
||||||
|
Messages::EMAIL->value => $email,
|
||||||
|
Messages::MESSAGE->value => $message,
|
||||||
|
Messages::DATE_CREATED->value => date(parent::DATE_FORMAT)
|
||||||
|
])) { throw new Exception("Failed to create Message entity"); }
|
||||||
|
|
||||||
|
return new Message($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function all(): array {
|
||||||
|
return array_map(fn(array $Message): Message => new Message($Message[Messages::ID->value]), new Database()
|
||||||
|
->from(Messages::TABLE)
|
||||||
|
->order([Messages::DATE_CREATED->value => Order::DESC])
|
||||||
|
->select(Messages::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public readonly string $id) {
|
||||||
|
parent::__construct(Messages::TABLE, Messages::values(), [
|
||||||
|
Messages::ID->value => $this->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public ?string $email {
|
||||||
|
get => $this->get(Messages::EMAIL->value);
|
||||||
|
set (?string $email) => $this->set(Messages::EMAIL->value, $email);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public string $message {
|
||||||
|
get => $this->get(Messages::MESSAGE->value);
|
||||||
|
set (string $message) => $this->set(Messages::MESSAGE->value, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public DateTimeImmutable $date_created {
|
||||||
|
get => new DateTimeImmutable($this->get(Messages::DATE_CREATED->value));
|
||||||
|
set (DateTimeImmutable $date_created) => $this->set(Messages::DATE_CREATED->value, $date_created->format(parent::DATE_FORMAT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,40 +4,59 @@
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
|
||||||
use VLW\API\{Client, Endpoints};
|
use VLW\Database\Database;
|
||||||
|
|
||||||
require_once VV::root("src/API/API.php");
|
require_once VV::root("src/Database/Database.php");
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
|
||||||
|
|
||||||
abstract class Model {
|
abstract class Model {
|
||||||
abstract public static function all(array $params = []): array;
|
const DATE_FORMAT = Database::DATE_FORMAT;
|
||||||
|
|
||||||
private array $row;
|
abstract public string $id { get; }
|
||||||
|
|
||||||
|
private static Database $_db;
|
||||||
|
|
||||||
|
protected readonly Database $db;
|
||||||
|
private bool $_resolved = false;
|
||||||
|
private array $_row;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly ?Endpoints $endpoint = null,
|
private readonly string $table,
|
||||||
private readonly array $params = []
|
private readonly array $columns,
|
||||||
|
private readonly array $where
|
||||||
) {
|
) {
|
||||||
if ($this->endpoint) {
|
// Establish once and reuse Database connection
|
||||||
$this->assign(self::first(self::list($endpoint, $params)));
|
$this->db = self::$_db ??= new Database();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ?array $row {
|
||||||
|
get {
|
||||||
|
// Return existing row data
|
||||||
|
if ($this->_resolved) { return $this->_row; }
|
||||||
|
|
||||||
|
$this->_resolved = true;
|
||||||
|
return $this->_row = $this->db
|
||||||
|
->from($this->table)
|
||||||
|
->where($this->where)
|
||||||
|
->limit(1)
|
||||||
|
->select($this->columns)
|
||||||
|
->fetch_assoc() ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function first(array $array): array {
|
protected static function create(string $table, array $values): bool {
|
||||||
return $array && is_array(array_values($array)[0]) ? $array[0] : $array;
|
return new Database()->from($table)->insert($values);
|
||||||
}
|
|
||||||
|
|
||||||
public static function list(Endpoints $endpoint, array $params = []): array {
|
|
||||||
$resp = (new Client())->call($endpoint->value)->params($params)->get();
|
|
||||||
return $resp->ok ? $resp->json() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function assign(array $row): self {
|
|
||||||
$this->row = $row;
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get(string $key): mixed {
|
public function get(string $key): mixed {
|
||||||
return $this->row[$key] ?? null;
|
return $this->row[$key] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function set(string $key, mixed $value): bool {
|
||||||
|
$this->_row[$key] = $value;
|
||||||
|
|
||||||
|
return $this->db
|
||||||
|
->from($this->table)
|
||||||
|
->where($this->where)
|
||||||
|
->update([$key => $value]);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,40 +3,69 @@
|
||||||
namespace VLW\Database\Models\Search;
|
namespace VLW\Database\Models\Search;
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
use \vlw\MySQL\Operators;
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
use VLW\Database\Models\Model;
|
use VLW\Database\Models\Model;
|
||||||
use VLW\Database\Tables\Search\{SearchTable, SearchCategoryEnum};
|
use VLW\Database\Tables\Search\{Search as SearchTable, SearchTypeEnum};
|
||||||
|
|
||||||
require_once VV::root("src/Consts.php");
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
require_once VV::root("src/Database/Database.php");
|
||||||
require_once VV::root("src/Database/Models/Model.php");
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
require_once VV::root("src/Database/Tables/Search/Search.php");
|
require_once VV::root("src/Database/Tables/Search/Search.php");
|
||||||
|
|
||||||
class Search extends Model {
|
class Search extends Model {
|
||||||
|
final public static function new(string $query, SearchTypeEnum $type, string $title): self {
|
||||||
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(SearchTable::TABLE, [
|
||||||
|
SearchTable::ID->value => $id,
|
||||||
|
SearchTable::QUERY->value => $query,
|
||||||
|
SearchTable::TYPE->value => $type->name,
|
||||||
|
SearchTable::TITLE->value => $title,
|
||||||
|
SearchTable::TEXT->value => null,
|
||||||
|
SearchTable::HREF->value => null
|
||||||
|
])) { throw new Exception("Failed to create Search entity"); }
|
||||||
|
|
||||||
|
return new Search($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function query(string $query, ?int $limit = null): array {
|
||||||
|
return array_map(fn(array $search): Search => new Search($search[SearchTable::ID->value]), new Database()
|
||||||
|
->from(SearchTable::TABLE)
|
||||||
|
->where([SearchTable::QUERY->value => [
|
||||||
|
Operators::LIKE->value => "%{$query}%"
|
||||||
|
]])
|
||||||
|
->limit($limit)
|
||||||
|
->select(SearchTable::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(public readonly string $id) {
|
public function __construct(public readonly string $id) {
|
||||||
parent::__construct(Endpoints::SEARCH, [
|
parent::__construct(SearchTable::TABLE, SearchTable::values(), [
|
||||||
SearchTable::ID->value => $this->id
|
SearchTable::ID->value => $this->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function all(array $params = []): array {
|
final public string $title {
|
||||||
return array_map(fn(array $item): Search => new Search($item[SearchTable::ID->value]), parent::list(Endpoints::SEARCH, $params));
|
get => $this->get(SearchTable::TITLE->value);
|
||||||
|
set (string $title) => $this->set(SearchTable::TITLE->value, $title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function title(): ?string {
|
final public ?string $text {
|
||||||
return $this->get(SearchTable::TITLE->value);
|
get => $this->get(SearchTable::TEXT->value);
|
||||||
|
set (?string $text) => $this->set(SearchTable::TEXT->value, $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function summary(): ?string {
|
final public SearchTypeEnum $type {
|
||||||
return $this->get(SearchTable::SUMMARY->value);
|
get => SearchTypeEnum::fromName($this->get(SearchTable::TYPE->value));
|
||||||
|
set (SearchTypeEnum $type) => $this->set(SearchTable::TYPE->value, $type->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function category(): ?SearchCategoryEnum {
|
final public string $href {
|
||||||
return SearchCategoryEnum::tryFromName($this->get(SearchTable::CATEGORY->value));
|
get => $this->get(SearchTable::HREF->value);
|
||||||
}
|
set (string $href) => $this->set(SearchTable::HREF->value, $href);
|
||||||
|
|
||||||
public function href(): ?string {
|
|
||||||
return $this->get(SearchTable::HREF->value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,49 +3,84 @@
|
||||||
namespace VLW\Database\Models\Work;
|
namespace VLW\Database\Models\Work;
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
use VLW\Database\Models\Model;
|
use VLW\Database\Models\Model;
|
||||||
use VLW\Database\Models\Work\Work;
|
use VLW\Database\Models\Work\Work;
|
||||||
use VLW\Database\Tables\Work\ActionsTable;
|
use VLW\Database\Tables\Work\Actions;
|
||||||
|
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
require_once VV::root("src/Database/Models/Model.php");
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
require_once VV::root("src/Database/Models/Work/Work.php");
|
require_once VV::root("src/Database/Models/Work/Work.php");
|
||||||
require_once VV::root("src/Database/Tables/Work/Actions.php");
|
require_once VV::root("src/Database/Tables/Work/Actions.php");
|
||||||
|
|
||||||
class Action extends Model {
|
class Action extends Model {
|
||||||
public function __construct() {
|
final public static function new(Work $work): self {
|
||||||
parent::__construct();
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(Actions::TABLE, [
|
||||||
|
Actions::ID->value => $id,
|
||||||
|
Actions::REF_WORK_ID->value => $work->id,
|
||||||
|
Actions::HREF->value => null,
|
||||||
|
Actions::CLASSLIST->value => null,
|
||||||
|
Actions::ICON_PREPEND->value => null,
|
||||||
|
Actions::ICON_APPEND->value => null
|
||||||
|
])) { throw new Exception("Failed to create Work Action entity"); }
|
||||||
|
|
||||||
|
return new Action($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function all(array $params = []): array {
|
final public static function from(Work $work): array {
|
||||||
return array_map(fn(array $item): Action => (new Action())->assign($item), parent::list(Endpoints::WORK_ACTIONS, $params));
|
return array_map(fn(array $tag): Action => new Action($tag[Actions::ID->value]), new Database()
|
||||||
|
->from(Actions::TABLE)
|
||||||
|
->where([Actions::REF_WORK_ID->value => $work->id])
|
||||||
|
->order([Actions::ORDER_IDX->value => Order::DESC])
|
||||||
|
->select(Actions::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function from(Work $work): array {
|
public function __construct(public readonly string $id) {
|
||||||
return self::all([
|
parent::__construct(Actions::TABLE, Actions::values(), [
|
||||||
ActionsTable::REF_WORK_ID->value => $work->id
|
Actions::ID->value => $this->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function icon_prepended(): ?string {
|
final public Work $work {
|
||||||
return $this->get(ActionsTable::ICON_PREPENDED->value);
|
get => $this->get(Actions::REF_WORK_ID->value);
|
||||||
|
set (Work $work) => $this->set(Actions::REF_WORK_ID->value, $work);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function icon_appended(): ?string {
|
final public int $order_idx {
|
||||||
return $this->get(ActionsTable::ICON_APPENDED->value);
|
get => $this->get(TimelineTable::ORDER_IDX->value);
|
||||||
|
set (int $order_idx) => $this->set(TimelineTable::ORDER_IDX->value, $order_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function display_text(): string {
|
final public ?string $href {
|
||||||
return $this->get(ActionsTable::DISPLAY_TEXT->value);
|
get => $this->get(Actions::HREF->value);
|
||||||
|
set (?string $href) => $this->set(Actions::HREF->value, $href);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function href(): ?string {
|
final public string $text {
|
||||||
return $this->get(ActionsTable::HREF->value);
|
get => $this->get(Actions::TEXT->value);
|
||||||
|
set (string $text) => $this->set(Actions::TEXT->value, $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function classes(): array {
|
final public ?string $classlist {
|
||||||
return $this->get(ActionsTable::CLASS_LIST->value) ? explode(",", $this->get(ActionsTable::CLASS_LIST->value)) : [];
|
get => $this->get(Actions::CLASSLIST->value);
|
||||||
|
set (?string $classlist) => $this->set(Actions::CLASSLIST->value, $classlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public ?string $icon_prepend {
|
||||||
|
get => $this->get(Actions::ICON_PREPEND->value);
|
||||||
|
set (?string $icon_prepend) => $this->set(Actions::ICON_PREPEND->value, $icon_prepend);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public ?string $icon_append {
|
||||||
|
get => $this->get(Actions::ICON_APPEND->value);
|
||||||
|
set (?string $icon_append) => $this->set(Actions::ICON_APPEND->value, $icon_append);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,33 +3,56 @@
|
||||||
namespace VLW\Database\Models\Work;
|
namespace VLW\Database\Models\Work;
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
use VLW\Database\Models\Model;
|
use VLW\Database\Models\Model;
|
||||||
use VLW\Database\Models\Work\Work;
|
use VLW\Database\Models\Work\Work;
|
||||||
use VLW\Database\Tables\Work\{TagsTable, TagsLabelEnum};
|
use VLW\Database\Tables\Work\{Tags, TagsLabelEnum};
|
||||||
|
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
require_once VV::root("src/Database/Models/Model.php");
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
require_once VV::root("src/Database/Models/Work/Work.php");
|
require_once VV::root("src/Database/Models/Work/Work.php");
|
||||||
require_once VV::root("src/Database/Tables/Work/Tags.php");
|
require_once VV::root("src/Database/Tables/Work/Tags.php");
|
||||||
|
|
||||||
class Tag extends Model {
|
class Tag extends Model {
|
||||||
public function __construct() {
|
final public static function new(Work $work, TagsLabelEnum $label): self {
|
||||||
parent::__construct();
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(TimelineTable::TABLE, [
|
||||||
|
TimelineTable::ID->value => $id,
|
||||||
|
TimelineTable::REF_WORK_ID->value => $work->id,
|
||||||
|
TimelineTable::LABEL->value => $label->name
|
||||||
|
])) { throw new Exception("Failed to create Work Tag entity"); }
|
||||||
|
|
||||||
|
return new Tag($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function all(array $params = []): array {
|
final public static function from(Work $work): array {
|
||||||
return array_map(fn(array $item): Tag => (new Tag())->assign($item), parent::list(Endpoints::WORK_TAGS, $params));
|
return array_map(fn(array $tag): Tag => new Tag($tag[Tags::ID->value]), new Database()
|
||||||
|
->from(Tags::TABLE)
|
||||||
|
->where([Tags::REF_WORK_ID->value => $work->id])
|
||||||
|
->order([Tags::LABEL->value => Order::DESC])
|
||||||
|
->select(Tags::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function from(Work $work): array {
|
public function __construct(public readonly string $id) {
|
||||||
return self::all([
|
parent::__construct(Tags::TABLE, Tags::values(), [
|
||||||
TagsTable::REF_WORK_ID->value => $work->id
|
Tags::ID->value => $this->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function label(): TagsLabelEnum {
|
final public Work $work {
|
||||||
return TagsLabelEnum::fromName($this->get(TagsTable::LABEL->value));
|
get => new Work($this->get(Tags::REF_WORK_ID->value));
|
||||||
|
set (Work $work) => $this->set(Tags::REF_WORK_ID->value, $work);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public TagsLabelEnum $label {
|
||||||
|
get => TagsLabelEnum::fromName($this->get(Tags::LABEL->value));
|
||||||
|
set (TagsLabelEnum $pathname) => $this->set(Tags::LABEL->value, $pathname->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,41 +3,71 @@
|
||||||
namespace VLW\Database\Models\Work;
|
namespace VLW\Database\Models\Work;
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
use VLW\Database\Models\Model;
|
use VLW\Database\Models\Model;
|
||||||
use VLW\Database\Models\Work\Work;
|
use VLW\Database\Models\Work\Work;
|
||||||
use VLW\Database\Tables\Work\TimelineTable;
|
use VLW\Database\Tables\Work\Timeline as TimelineTable;
|
||||||
|
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
require_once VV::root("src/Database/Models/Model.php");
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
require_once VV::root("src/Database/Models/Work/Work.php");
|
require_once VV::root("src/Database/Models/Work/Work.php");
|
||||||
require_once VV::root("src/Database/Tables/Work/Timeline.php");
|
require_once VV::root("src/Database/Tables/Work/Timeline.php");
|
||||||
|
|
||||||
class Timeline extends Model {
|
class Timeline extends Model {
|
||||||
|
final public static function new(Work $work): self {
|
||||||
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(TimelineTable::TABLE, [
|
||||||
|
TimelineTable::ID->value => $id,
|
||||||
|
TimelineTable::REF_WORK_ID->value => $work->id,
|
||||||
|
TimelineTable::YEAR->value => (int) $work->date_created->format("Y"),
|
||||||
|
TimelineTable::MONTH->value => (int) $work->date_created->format("n"),
|
||||||
|
TimelineTable::DAY->value => (int) $work->date_created->format("j"),
|
||||||
|
])) { throw new Exception("Failed to create Work Timeline entity"); }
|
||||||
|
|
||||||
|
return new Timeline($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function all(): array {
|
||||||
|
return array_map(fn(array $work): Timeline => new Timeline($work[TimelineTable::ID->value]), new Database()
|
||||||
|
->from(TimelineTable::TABLE)
|
||||||
|
->order([
|
||||||
|
TimelineTable::YEAR->value => Order::DESC,
|
||||||
|
TimelineTable::MONTH->value => Order::DESC,
|
||||||
|
TimelineTable::DAY->value => Order::DESC
|
||||||
|
])
|
||||||
|
->select(TimelineTable::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(public readonly string $id) {
|
public function __construct(public readonly string $id) {
|
||||||
parent::__construct(Endpoints::WORK_TIMELINE, [
|
parent::__construct(TimelineTable::TABLE, TimelineTable::values(), [
|
||||||
TimelineTable::REF_WORK_ID->value => $this->id
|
TimelineTable::ID->value => $this->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function all(array $params = []): array {
|
final public Work $work {
|
||||||
return array_map(fn(array $item): Timeline => new Timeline($item[TimelineTable::REF_WORK_ID->value]), parent::list(Endpoints::WORK_TIMELINE, $params));
|
get => new Work($this->get(TimelineTable::REF_WORK_ID->value));
|
||||||
|
set (Work $work) => $this->set(TimelineTable::REF_WORK_ID->value, $work->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function work(): Work {
|
final public int $year {
|
||||||
return new Work($this->get(TimelineTable::REF_WORK_ID->value));
|
get => $this->get(TimelineTable::YEAR->value);
|
||||||
|
set (int $year) => $this->set(TimelineTable::YEAR->value, $year);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function year(): int {
|
final public int $month {
|
||||||
return $this->get(TimelineTable::YEAR->value);
|
get => $this->get(TimelineTable::MONTH->value);
|
||||||
|
set (int $month) => $this->set(TimelineTable::MONTH->value, $month);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function month(): int {
|
final public int $day {
|
||||||
return $this->get(TimelineTable::MONTH->value);
|
get => $this->get(TimelineTable::DAY->value);
|
||||||
}
|
set (int $day) => $this->set(TimelineTable::DAY->value, $day);
|
||||||
|
|
||||||
public function day(): int {
|
|
||||||
return $this->get(TimelineTable::DAY->value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,46 +3,88 @@
|
||||||
namespace VLW\Database\Models\Work;
|
namespace VLW\Database\Models\Work;
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
use \DateTimeImmutable;
|
||||||
|
|
||||||
use VLW\API\Endpoints;
|
use VLW\Helpers\UUID;
|
||||||
|
use VLW\Database\Database;
|
||||||
use VLW\Database\Models\Model;
|
use VLW\Database\Models\Model;
|
||||||
use VLW\Database\Tables\Work\WorkTable;
|
|
||||||
use VLW\Database\Models\Work\{Tag, Action};
|
use VLW\Database\Models\Work\{Tag, Action};
|
||||||
|
use VLW\Database\Tables\Work\Work as WorkTable;
|
||||||
|
|
||||||
require_once VV::root("src/API/Endpoints.php");
|
require_once VV::root("src/Helpers/UUID.php");
|
||||||
|
require_once VV::root("src/Database/Database.php");
|
||||||
require_once VV::root("src/Database/Models/Model.php");
|
require_once VV::root("src/Database/Models/Model.php");
|
||||||
require_once VV::root("src/Database/Models/Work/Tag.php");
|
require_once VV::root("src/Database/Models/Work/Tag.php");
|
||||||
require_once VV::root("src/Database/Tables/Work/Work.php");
|
require_once VV::root("src/Database/Tables/Work/Work.php");
|
||||||
require_once VV::root("src/Database/Models/Work/Action.php");
|
require_once VV::root("src/Database/Models/Work/Action.php");
|
||||||
|
|
||||||
class Work extends Model {
|
class Work extends Model {
|
||||||
|
final public static function new(string $namespace): self {
|
||||||
|
$id = UUID::v4();
|
||||||
|
|
||||||
|
if (!parent::create(WorkTable::TABLE, [
|
||||||
|
WorkTable::ID->value => $id,
|
||||||
|
WorkTable::NAMESPACE->value => $namespace,
|
||||||
|
WorkTable::TITLE->value => $pathname,
|
||||||
|
WorkTable::SUMMARY->value => null,
|
||||||
|
WorkTable::DATE_CREATED->value => date(parent::DATE_FORMAT)
|
||||||
|
])) { throw new Exception("Failed to create Work entity"); }
|
||||||
|
|
||||||
|
return new Work($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function all(): array {
|
||||||
|
return array_map(fn(array $work): Work => new Work($work[WorkTable::ID->value]), new Database()
|
||||||
|
->from(WorkTable::TABLE)
|
||||||
|
->order([WorkTable::DATE_CREATED->value => Order::DESC])
|
||||||
|
->select(WorkTable::ID->value)
|
||||||
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public static function from(string $namespace): ?self {
|
||||||
|
$work = new Database()
|
||||||
|
->from(WorkTable::TABLE)
|
||||||
|
->where([WorkTable::NAMESPACE->value => $namespace])
|
||||||
|
->limit(1)
|
||||||
|
->select(WorkTable::ID->value)
|
||||||
|
->fetch_assoc();
|
||||||
|
|
||||||
|
return $work ? new Work($work[WorkTable::ID->value]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(public readonly string $id) {
|
public function __construct(public readonly string $id) {
|
||||||
parent::__construct(Endpoints::WORK, [
|
parent::__construct(WorkTable::TABLE, WorkTable::values(), [
|
||||||
WorkTable::ID->value => $this->id
|
WorkTable::ID->value => $this->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function all(array $params = []): array {
|
final public function tags(): array {
|
||||||
return array_map(fn(array $item): Work => new Work($item[WorkTable::ID->value]), parent::list(Endpoints::WORK, $params));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function title(): ?string {
|
|
||||||
return $this->get(WorkTable::TITLE->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function summary(): string {
|
|
||||||
return $this->get(WorkTable::SUMMARY->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function created(): DateTimeImmutable {
|
|
||||||
return new DateTimeImmutable($this->get(WorkTable::CREATED->value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tags(): array {
|
|
||||||
return Tag::from($this);
|
return Tag::from($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function actions(): array {
|
final public function actions(): array {
|
||||||
return Action::from($this);
|
return Action::from($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final public string $namespace {
|
||||||
|
get => $this->get(WorkTable::NAMESPACE->value);
|
||||||
|
set (string $namespace) => $this->set(WorkTable::NAMESPACE->value, $namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public string $title {
|
||||||
|
get => $this->get(WorkTable::TITLE->value);
|
||||||
|
set (string $title) => $this->set(WorkTable::TITLE->value, $title);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public ?string $summary {
|
||||||
|
get => $this->get(WorkTable::SUMMARY->value);
|
||||||
|
set (?string $summary) => $this->set(WorkTable::SUMMARY->value, $summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
final public DateTimeImmutable $date_created {
|
||||||
|
get => new DateTimeImmutable($this->get(WorkTable::DATE_CREATED->value));
|
||||||
|
set (DateTimeImmutable $date_created) => $this->set(WorkTable::DATE_CREATED->value, $date_created->format(parent::DATE_FORMAT));
|
||||||
|
}
|
||||||
}
|
}
|
96
src/Database/Seeds/api.sql
Normal file
96
src/Database/Seeds/api.sql
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
|
START TRANSACTION;
|
||||||
|
SET time_zone = "+00:00";
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `acl` (
|
||||||
|
`ref_group` varchar(255) DEFAULT NULL,
|
||||||
|
`ref_endpoint` varchar(255) NOT NULL,
|
||||||
|
`method` enum('GET','POST','PUT','PATCH','DELETE') NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
INSERT INTO `acl` (`ref_group`, `ref_endpoint`, `method`) VALUES
|
||||||
|
(NULL, 'coffee', 'GET'),
|
||||||
|
(NULL, 'languages', 'GET'),
|
||||||
|
(NULL, 'update', 'GET'),
|
||||||
|
(NULL, 'work', 'GET');
|
||||||
|
|
||||||
|
CREATE TABLE `endpoints` (
|
||||||
|
`id` varchar(255) NOT NULL,
|
||||||
|
`active` tinyint(1) UNSIGNED NOT NULL DEFAULT 1
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
INSERT INTO `endpoints` (`id`, `active`) VALUES
|
||||||
|
('coffee', 1),
|
||||||
|
('languages', 1),
|
||||||
|
('update', 1),
|
||||||
|
('work', 1);
|
||||||
|
|
||||||
|
CREATE TABLE `groups` (
|
||||||
|
`id` varchar(255) NOT NULL,
|
||||||
|
`active` tinyint(1) UNSIGNED NOT NULL DEFAULT 1,
|
||||||
|
`date_created` int(32) NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `keys` (
|
||||||
|
`id` varchar(255) NOT NULL,
|
||||||
|
`active` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`ref_user` varchar(255) DEFAULT NULL,
|
||||||
|
`expires` int(32) DEFAULT NULL,
|
||||||
|
`created` int(32) NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `rel_users_groups` (
|
||||||
|
`ref_user` varchar(255) NOT NULL,
|
||||||
|
`ref_group` varchar(255) NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` varchar(255) NOT NULL,
|
||||||
|
`active` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`created` int(32) NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `acl`
|
||||||
|
ADD KEY `endpoint` (`ref_endpoint`),
|
||||||
|
ADD KEY `ref_group` (`ref_group`);
|
||||||
|
|
||||||
|
ALTER TABLE `endpoints`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `groups`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `keys`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `ref_user` (`ref_user`);
|
||||||
|
|
||||||
|
ALTER TABLE `rel_users_groups`
|
||||||
|
ADD KEY `ref_user` (`ref_user`),
|
||||||
|
ADD KEY `ref_group` (`ref_group`);
|
||||||
|
|
||||||
|
ALTER TABLE `users`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `acl`
|
||||||
|
ADD CONSTRAINT `acl_ibfk_1` FOREIGN KEY (`ref_endpoint`) REFERENCES `endpoints` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
ADD CONSTRAINT `acl_ibfk_2` FOREIGN KEY (`ref_group`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE `keys`
|
||||||
|
ADD CONSTRAINT `keys_ibfk_1` FOREIGN KEY (`ref_user`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE `rel_users_groups`
|
||||||
|
ADD CONSTRAINT `rel_users_groups_ibfk_1` FOREIGN KEY (`ref_user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
ADD CONSTRAINT `rel_users_groups_ibfk_2` FOREIGN KEY (`ref_group`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
143
src/Database/Seeds/vlw.sql
Normal file
143
src/Database/Seeds/vlw.sql
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
|
START TRANSACTION;
|
||||||
|
SET time_zone = "+00:00";
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `coffee` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`date_created` datetime NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE TRIGGER `coffee_stats_update` AFTER INSERT ON `coffee` FOR EACH ROW BEGIN
|
||||||
|
DECLARE count_recent INT;
|
||||||
|
DECLARE count_average INT;
|
||||||
|
|
||||||
|
|
||||||
|
DELETE FROM coffee_stats;
|
||||||
|
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO count_recent
|
||||||
|
FROM coffee
|
||||||
|
WHERE date_created > NOW() - INTERVAL 7 DAY;
|
||||||
|
|
||||||
|
|
||||||
|
SELECT COUNT(*) / COUNT(DISTINCT YEAR(date_created), WEEK(date_created))
|
||||||
|
INTO count_average
|
||||||
|
FROM coffee;
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO coffee_stats (count_week, count_week_average) VALUES (count_recent, count_average);
|
||||||
|
END
|
||||||
|
$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CREATE TABLE `coffee_stats` (
|
||||||
|
`count_week` smallint(5) UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`count_week_average` smallint(5) UNSIGNED NOT NULL DEFAULT 0
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `languages` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`bytes` int(10) UNSIGNED NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `messages` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`email` varchar(255) DEFAULT NULL,
|
||||||
|
`message` text NOT NULL,
|
||||||
|
`date_created` datetime NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `search` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`query` text NOT NULL,
|
||||||
|
`type` enum('WORK') NOT NULL,
|
||||||
|
`title` varchar(255) NOT NULL,
|
||||||
|
`text` text DEFAULT NULL,
|
||||||
|
`href` varchar(255) DEFAULT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `work` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`namespace` varchar(255) NOT NULL,
|
||||||
|
`title` varchar(255) NOT NULL,
|
||||||
|
`summary` text DEFAULT NULL,
|
||||||
|
`date_created` datetime NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `work_actions` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`ref_work_id` char(36) NOT NULL,
|
||||||
|
`order_idx` tinyint(3) UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`href` varchar(255) DEFAULT NULL,
|
||||||
|
`text` varchar(255) NOT NULL,
|
||||||
|
`classlist` varchar(255) DEFAULT NULL,
|
||||||
|
`icon_prepend` varchar(255) DEFAULT NULL,
|
||||||
|
`icon_append` varchar(255) DEFAULT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `work_tags` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`ref_work_id` char(36) NOT NULL,
|
||||||
|
`label` enum('VLW','WEBSITE','REPO') NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `work_timeline` (
|
||||||
|
`id` char(36) NOT NULL,
|
||||||
|
`ref_work_id` char(36) NOT NULL,
|
||||||
|
`year` smallint(5) UNSIGNED NOT NULL,
|
||||||
|
`month` tinyint(3) UNSIGNED NOT NULL,
|
||||||
|
`day` tinyint(3) UNSIGNED NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `coffee`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `languages`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD UNIQUE KEY `name` (`name`);
|
||||||
|
|
||||||
|
ALTER TABLE `messages`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `search`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
ALTER TABLE `search` ADD FULLTEXT KEY `query` (`query`);
|
||||||
|
|
||||||
|
ALTER TABLE `work`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD UNIQUE KEY `pathname` (`namespace`);
|
||||||
|
|
||||||
|
ALTER TABLE `work_actions`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `ref_work_id` (`ref_work_id`);
|
||||||
|
|
||||||
|
ALTER TABLE `work_tags`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD KEY `ref_work_id` (`ref_work_id`);
|
||||||
|
|
||||||
|
ALTER TABLE `work_timeline`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD UNIQUE KEY `ref_work_id` (`ref_work_id`);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `work_actions`
|
||||||
|
ADD CONSTRAINT `work_actions_ibfk_1` FOREIGN KEY (`ref_work_id`) REFERENCES `work` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE `work_tags`
|
||||||
|
ADD CONSTRAINT `work_tags_ibfk_1` FOREIGN KEY (`ref_work_id`) REFERENCES `work` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE `work_timeline`
|
||||||
|
ADD CONSTRAINT `work_timeline_ibfk_1` FOREIGN KEY (`ref_work_id`) REFERENCES `work` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\About;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum LanguagesTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "about_languages";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case BYTES = "bytes";
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum ChassisTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "chassis";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case STORAGE_TWOINCHFIVE = "storage_2i5hi";
|
|
||||||
case STORAGE_THREEINCHFIVE = "storage_3i5hi";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum ChassisMbTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config_chassis_mb";
|
|
||||||
|
|
||||||
case REF_CHASSIS_ID = "ref_chassis_id";
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum ConfigModel: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config";
|
|
||||||
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
case FRIENDLY_NAME = "friendly_name";
|
|
||||||
case DATE_BUILT = "date_built";
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum SocketTypeEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case SLOTTED;
|
|
||||||
case INTEGRATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MbCpuCoolerModel: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config_mb_cpu_cooler";
|
|
||||||
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
case REF_CPU_ID = "ref_cpu_id";
|
|
||||||
case REF_COOLER_ID = "ref_cooler_id";
|
|
||||||
case SOCKET = "socket";
|
|
||||||
case SOCKET_TYPE = "socket_type";
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum SocketTypeModel {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case SLOTTED;
|
|
||||||
case INTEGRATED;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MbDramTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config_mb_dram";
|
|
||||||
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
case REF_DRAM_ID = "ref_dram_id";
|
|
||||||
case SOCKET = "socket";
|
|
||||||
case SOCKET_TYPE = "socket_type";
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum MbGpuTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config_mb_gpu";
|
|
||||||
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
case REF_GPU_ID = "ref_gpu_id";
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum MbPsuTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config_mb_psu";
|
|
||||||
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
case REF_PSU_ID = "ref_psu_id";
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation\Config;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum MbStorageSlotFormfactorEnum: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case TWODOTFIVE = "2.5";
|
|
||||||
case THREEDOTFIVE = "3.5";
|
|
||||||
case MDOTTWO = "M.2";
|
|
||||||
case EXTERNAL = "EXTERNAL";
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MbStorageTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "config_mb_storage";
|
|
||||||
|
|
||||||
case REF_MB_ID = "ref_mb_id";
|
|
||||||
case REF_STORAGE_ID = "ref_storage_id";
|
|
||||||
case INTERFACE = "interface";
|
|
||||||
case SLOT_FORMFACTOR = "slot_formfactor";
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
enum CoolersTable: string {
|
|
||||||
const NAME = "coolers";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case TYPE_LIQUID = "type_liquid";
|
|
||||||
case SIZE_FAN = "size_fan";
|
|
||||||
case SIZE_RADIATOR = "size_radiator";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum ClassEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case DESKTOP;
|
|
||||||
case LAPTOP;
|
|
||||||
case SERVER;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CpuTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "cpu";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case CPU_CLASS = "class";
|
|
||||||
case CLOCK_BASE = "clock_base";
|
|
||||||
case CLOCK_TURBO = "clock_turbo";
|
|
||||||
case CORE_COUNT_PERFORMANCE = "core_count_performance";
|
|
||||||
case CORE_COUNT_EFFICIENCY = "core_count_efficiency";
|
|
||||||
case CORE_THREADS = "core_threads";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum DramFormfactorEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case DIMM;
|
|
||||||
case SODIMM;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DramTechnologyEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case DDR4;
|
|
||||||
case DDR5;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DramTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "dram";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case CAPACITY = "capacity";
|
|
||||||
case SPEED = "speed";
|
|
||||||
case FORMFACTOR = "formfactor";
|
|
||||||
case TECHNOLOGY = "technology";
|
|
||||||
case ECC = "ecc";
|
|
||||||
case BUFFERED = "buffered";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum GpuTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "gpu";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case MEMORY = "memory";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case VENDOR_CHIP_NAME = "vendor_chip_name";
|
|
||||||
case VENDOR_CHIP_MODEL = "vendor_chip_model";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum MbFormfactorEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case ATX;
|
|
||||||
case MTX;
|
|
||||||
case ITX;
|
|
||||||
case LAPTOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MbTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "mb";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case FORMFACTOR = "formfactor";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case NETWORK_ETHERNET = "network_ethernet";
|
|
||||||
case NETWORK_WLAN = "network_wlan";
|
|
||||||
case NETWORK_BLUETOOTH = "network_bluetooth";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum EightyplusRatingEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case BASE;
|
|
||||||
case BRONZE;
|
|
||||||
case SILVER;
|
|
||||||
case GOLD;
|
|
||||||
case PLATINUM;
|
|
||||||
case TITANIUM;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PsuTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "psu";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case POWER = "power";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case TYPE_MODULAR = "type_modular";
|
|
||||||
case EIGHTYPLUS_RATING = "80plus_rating";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Battlestation;
|
|
||||||
|
|
||||||
use vlw\xEnum;
|
|
||||||
|
|
||||||
enum StorageDiskTypeEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case SSD;
|
|
||||||
case HDD;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum StorageDiskInterfaceEnum {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case SATA;
|
|
||||||
case NVME;
|
|
||||||
case USB;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum StorageDiskFormfactorEnum{
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
case TWODOTFIVE;
|
|
||||||
case THREEDOTFIVE;
|
|
||||||
case MDOTTWO;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum StorageTable: string {
|
|
||||||
use xEnum;
|
|
||||||
|
|
||||||
const NAME = "storage";
|
|
||||||
|
|
||||||
case ID = "id";
|
|
||||||
case DISK_TYPE = "disk_type";
|
|
||||||
case DISK_SIZE = "disk_size";
|
|
||||||
case DISK_SECTORS = "disk_sectors";
|
|
||||||
case DISK_INTERFACE = "disk_interface";
|
|
||||||
case DISK_FORMFACTOR = "disk_formfactor";
|
|
||||||
case VENDOR_NAME = "vendor_name";
|
|
||||||
case VENDOR_MODEL = "vendor_model";
|
|
||||||
case DATE_AQUIRED = "date_aquired";
|
|
||||||
case IS_RETIRED = "is_retired";
|
|
||||||
}
|
|
14
src/Database/Tables/Coffee/Coffee.php
Normal file
14
src/Database/Tables/Coffee/Coffee.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VLW\Database\Tables\Coffee;
|
||||||
|
|
||||||
|
use vlw\xEnum;
|
||||||
|
|
||||||
|
enum Coffee: string {
|
||||||
|
use xEnum;
|
||||||
|
|
||||||
|
const TABLE = "coffee";
|
||||||
|
|
||||||
|
case ID = "id";
|
||||||
|
case DATE_CREATED = "date_created";
|
||||||
|
}
|
14
src/Database/Tables/Coffee/Stats.php
Normal file
14
src/Database/Tables/Coffee/Stats.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VLW\Database\Tables\Coffee;
|
||||||
|
|
||||||
|
use vlw\xEnum;
|
||||||
|
|
||||||
|
enum Stats: string {
|
||||||
|
use xEnum;
|
||||||
|
|
||||||
|
const TABLE = "coffee_stats";
|
||||||
|
|
||||||
|
case COUNT_WEEK = "count_week";
|
||||||
|
case COUNT_WEEK_AVERAGE = "count_week_average";
|
||||||
|
}
|
15
src/Database/Tables/Languages/Languages.php
Normal file
15
src/Database/Tables/Languages/Languages.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VLW\Database\Tables\Languages;
|
||||||
|
|
||||||
|
use vlw\xEnum;
|
||||||
|
|
||||||
|
enum Languages: string {
|
||||||
|
use xEnum;
|
||||||
|
|
||||||
|
const TABLE = "languages";
|
||||||
|
|
||||||
|
case ID = "id";
|
||||||
|
case NAME = "name";
|
||||||
|
case BYTES = "bytes";
|
||||||
|
}
|
|
@ -2,10 +2,15 @@
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Messages;
|
namespace VLW\Database\Tables\Messages;
|
||||||
|
|
||||||
enum MessagesTable: string {
|
use vlw\xEnum;
|
||||||
const NAME = "messages";
|
|
||||||
|
|
||||||
case EMAIL = "email";
|
enum Messages: string {
|
||||||
case MESSAGE = "message";
|
use xEnum;
|
||||||
case TIMESTAMP_CREATED = "timestamp_created";
|
|
||||||
|
const TABLE = "messages";
|
||||||
|
|
||||||
|
case ID = "id";
|
||||||
|
case EMAIL = "email";
|
||||||
|
case MESSAGE = "message";
|
||||||
|
case DATE_CREATED = "date_created";
|
||||||
}
|
}
|
|
@ -4,21 +4,21 @@
|
||||||
|
|
||||||
use vlw\xEnum;
|
use vlw\xEnum;
|
||||||
|
|
||||||
enum SearchCategoryEnum {
|
enum SearchTypeEnum {
|
||||||
use xEnum;
|
use xEnum;
|
||||||
|
|
||||||
case WORK;
|
case WORK;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SearchTable: string {
|
enum Search: string {
|
||||||
use xEnum;
|
use xEnum;
|
||||||
|
|
||||||
const NAME = "search";
|
const TABLE = "search";
|
||||||
|
|
||||||
case QUERY = "query";
|
case ID = "id";
|
||||||
case ID = "id";
|
case QUERY = "query";
|
||||||
case TITLE = "title";
|
case TYPE = "type";
|
||||||
case SUMMARY = "summary";
|
case TITLE = "title";
|
||||||
case CATEGORY = "category";
|
case TEXT = "text";
|
||||||
case HREF = "href";
|
case HREF = "href";
|
||||||
}
|
}
|
|
@ -4,16 +4,17 @@
|
||||||
|
|
||||||
use vlw\xEnum;
|
use vlw\xEnum;
|
||||||
|
|
||||||
enum ActionsTable: string {
|
enum Actions: string {
|
||||||
use xEnum;
|
use xEnum;
|
||||||
|
|
||||||
const NAME = "work_actions";
|
const TABLE = "work_actions";
|
||||||
|
|
||||||
case REF_WORK_ID = "ref_work_id";
|
case ID = "id";
|
||||||
case ICON_PREPENDED = "icon_prepended";
|
case REF_WORK_ID = "ref_work_id";
|
||||||
case ICON_APPENDED = "icon_appended";
|
case ORDER_IDX = "order_idx";
|
||||||
case ORDER_IDX = "order_idx";
|
case HREF = "href";
|
||||||
case DISPLAY_TEXT = "display_text";
|
case TEXT = "text";
|
||||||
case HREF = "href";
|
case CLASSLIST = "classlist";
|
||||||
case CLASS_LIST = "class_list";
|
case ICON_PREPEND = "icon_prepend";
|
||||||
|
case ICON_APPEND = "icon_append";
|
||||||
}
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Work;
|
|
||||||
|
|
||||||
enum MediaTable: string {
|
|
||||||
const NAME = "work_media";
|
|
||||||
|
|
||||||
case ANCHOR = "anchor";
|
|
||||||
case MEDIA = "media";
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Work;
|
|
||||||
|
|
||||||
enum NamespacesTable: string {
|
|
||||||
const NAME = "work_actions";
|
|
||||||
|
|
||||||
case REF_WORK_ID = "ref_work_id";
|
|
||||||
case ICON_PREFIX = "icon_prefix";
|
|
||||||
case ICON_SUFFIX = "icon_suffix";
|
|
||||||
case ORDER_IDX = "order_idx";
|
|
||||||
case DISPLAY_TEXT = "display_text";
|
|
||||||
case HREF = "href";
|
|
||||||
case CLASS_LIST = "class_list";
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace VLW\Database\Tables\Work;
|
|
||||||
|
|
||||||
enum PermalinksTable: string {
|
|
||||||
const NAME = "work_permalinks";
|
|
||||||
|
|
||||||
case SLUG = "slug";
|
|
||||||
case ANCHOR = "anchor";
|
|
||||||
case DATE_TIMESTAMP_CREATED = "date_timestamp_created";
|
|
||||||
}
|
|
|
@ -13,11 +13,12 @@
|
||||||
case REPO;
|
case REPO;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TagsTable: string {
|
enum Tags: string {
|
||||||
use xEnum;
|
use xEnum;
|
||||||
|
|
||||||
const NAME = "work_tags";
|
const TABLE = "work_tags";
|
||||||
|
|
||||||
|
case ID = "id";
|
||||||
case REF_WORK_ID = "ref_work_id";
|
case REF_WORK_ID = "ref_work_id";
|
||||||
case LABEL = "label";
|
case LABEL = "label";
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue