diff --git a/api/endpoints/work/DELETE.php b/api/endpoints/work/DELETE.php index 47b5d29..2b5905a 100755 --- a/api/endpoints/work/DELETE.php +++ b/api/endpoints/work/DELETE.php @@ -39,10 +39,7 @@ ->min(1) ->max(parent::MYSQL_TEXT_MAX_LENGTH), - (new Rules(WorkModel::IS_LISTABLE->value)) - ->type(Type::BOOLEAN), - - (new Rules(WorkModel::IS_READABLE->value)) + (new Rules(WorkModel::IS_LISTED->value)) ->type(Type::BOOLEAN), (new Rules(WorkModel::DATE_MODIFIED->value)) diff --git a/api/endpoints/work/GET.php b/api/endpoints/work/GET.php index aa8dd66..21f3046 100755 --- a/api/endpoints/work/GET.php +++ b/api/endpoints/work/GET.php @@ -15,6 +15,8 @@ require_once Path::root("src/databases/VLWdb.php"); require_once Path::root("src/databases/models/Work/Work.php"); + const PARAM_LIMIT = "limit"; + class GET_Work extends VLWdb { protected Ruleset $ruleset; @@ -35,11 +37,7 @@ ->type(Type::STRING) ->max(parent::MYSQL_TEXT_MAX_LENGTH), - (new Rules(WorkModel::IS_LISTABLE->value)) - ->type(Type::BOOLEAN) - ->default(true), - - (new Rules(WorkModel::IS_READABLE->value)) + (new Rules(WorkModel::IS_LISTED->value)) ->type(Type::BOOLEAN) ->default(true), @@ -51,15 +49,26 @@ (new Rules(WorkModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGTH), + + (new Rules(PARAM_LIMIT)) + ->type(Type::NUMBER) + ->type(Type::NULL) + ->min(1) ->max(parent::MYSQL_INT_MAX_LENGTH) + ->default(null) ]); parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { - // Use copy of search paramters as filters + // Use search parameters from model as filters $filters = $_GET; + // Unset keys not included in database model from filter + foreach (array_diff(array_keys($_GET), WorkModel::values()) as $k) { + unset($filters[$k]); + } // Do a wildcard search on the title column if provided if (array_key_exists(WorkModel::TITLE->value, $_GET)) { @@ -78,12 +87,12 @@ $response = $this->db->for(WorkModel::TABLE) ->where($filters) ->order([WorkModel::DATE_CREATED->value => "DESC"]) + ->limit($_GET[PARAM_LIMIT]) ->select([ WorkModel::ID->value, WorkModel::TITLE->value, WorkModel::SUMMARY->value, - WorkModel::IS_LISTABLE->value, - WorkModel::IS_READABLE->value, + WorkModel::IS_LISTED->value, WorkModel::DATE_YEAR->value, WorkModel::DATE_MONTH->value, WorkModel::DATE_DAY->value, diff --git a/api/endpoints/work/PATCH.php b/api/endpoints/work/PATCH.php index 43eb6a0..de44730 100755 --- a/api/endpoints/work/PATCH.php +++ b/api/endpoints/work/PATCH.php @@ -47,10 +47,7 @@ ->min(1) ->max(parent::MYSQL_TEXT_MAX_LENGTH), - (new Rules(WorkModel::IS_LISTABLE->value)) - ->type(Type::BOOLEAN), - - (new Rules(WorkModel::IS_READABLE->value)) + (new Rules(WorkModel::IS_LISTED->value)) ->type(Type::BOOLEAN), (new Rules(WorkModel::DATE_MODIFIED->value)) diff --git a/api/endpoints/work/POST.php b/api/endpoints/work/POST.php index 74eb7a2..cf6c3c1 100755 --- a/api/endpoints/work/POST.php +++ b/api/endpoints/work/POST.php @@ -41,11 +41,7 @@ ->max(parent::MYSQL_TEXT_MAX_LENGTH) ->default(null), - (new Rules(WorkModel::IS_LISTABLE->value)) - ->type(Type::BOOLEAN) - ->default(false), - - (new Rules(WorkModel::IS_READABLE->value)) + (new Rules(WorkModel::IS_LISTED->value)) ->type(Type::BOOLEAN) ->default(false), diff --git a/api/endpoints/work/actions/GET.php b/api/endpoints/work/actions/GET.php index e286697..902a97d 100644 --- a/api/endpoints/work/actions/GET.php +++ b/api/endpoints/work/actions/GET.php @@ -38,12 +38,11 @@ WorkActionsModel::REF_WORK_ID->value, WorkActionsModel::DISPLAY_TEXT->value, WorkActionsModel::HREF->value, - WorkActionsModel::CLASS_LIST->value, - WorkActionsModel::EXTERNAL->value + WorkActionsModel::CLASS_LIST->value ]); return $response->num_rows > 0 - ? new Response($response->fetch_all(MYSQLI_ASSOC)) + ? new Response(parent::index_array_by_key($response->fetch_all(MYSQLI_ASSOC), WorkActionsModel::REF_WORK_ID->value)) : new Response([], 404); } } \ No newline at end of file diff --git a/api/endpoints/work/actions/POST.php b/api/endpoints/work/actions/POST.php index 2a07540..7c8175a 100755 --- a/api/endpoints/work/actions/POST.php +++ b/api/endpoints/work/actions/POST.php @@ -50,11 +50,7 @@ (new Rules(WorkActionsModel::CLASS_LIST->value)) ->type(Type::ARRAY) ->min(1) - ->default([]), - - (new Rules(WorkActionsModel::EXTERNAL->value)) - ->type(Type::BOOLEAN) - ->default(false) + ->default([]) ]); parent::__construct(Databases::VLW, $this->ruleset); diff --git a/api/endpoints/work/tags/GET.php b/api/endpoints/work/tags/GET.php index bacfb66..9d2bae3 100644 --- a/api/endpoints/work/tags/GET.php +++ b/api/endpoints/work/tags/GET.php @@ -45,7 +45,7 @@ ]); return $response->num_rows > 0 - ? new Response($response->fetch_all(MYSQLI_ASSOC)) + ? new Response(parent::index_array_by_key($response->fetch_all(MYSQLI_ASSOC), WorkTagsModel::REF_WORK_ID->value)) : new Response([], 404); } } \ No newline at end of file diff --git a/api/src/databases/VLWdb.php b/api/src/databases/VLWdb.php index 5286539..7a06a4f 100755 --- a/api/src/databases/VLWdb.php +++ b/api/src/databases/VLWdb.php @@ -37,6 +37,11 @@ ); } + // Bail out if provided ReflectRules\Ruleset is invalid + private static function eval_ruleset_or_exit(Ruleset $ruleset): ?Response { + return !$ruleset->is_valid() ? new Response($ruleset->get_errors(), 422) : null; + } + // Generate and return UUID4 string public static function gen_uuid4(): string { return sprintf("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", @@ -76,8 +81,21 @@ return $filters; } - // Bail out if provided ReflectRules\Ruleset is invalid - private static function eval_ruleset_or_exit(Ruleset $ruleset): ?Response { - return !$ruleset->is_valid() ? new Response($ruleset->get_errors(), 422) : null; + public function index_array_by_key(array $input, string $key): array { + $output = []; + + foreach ($input as $item) { + $idx = $item[$key]; + + // Create entry for key in output array if first item + if (!array_key_exists($idx, $output)) { + $output[$idx] = []; + } + + // Append item to array of array by key + $output[$idx][] = $item; + } + + return $output; } } \ No newline at end of file diff --git a/api/src/databases/models/Work/Work.php b/api/src/databases/models/Work/Work.php index 0111a30..30c1e0d 100644 --- a/api/src/databases/models/Work/Work.php +++ b/api/src/databases/models/Work/Work.php @@ -2,18 +2,21 @@ namespace VLW\API\Databases\VLWdb\Models\Work; + use victorwesterlund\xEnum; + enum WorkModel: string { + use xEnum; + const TABLE = "work"; - case ID = "id"; - case TITLE = "title"; - case SUMMARY = "summary"; - case COVER_SRCSET = "cover_srcset"; - case IS_LISTABLE = "is_listable"; - case IS_READABLE = "is_readable"; - case DATE_YEAR = "date_year"; - case DATE_MONTH = "date_month"; - case DATE_DAY = "date_day"; - case DATE_MODIFIED = "date_modified"; - case DATE_CREATED = "date_created"; + case ID = "id"; + case TITLE = "title"; + case SUMMARY = "summary"; + case COVER_SRCSET = "cover_srcset"; + case IS_LISTED = "is_listed"; + case DATE_YEAR = "date_year"; + case DATE_MONTH = "date_month"; + case DATE_DAY = "date_day"; + case DATE_MODIFIED = "date_modified"; + case DATE_CREATED = "date_created"; } \ No newline at end of file diff --git a/api/src/databases/models/Work/WorkActions.php b/api/src/databases/models/Work/WorkActions.php index dc5021d..6b411a1 100644 --- a/api/src/databases/models/Work/WorkActions.php +++ b/api/src/databases/models/Work/WorkActions.php @@ -6,8 +6,10 @@ const TABLE = "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"; - case EXTERNAL = "external"; } \ No newline at end of file diff --git a/api/src/databases/models/Work/WorkNamespaces.php b/api/src/databases/models/Work/WorkNamespaces.php new file mode 100644 index 0000000..0834725 --- /dev/null +++ b/api/src/databases/models/Work/WorkNamespaces.php @@ -0,0 +1,15 @@ + .track p::before { - content: "/ "; - color: rgba(255, 255, 255, .3); -} - -/* ### Item */ - -section.timeline .items .item { + z-index: 1; + height: 100%; display: flex; + position: relative; + align-items: baseline; flex-direction: column; - gap: calc(var(--padding) / 2); - padding: var(--padding); + padding: calc(var(--padding) * 1.5); } -section.timeline .items .item + .item { - border-top: solid 2px rgba(255, 255, 255, .2); +section.hero .item .title { + display: grid; + align-items: center; + gap: var(--padding); + grid-template-columns: 40px 1fr; } -section.timeline .items .item:first-of-type { - margin-top: var(--padding); - border-top: solid 2px var(--color-accent); -} - -/* No border style for the latest item (from the top) in the list */ -section.timeline .year:first-of-type .month:first-of-type .day:first-of-type .items .item:first-of-type { - margin-top: unset; - border-top: unset; -} - -section.timeline .items .item .tags { - display: flex; - gap: calc(var(--padding) / 2); -} - -section.timeline .items .item .tags .tag { - font-size: 11px; - letter-spacing: 1px; - color: rgba(255, 255, 255, .7); - background-color: rgba(255, 255, 255, .15); +section.hero .item .title svg { + height: 3em; border-radius: 4px; - padding: 5px 10px; } -section.timeline .items .item img { - max-width: 100%; - height: 250px; +section.hero .actions { + margin-top: auto; } -section.timeline .items .item .actions { - margin-top: 7px; +/* ### Vegivisr */ + +section.hero .item.vegvisir { + --color-accent: var(--color-vegvisir); + + color: rgb(var(--color-vegvisir)); + background-color: rgba(var(--color-vegvisir), .1); } -/* ## Note */ +/* ### Reflect */ -section.note { - text-align: center; +section.hero .item.reflect { + --color-accent: var(--color-reflect); + + color: rgb(var(--color-reflect)); + background-color: rgba(var(--color-reflect), .2); +} + +/* ## Heading */ + +section.heading { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; +} + +section.heading svg { + fill: white; + height: 2em; +} + +/* ## Featured */ + +section.featured { + display: grid; + gap: var(--padding); + grid-template-columns: repeat(1, 1fr); +} + +section.featured featured-item { + display: flex; + fill: white; + color: white; + border-radius: 8px; + align-items: baseline; + flex-direction: column; + padding: var(--padding); + background-color: rgba(255, 255, 255, .1); +} + +section.featured featured-item .title { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: calc(var(--padding) / 2); +} + +section.featured featured-item .title svg { + height: 2em; + fill: var(--color-accent); +} + +/* ### Languages */ + +/* ### Actions */ + +section.featured featured-item .actions { + gap: 5px; + display: flex; + padding-top: var(--padding); + margin-top: auto; } /* # Size queries */ -@media (min-width: 460px) { - section.git .buttons { - flex-direction: row; +@media (min-width: 600px) { + section.hero { + grid-template-columns: repeat(2, 1fr); } } @media (min-width: 900px) { - section.git { - display: grid; - grid-template-columns: 70px 1fr 400px; - align-items: center; - gap: calc(var(--padding) * 1.5); - } - - section.git svg { - width: 100%; - } - - section.git .buttons { - justify-content: end; - } -} - -@media (max-width: 500px) { - section.timeline { - padding: unset; - } - - section.timeline .track { - position: relative; - background: unset; - z-index: 10; - pointer-events: none; - } - - section.timeline .track p { - background-color: black; - } - - section.timeline :is(.years, .year, .months, .month, .days, .day) { - width: 0; - } - - section.timeline .items { - position: relative; - left: -140px; - } - - section.timeline .items .item { - padding: calc(var(--padding) * 1.5) 0; - width: calc(100vw - (var(--padding) * 3.5)); - } - - section.timeline .items .item:first-of-type { - border-top-color: rgba(var(--primer-color-accent), .2); - } - - section.timeline .year:first-of-type .month:first-of-type .day:first-of-type .items .item:first-of-type { - margin-top: var(--padding); + section.featured { + grid-template-columns: repeat(3, 1fr); } } \ No newline at end of file diff --git a/public/assets/css/pages/work/timeline.css b/public/assets/css/pages/work/timeline.css new file mode 100644 index 0000000..d3228d6 --- /dev/null +++ b/public/assets/css/pages/work/timeline.css @@ -0,0 +1,188 @@ +/* # Overrides */ + +:root { + --primer-color-accent: 3, 255, 219; + --color-accent: rgb(var(--primer-color-accent)); +} + +vv-shell { + display: flex; + flex-direction: column; + gap: var(--padding); + width: 100%; + max-width: 1200px; + overflow-x: initial; +} + +/* # Sections */ + +/* ## Git */ + +section.git { + display: flex; + flex-direction: column; + gap: var(--padding); + background-color: rgba(var(--primer-color-accent), .1); + padding: calc(var(--padding) * 1.5); + border-radius: 6px; +} + +section.git svg { + fill: white; + width: 60px; +} + +section.git .buttons { + display: flex; + flex-direction: column; + gap: var(--padding); +} + +/* ## Timeline */ + +section.timeline { + --timestamp-gap: calc(var(--padding) / 2); + + width: 100%; +} + +section.timeline :is(.year, .month, .day) { + display: grid; + grid-template-columns: calc(40px + var(--timestamp-gap)) 1fr; + grid-template-rows: 1fr; +} + +section.timeline .track { + --opacity: .15; + --width: 2%; + + background: linear-gradient(90deg, + transparent 0%, transparent calc(50% - var(--width)), + rgba(255, 255, 255, var(--opacity)) calc(50% - var(--width)), rgba(255, 255, 255, var(--opacity)) calc(50% + var(--width)), + transparent calc(50% + var(--width)), transparent 100% + ); +} + +section.timeline .track p { + position: sticky; + top: calc(var(--running-size) + var(--padding)); + padding: calc(var(--padding) / 2) 0; + background-color: black; + color: var(--color-accent); +} + +section.timeline :not(.year) > .track p::before { + content: "/ "; + color: rgba(255, 255, 255, .3); +} + +/* ### Item */ + +section.timeline .items .item { + display: flex; + flex-direction: column; + gap: calc(var(--padding) / 2); + padding: var(--padding); +} + +section.timeline .items .item + .item { + border-top: solid 2px rgba(255, 255, 255, .2); +} + +section.timeline .items .item:first-of-type { + margin-top: var(--padding); + border-top: solid 2px var(--color-accent); +} + +/* No border style for the latest item (from the top) in the list */ +section.timeline .year:first-of-type .month:first-of-type .day:first-of-type .items .item:first-of-type { + margin-top: unset; + border-top: unset; +} + +section.timeline .items .item .tags { + display: flex; + gap: calc(var(--padding) / 2); +} + +section.timeline .items .item .tags .tag { + font-size: 11px; + letter-spacing: 1px; + color: rgba(255, 255, 255, .7); + background-color: rgba(255, 255, 255, .15); + border-radius: 4px; + padding: 5px 10px; +} + +section.timeline .items .item img { + max-width: 100%; + height: 250px; +} + +section.timeline .items .item .actions { + margin-top: 7px; +} + +/* # Size queries */ + +@media (min-width: 460px) { + section.git .buttons { + flex-direction: row; + } +} + +@media (max-width: 500px) { + section.timeline { + padding: unset; + } + + section.timeline .track { + position: relative; + background: unset; + z-index: 10; + pointer-events: none; + } + + section.timeline .track p { + background-color: black; + } + + section.timeline :is(.years, .year, .months, .month, .days, .day) { + width: 0; + } + + section.timeline .items { + position: relative; + left: -140px; + } + + section.timeline .items .item { + padding: calc(var(--padding) * 1.5) 0; + width: calc(100vw - (var(--padding) * 3.5)); + } + + section.timeline .items .item:first-of-type { + border-top-color: rgba(var(--primer-color-accent), .2); + } + + section.timeline .year:first-of-type .month:first-of-type .day:first-of-type .items .item:first-of-type { + margin-top: var(--padding); + } +} + +@media (min-width: 900px) { + section.git { + display: grid; + grid-template-columns: 70px 1fr 400px; + align-items: center; + gap: calc(var(--padding) * 1.5); + } + + section.git svg { + width: 100%; + } + + section.git .buttons { + justify-content: end; + } +} \ No newline at end of file diff --git a/public/assets/css/pages/work/wip.css b/public/assets/css/pages/work/wip.css new file mode 100644 index 0000000..aa7c498 --- /dev/null +++ b/public/assets/css/pages/work/wip.css @@ -0,0 +1,15 @@ +/* # Overrides */ + +:root { + --primer-color-accent: 3, 255, 219; + --color-accent: rgb(var(--primer-color-accent)); +} + +vv-shell { + display: flex; + flex-direction: column; + gap: var(--padding); + width: 100%; + max-width: 1200px; + overflow-x: initial; +} \ No newline at end of file diff --git a/public/assets/css/shell.css b/public/assets/css/shell.css index f1e927b..56d0743 100644 --- a/public/assets/css/shell.css +++ b/public/assets/css/shell.css @@ -285,6 +285,7 @@ search-results { transform: scale(.99); transform-origin: 100% 0; overflow-y: scroll; + z-index: 50; } search-results:not([vv-page]) { diff --git a/public/assets/js/pages/work.js b/public/assets/js/pages/work/timeline.js similarity index 100% rename from public/assets/js/pages/work.js rename to public/assets/js/pages/work/timeline.js diff --git a/public/assets/media/icons/reflect.svg b/public/assets/media/icons/reflect.svg new file mode 100644 index 0000000..39d0f60 --- /dev/null +++ b/public/assets/media/icons/reflect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/media/icons/repo.svg b/public/assets/media/icons/repo.svg new file mode 100644 index 0000000..4be9da5 --- /dev/null +++ b/public/assets/media/icons/repo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/media/icons/star.svg b/public/assets/media/icons/star.svg new file mode 100644 index 0000000..9fca4b6 --- /dev/null +++ b/public/assets/media/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/media/icons/vegvisir.svg b/public/assets/media/icons/vegvisir.svg new file mode 100644 index 0000000..5c52250 --- /dev/null +++ b/public/assets/media/icons/vegvisir.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/media/icons/vw.svg b/public/assets/media/icons/vw.svg new file mode 100644 index 0000000..a9bd24a --- /dev/null +++ b/public/assets/media/icons/vw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/media/vegvisir.webm b/public/assets/media/vegvisir.webm new file mode 100644 index 0000000..164810f Binary files /dev/null and b/public/assets/media/vegvisir.webm differ diff --git a/public/search.php b/public/search.php index fd4f7d0..51bf8a5 100644 --- a/public/search.php +++ b/public/search.php @@ -66,11 +66,7 @@ json() as $action): ?> - value]): ?> - - - - + diff --git a/public/work.php b/public/work.php index 9bc9ec9..00dc63a 100644 --- a/public/work.php +++ b/public/work.php @@ -1,181 +1,138 @@ call(Endpoints::WORK->value)->get(); + $work = new class extends API { + const ERROR_MSG = "Something went wrong"; - // Resolve tags and actions if we got work results - if ($resp_work->ok) { - $work_tags = $api->call(Endpoints::WORK_TAGS->value)->get()->json(); - $work_actions = $api->call(Endpoints::WORK_ACTIONS->value)->get()->json(); + private readonly Response $resp; + + public function __construct() { + parent::__construct(); + + // Get work items from endpoint + $this->resp = $this->call(Endpoints::WORK->value)->params([ + WorkModel::IS_LISTED->value => true + ])->get(); + } + + private function get_item(string $key): array { + $idx = array_search($key, array_column($this->resp->json(), WorkModel::ID->value)); + return $this->resp->json()[$idx]; + } + + public function get_summary(string $key): string { + return $this->resp->ok ? $this->get_item($key)[WorkModel::SUMMARY->value] : self::ERROR_MSG; + } } ?> - -
- -

I have moved most of my free open-source software away from GitHub to Codeberg. I also have a mirror of everything and sources for some smaller projects on Forgejo.

-
- - +
+
+
+
+ +

vegvisir

+
+

get_summary("vlw/vegvisir") ?>

+
+ +
+
+
+
+
+
+ +

reflect

+
+

get_summary("vlw/reflect") ?>

+
+ +
+
- -ok): ?> - [[02 => [14 => []]]]] - */ - - $rows = []; - // Create array of arrays ordered by decending year, month, day, items - foreach ($resp_work->json() as $row) { - // Create array for current year if it doesn't exist - if (!array_key_exists($row[WorkModel::DATE_YEAR->value], $rows)) { - $rows[$row[WorkModel::DATE_YEAR->value]] = []; - } - - // Create array for current month if it doesn't exist - if (!array_key_exists($row[WorkModel::DATE_MONTH->value], $rows[$row[WorkModel::DATE_YEAR->value]])) { - $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]] = []; - } - - // Create array for current day if it doesn't exist - if (!array_key_exists($row[WorkModel::DATE_DAY->value], $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]])) { - $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]] = []; - } - - // Append item to ordered array - $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]][] = $row; - } - - ?> - -
- - $months): ?> -
-
-

-
- -
- - $days): ?> -
-
- -

-
- -
- - $items): ?> -
-
- -

-
- -
- -
- - - value), $item[WorkModel::ID->value]); ?> - - - -
- - - - -

value] ?>

- -
- - - - value])): ?> -

value] ?>

- - -

value] ?>

- - - value), $item[WorkModel::ID->value]); ?> - - - -
- - value] - // Bind VV Interactions for local links - ? "vv='work' vv-call='navigate'" - // Open external links in a new tab - : "target='_blank'"; - - $link_href = $action[WorkActionsModel::HREF->value] === null - // Navigate to work details page if no href is defined - ? "/work/{$item[WorkModel::ID->value]}" - // Href is defined so use it directly - : $action[WorkActionsModel::HREF->value]; - ?> - - > - -
- - -
- -
- -
- -
- -
- -
- -
- -
-
-

This is not really the end of the list. I will add some of my notable older work at some point.

-
- -

Something went wrong!

- - - + +
+

latest projects

+
+ +
+ +
\ No newline at end of file diff --git a/public/work/deltaco/asyncapp.php b/public/work/deltaco/asyncapp.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/deltaco/asyncapp.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/deltaco/distit.php b/public/work/deltaco/distit.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/deltaco/distit.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/deltaco/e-charge.php b/public/work/deltaco/e-charge.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/deltaco/e-charge.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/deltaco/office.php b/public/work/deltaco/office.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/deltaco/office.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/deltaco/pdf-generator.php b/public/work/deltaco/pdf-generator.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/deltaco/pdf-generator.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/deltaco/reseller-form.php b/public/work/deltaco/reseller-form.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/deltaco/reseller-form.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/icellate/genemate.php b/public/work/icellate/genemate.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/icellate/genemate.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/icellate/website.php b/public/work/icellate/website.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/icellate/website.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/itg/lan.php b/public/work/itg/lan.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/itg/lan.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/itg/upload.php b/public/work/itg/upload.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/itg/upload.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/timeline.php b/public/work/timeline.php new file mode 100644 index 0000000..d783653 --- /dev/null +++ b/public/work/timeline.php @@ -0,0 +1,184 @@ +resp = $this->call(Endpoints::WORK->value)->params([ + WorkModel::IS_LISTED->value => true, + self::API_PARAM_LIMIT => $_GET[self::API_PARAM_LIMIT] ?? null + ])->get(); + + // Fetch metadata for work items if we got an ok from work endpoint + if ($this->resp->ok) { + $this->tags = $this->call(Endpoints::WORK_TAGS->value)->get(); + $this->actions = $this->call(Endpoints::WORK_ACTIONS->value)->get(); + } + } + + /* + Order response from endpoint into a multi-dimensional array. + For example, a single item created at 14th of February 2024 would be ordered like this + [2024 => [[02 => [14 => []]]]] + */ + public function get_timeline(): array { + if (!$this->resp->ok) { + return []; + } + + $timeline = []; + + // Create array of arrays ordered by decending year, month, day, items + foreach ($this->resp->json() as $row) { + // Create array for current year if it doesn't exist + if (!array_key_exists($row[WorkModel::DATE_YEAR->value], $timeline)) { + $timeline[$row[WorkModel::DATE_YEAR->value]] = []; + } + + // Create array for current month if it doesn't exist + if (!array_key_exists($row[WorkModel::DATE_MONTH->value], $timeline[$row[WorkModel::DATE_YEAR->value]])) { + $timeline[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]] = []; + } + + // Create array for current day if it doesn't exist + if (!array_key_exists($row[WorkModel::DATE_DAY->value], $timeline[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]])) { + $timeline[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]] = []; + } + + // Append item to ordered array + $timeline[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]][] = $row; + } + + return $timeline; + } + + public function get_tags(string $key): array { + if (!$this->resp->ok) { + return []; + } + + return in_array($key, $this->tags->json()) ? $this->tags->json()[$key] : []; + } + + public function get_actions(string $key): array { + if (!$this->resp->ok) { + return []; + } + + return array_key_exists($key, $this->actions->json()) ? $this->actions->json()[$key] : []; + } + } + +?> + +
+ +

This timeline has most but not all of my FOSS software. If you want to see a list of all things I've created for the free software world, check out my repos on Codeberg or Forgejo.

+ +
+
+ + + get_timeline() as $year => $months): ?> +
+
+

+
+ +
+ + $days): ?> +
+
+ +

+
+ +
+ + $items): ?> +
+
+ +

+
+ +
+ +
+ + + get_tags($item[WorkModel::ID->value])): ?> +
+ get_tags($item[WorkModel::ID->value]) as $tag): ?> +

value] ?>

+ +
+ + + + value])): ?> +

value] ?>

+ + +

value] ?>

+ +
+ get_actions($item[WorkModel::ID->value])): ?> + + + get_actions($item[WorkModel::ID->value]) as $action): ?> + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ \ No newline at end of file diff --git a/public/work/vlw/camera-obscura.php b/public/work/vlw/camera-obscura.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/vlw/camera-obscura.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/vlw/dediprison.php b/public/work/vlw/dediprison.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/vlw/dediprison.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/vlw/eyeart.php b/public/work/vlw/eyeart.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/vlw/eyeart.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/vlw/ion-musik.php b/public/work/vlw/ion-musik.php new file mode 100644 index 0000000..3deb03c --- /dev/null +++ b/public/work/vlw/ion-musik.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/work/wip.php b/public/work/wip.php new file mode 100644 index 0000000..b43bd06 --- /dev/null +++ b/public/work/wip.php @@ -0,0 +1,8 @@ + +
+

Soon, very soon!

+

Bear with me as I cook up some texts about this project! Hopefully with some pictures too.

+
+
+ +
\ No newline at end of file