Nuxt Content 3 migration #12

Merged
eggy merged 81 commits from js into master 2022-08-10 18:38:39 -04:00
28 changed files with 715 additions and 3 deletions
Showing only changes of commit 81a7f8b541 - Show all commits

View File

@ -1,10 +1,15 @@
<script setup lang="ts">
const { src } = defineProps<{ src: string }>();
const imgSrc =
src.startsWith("http://") || src.startsWith("https://")
? src
: `/images/posts/${src}`;
</script>
<template>
<figure>
<img :src="`/images/posts/${src}`" />
<figure class="flex flex-col items-center">
<img :src="imgSrc" />
<figcaption class="text-center"><Markdown /></figcaption>
</figure>
</template>

View File

@ -0,0 +1,77 @@
---
title: "3 Reasons to Switch to Linux"
date: "2021-10-30"
tags:
- tech
---
In the computing world, Linux is already extremely widespread — it's near-ubiquitous on servers as well as many appliances such as smart fridges, [cars](https://www.zdnet.com/article/tesla-starts-to-release-its-cars-open-source-linux-software-code/), or even [Mars helicopters](https://www.theverge.com/2021/2/19/22291324/linux-perseverance-mars-curiosity-ingenuity) . If you have an Android phone, you're running Linux already. So why not give it a shot on your computer, too?
<!-- more -->
First, what is Linux? At its heart, it is a [kernel](https://en.wikipedia.org/wiki/Kernel_(operating_system)) that the rest of your operating system and software depends on, from drivers to the display manager to games.
However, this article will largely focus on **desktop Linux**, which competes with other operating systems such as Windows and macOS.
# Complete freedom
Perhaps the biggest feature of Linux is its ability to do whatever you want, however you want. After a *tiny* bit of tinkering, you'll be able to set up your computer exactly how you'd like it!
::image{src=sway-desktop.png}
A terminal, an emulated Switch game, a game launcher, and a browser all automagically arranged by a tiling window manager. The currently playing song is in the top bar.
::
Or, if you aren't the type to spend hours fiddling every little thing, you can choose from a variety of existing default desktop interfaces.
Try GNOME's minimalistic and beautiful desktop with its well-integrated virtual workspaces:
![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/Gnome_40_navigation.webm/1200px--Gnome_40_navigation.webm.jpg)
![](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Screenshot_of_GNOME_40_taken_on_Manjaro.png/1200px-Screenshot_of_GNOME_40_taken_on_Manjaro.png)
Or Plasma's endless customisation:
![](https://upload.wikimedia.org/wikipedia/commons/9/94/KDE_Plasma_5.21_Breeze_Twilight_screenshot.png)
::image{src=https://www.omgubuntu.co.uk/wp-content/uploads/2019/07/kde-plasma-desktop.jpg}
::
And this is only the beginning — it's not just appearance you have control over, although both GNOME and Plasma also come with their assortment of applications that have designs that perfectly mesh with the desktop, with global theming letting you click a single button in your settings menu to change colours or styles across all your apps.
Don't like your file manager? Swap it out for one of the dozens out there. Don't like how the update manager churns in the background? Turn it off! Or heck, even switch to another flavour of Linux that doesn't ask you to update so often. The sky's the limit!
**You can do anything.**
# The package manager
Speaking of the update manager…
Take a look at your phone and how you get new software or update old software — the App Store on iPhones and Google Play on Android phones. Now imagine being able to do that…on your computer. Being able to install whatever you want using a store with built-in security along with updates.
No more downloading random apps from the internet and hoping they aren't infected!
Not only that, but you can also update your computer *while* you're using it — no more waiting ages for Windows to finish doing what it does — most of the time you don't even need to reboot, but when you do it's just as fast as when you turn your computer on normally.
Here are just a couple of the graphical stores available:
GNOME Software for GNOME:
::image{src=https://www.omgubuntu.co.uk/wp-content/uploads/2021/02/gnome-software-refresh.jpg}
::
Discover for Plasma:
::image{src=https://userbase.kde.org/images.userbase/thumb/2/2d/Discoverappfocus.png/500px-Discoverappfocus.png}
::
By contrast, the Microsoft Store was (is) a complete and utter mess that is nowhere near the integration and experience Linux has had for decades.
# Open source
Not only that, desktop Linux was built by thousands of volunteers, each contributing their own code to make the best product they can. Because it's completely open source (anyone can see or edit the source code), it's inherently more secure as simply more people are looking at it to fix issues and squash bugs.
Learning Linux is a great opportunity to jump into learning more about computers because of the knowledge you gain over time of how your computer works on a fundamental level as you inevitably start troubleshooting *when* something breaks. And perhaps you'll be the one to contribute back upstream to the project too, if you fix a bug or add a new feature, and have your own code distributed around to millions of other users.
# Try it now!
With dozens of well-maintained versions of Linux operating systems out there, you'll be sure to find one that suits your needs. To try GNOME, [Pop!_OS](https://pop.system76.com/) or [Fedora](https://getfedora.org/en/workstation/download/) provide a seamless out-of-the-box experience. To try Plasma, [Kubuntu](https://kubuntu.org/) is a fantastic starting point. To get a macOS-like feel, [Elementary OS](https://elementary.io/) gives you that Apple vibe while, like every other Linux OS, is completely free of charge, and lets you try it out before you decide to install it.

View File

@ -0,0 +1,14 @@
---
title: Being Complacent
date: 2021-05-21
tags:
- misc
---
Imagine this: you think you're doing great in the world, and all your friends are telling you you're doing great in the world. But one day, you look outside the bubble you're in and realise just how far behind you really are.
<!-- more -->
This is what complacency leads to. By limiting your own perspective and telling yourself that you're doing great, you don't notice how everyone else is capable of so much more than you are. Even if you once had a near-insurmountable lead, by no longer applying pressure to keep moving forward, or by reducing pressure to the point that you *feel* like you're moving forward but are practically staying still, anyone can catch up should they decide to.
It is important to avoid being complacent. Never think what you're doing is completely enough. Continuously push your boundaries. Like in Maoism, "constant revolution" is needed to "prevent counter-revolution". Constant progression is required to prevent stagnation.

View File

@ -0,0 +1,262 @@
---
title: Handy Python Tips
date: 2021-05-29
tags:
- blog
- tech
---
Python is a flexible programming language that is well-known for its simplicity and numerous features that make programming in it a lot faster. This article presents just a few that might speed up your productivity a little bit.
<!-- more -->
## 1. Tuple expansion
```python
a, b = tuple([1, 2])
s1, s2 = input(), int(input())
```
Tuple expansion is one of Python's most powerful features, allowing you to assign multiple values or expand multiple values of a tuple in a single line.
```python
>>> print(a, b)
1 2
>>> print(s1, s2)
baguette
pomme
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'pomme'
```
The code above effectively assigns a tuple `(a, b)` to another tuple `(1, 2)`, and is equivalent to the code below:
```python
a = 1
b = 2
s1 = input()
s2 = int(input())
```
This can let you return multiple values from a function.
```python
def init():
return (5, 6)
a, b = init()
print(a, b)
```
Output:
```
5 6
```
Tuple expansion is also used in for loops to iterate over tuples or even dictionaries.
```python
array = [
(1, 2),
(3, 4),
(5, 6)
]
items = {
7: 8,
9: 10,
11: 12
}
for i, j in array:
print(i, j)
for k, v in items.items():
print(k, v)
```
Output:
```
1 2
3 4
5 6
7 8
9 10
11 12
```
## 2. Nicer iteration with zip() and enumerate()
Python's for loop is commonly known in other programming languages as a for-each loop. This is great if you just want each item in an iterable, but sometimes you want the index too! Instead of having to resort to `range(len(array))`, instead you can use `enumerate()` and tuple expansion to easily get both the index of the element and the element itself:
```python
array = ["a", "b", "c", "d", "e"]
for i, c in enumerate(array):
print(i, c)
```
Output:
```
0 a
1 b
2 c
3 d
4 e
```
In a similar vein, if you have two arrays you want to process at the same time, you don't need to use `range(len(array))` when you have `zip()`, which will bundle the different iterators into one big one as big as the smallest iterable.
```python
ints = [1, 2, 3, 4]
strs = ("pomme", "poutine", "pinterest", "pear")
floats = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
for i, s, f in zip(ints, strs, floats):
print(i, s, f)
```
Output:
```
1 pomme 1.0
2 poutine 2.0
3 pinterest 3.0
4 pear 4.0
```
## 3. Iterable unpacking
In competitive programming, often you have to print out space-separated results. This can be done by the mildly inconveniencing
```python
print(" ".join([1, 2, 3, 4]))
```
or heaven forbid, via iteration:
```python
for i in [1, 2, 3, 4]:
print(i, end=" ")
print()
```
which is where iterable unpacking comes in, and you can go straight to
```python
print(*[1, 2, 3, 4])
```
The **unpacking operator** (the asterisk) basically gets rid of the container and throws all of the elements inside directly into the print function as parameters.
That means that the above line of code is equivalent to:
```python
print(1, 2, 3, 4)
```
which nicely prints out the integers separated by spaces with a newline at the end.
But wait, there's more! The unpacking operator is also commonly used in function definitions as a catch-all parameter for extra arguments, stuffing them into a list.
```python
def init(a, b, *args):
print(a, b)
print(args)
init(1, 2, "pomme", 4, 6.0)
```
Output:
```
1, 2
['pomme', 4, 6.0]
```
You can also use this in normal assignment:
```python
a, *args, b = (1, 2, 3, 4, 5)
print(a)
print(args)
print(b)
```
Output:
```
1
[2, 3, 4]
5
```
And it can be used as a handy way to expand a list instead of using `list()`. The comma at the end of the variable is there to indicate it is a tuple with a single element (a singleton).
```python
*s, = "abcde"
print(s)
```
Output:
```
['a', 'b', 'c', 'd', 'e']
```
## 4. map()
This makes one-liners super easy in Python. `map()` takes a function in the first parameter and applies it to all values in the iterable in the second parameter.
```python
def readints(string):
print(list(map(int, string.split()))
readInts("1 2 3 4 5")
```
Output:
```
[1, 2, 3, 4, 5]
```
It's most useful in assigning variables easily when you know the format the input will be in.
```python
a, b = (map(int, input().split()))
```
## 5. List generators
You can generate a new list using inline `for`.
```python
array = [i for i in range(10)]
print(array)
```
Output:
```
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```
## 6. One-line prefix sum array
A [prefix sum array](https://en.wikipedia.org/wiki/Prefix_sum) is a common data structure used in competitive programming. Python's `itertools` module has a wide array of functionality that can make this easier. A traditional PSA requires three lines:
```python
psa = [0]
for i in [1, 2, 3]:
psa.append(psa[-1] + i)
```
Excluding the import, you can shrink it down to one with `itertools`:
```python
import itertools
*psa, = itertools.accumulate([1, 2, 3])
```

View File

@ -0,0 +1,161 @@
---
title: Primoprod Progress Report
date: 2021-08-21
tags:
- primoprod
- tech
---
Welcome to the very first [Primoprod](https://primoprod.eggworld.tk) progress report! In a similar vein to quite a few open source emulation projects (such as those I follow myself using [emufeed](https://github.com/potatoeggy/emufeed/blob/master/sources.py)), I'll be releasing these tidbits in lieu of daily Unstagnation shorts sometimes.
In this hopefully small series of development notes, I'll be laying out my experiences learning web development as an absolute amateur.
This report will cover the beginnings of the project to the present day: 16 July - 20 August 2021.
<!-- more -->
## Introduction
What is Primoprod? Short for "Productivity Primogems", it was born when I noticed that the gacha system employed by games such as *Genshin Impact* could be incredibly addictive, so I decided to see if I could take advantage of it to boost my productivity and at the same time try to learn web development.
The basic premise was to assign a given point value for each productive task and be able to spend those points on something or have them progress toward a milestone, so that productive tasks would be incentivised. The aforementioned gacha games and Uber use this to great effect, so I decided to emulate the Wish UI of [*Genshin Impact*](https://genshin.mihoyo.com/en).
And so the project began! I decided to work with Vue.js because of its gentler learning curve compared to Angular and more traditional HTML/CSS/JS separation compared to React. You can tell when I became more comfortable in using Vue's declarative system in the later components compared to, say, [`App.vue`](https://github.com/potatoeggy/primoprod/blob/master/src/App.vue).
Of course, the project was made open source on [GitHub](https://github.com/potatoeggy/primoprod) and licensed under the [GNU AGPLv3](https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License) so that anyone can use it. (Except for the assets, but we'll get to that later.)
## First steps
As my first foray into web development, there were many tools and practices I could choose from. Vue's justification was outlined in the introduction, but I had no clue how Webpack worked, what folder structure I should use, how I should lay out my code, etc.
Luckily, I didn't have to make any of these decisions because Vue's [CLI](https://cli.vuejs.org/) gives you a list of sane defaults that you can pick from, and since I was learning for the future, I went with the first option of Vue 3 + Typescript. `vue-cli` even nicely [initialised a git repo for me](https://github.com/potatoeggy/primoprod/commit/9b7d7841806c905e8f580f98d1c95d4732178810)!
At the time, coming from Python/Java, I opted in to the class components plugin hoping it made it easier to develop for, but later removed it due to a lack of documentation with it for Vue 3. Occasional downsides of newer technologies ¯\\\_(ツ)_/¯.
The [first few commits](https://github.com/potatoeggy/primoprod/commit/ed9d94b61bf91ea9b82ac4d832dfb2b9ff2efc59) had me playing around until I was comfortable enough to introduce my very [first component](https://github.com/potatoeggy/primoprod/commit/fcbb4068dd3b018db2809ccfcc5381d4ea3ae727): the WishButton.
::image{src=wish-button-emulated.png}
::
I'd say it turned out pretty well! Since I wanted to emulate Genshin's UI, I wanted to match it as closely as I could. These two buttons are made of an image inside of a div relatively positioned with text absolutely positioned inside. Original image for comparison:
::image{src=wish-button-original.png}
::
There are still some differences between the texts since Genshin uses antialiasing, and the alignment and shadow of the icon beside the wish quantity is slightly off too, but I would consider this result to be acceptable.
## CSS pain
When working with "pure" Vue, the hard part is not usually HTML, which is quite simple and is usually obvious if something is wrong, nor is it Vue/TypeScript itself — I know TypeScript saved me some pain by doing type checking prior to runtime several times, even — but instead CSS.
CSS.
No, it's not that it was difficult to center or align a `div`, generally (slap everything in flexboxes and magic happens), but the time spent getting the wish buttons to look exactly how I wanted to would be nothing compared to some of the later parts.
No one warns you about this stuff either; all the memes are about JS not making sense but no one talks about CSS except being hard to center divs!
See [GemCounter](https://github.com/potatoeggy/primoprod/blob/master/src/components/GemCounter.vue), the second component I made. All those precise margins/padding…
Although I had read up on [MDN's](https://developer.mozilla.org/en-US/) fantastic tutorials/documentation a fair bit and used flexboxes and `rem` everywhere, I apparently did not catch `box-sizing: border-box` and the margins and padding just did not arrange themselves how they should have.
::image{src=mdn-box-sizing-tip.png}
::
:/ thanks MDN for letting me know
[Some foreshadowing](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemRevealScreen.vue#L224)
## Smooth beginnings
Designing the basic screen was pretty straightforward. For all its woes, pure CSS still works and is intuitive enough that my git history was only slightly too messy and I got my results.
::image{src=primoprod-wishbanners.png}
::
Pretty good, right? Now, the design still isn't adaptive enough *since things get cut off for who knows why I thought flexboxes were supposed to solve all this* but for the most part it looks good enough. It appears I'll have to make a lot of exceptions for mobile devices…
::image{src=primoprod-wishbanners-scaled.png}
::
With some help taken by examining the assets of https://genshin.thekima.com and https://gi-wish-simulator.uzairashraf.dev, I grabbed a static background image as well as the videos!
For now, every time you pressed the wish button, you got a five-star animation.
## Asset prefetching and inconsistencies
Progress was steady and things appeared to be running perfectly. but on my local machine things load instantly, courtesy of it not being beamed around the world. So when it was deployed to https://primoprod.eggworld.tk, even with a relatively fast download speed, the background image loaded slowly, and there were a few seconds of downtime before a video would play.
This was unacceptable, so I investigated around on how Vue prefetches assets before giving up and setting up an [Electron](https://www.electronjs.org/) build for the desktop — this doesn't resolve the issue on the web, but until I'm more competent with Vue I can't quite make the necessary changes. The issue of slow asset loading also severely lengthened the suffering in the `ItemRevealScreen.vue` arc, where animations and audio had to be timed…
## Gacha and storage backend
And with the basic frontend done, a backend would be needed to determine what items were pulled, and they had to be stored in a compact way.
I opted for a [standard item database](https://github.com/potatoeggy/primoprod/blob/master/src/banners/ItemDatabase.json) accessible from anywhere that would make it easy to add/remove/reference characters in the future. To conserve storage space on the user's end (why do I bother, really), items are stored as [string IDs](https://github.com/potatoeggy/primoprod/blob/master/src/banners/Inventory.ts) and the inventory class has a plethora of helper functions to work with.
The [gacha](https://github.com/potatoeggy/primoprod/blob/master/src/banners/Gacha.ts) file was heavily inspired (and many ideas taken from) https://github.com/uzair-ashraf/genshin-impact-wish-simulator/blob/master/src/models/base-gacha.js, many thanks! Because we don't need the previous-banner-switching capability that it uses, I stripped out the unnecessary bits and kept it simple-ish.
As Primoprod evolved and grew past my initial design of random bullshit go!, these critical classes went through some changes that added functions such as being able to calculate the extra stardust/starglitter rewards from a gacha. This let me keep banners to be comparatively simple, defined in [easy-to-read and copy JSON](https://github.com/potatoeggy/primoprod/blob/master/src/banners/tapestry-of-golden-flames.json).
Typescript was actually really helpful here, since as a nearly complete newcomer to JavaScript, it made sure I was doing legal things — something I also found a little annoying in Python.
Just my opinion, but I much prefer statically typed languages such as C#/Java — not Java screw Java.
## ItemRevealScreen horror
Everything had gone extremely smoothly up to this point. I was making rapid progress, even with the time spent on Stack Overflow to learn CSS and JavaScript.
But then came the pull screen.
https://youtu.be/g1jDTHCRyCY?t=165
It looks simple, right? Deceptively simple. But there's a lot going on here.
There is:
- a zoom-in of the character image, followed by
- a slide-right of the character image, while
- the text slides left, while-after
- the element icon slides left, after which
- the stars appear in sequence, during? which
- the extra reward screen slides left.
Not to mention all of the glow and particle effects. All of these had to be aligned with audio, which I couldn't find after listening for hours on end to many of the sound effects in the game files so gave up and recorded my own pulls.
I don't know how actual developers take care of this — potentially a library — but aside from the alignment of everything (which was its own nightmare to be adaptable across multiple display sizes and I only finished everything but the extra slide yesterday), I spent waaaaaaay too long making Vue work with an "animation index" that tracked whose turn it was to show animations.
The lack of proper asset prefetching was also an issue after deployment since the images took time to load and the animation had already started, so even more went into `animationIndex` to check that the audio and images are loaded before playing.
The code got [progressively worse](https://github.com/potatoeggy/primoprod/commits/master/src/components/ItemRevealScreen.vue) and is now the [most complex](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemRevealScreen.vue) thing that I never want to touch again by far, but there's still more to do ;-;. Not to mention that if I need to add more animation indexes (say, if I need them for glow effects) I am utterly screwed unless I add a second animation index for those effects…
There are still occasional desyncs and alignment isn't actually completely fixed and the extra rewards side is the simplest I can get away with and the weapons are missing backgrounds and shadows and the element icon doesn't glow and the weapon element doesn't have a correct gradient since unfortunately CSS doesn't support recolouring images with gradients.
But hey, it looks good enough, right?
No it doesn't it's not even close but I'm not coming back to that
## Sweet release
Now that `ItemRevealScreen` is *done and over with and will not need any changes*, before making myself work on the equally fun part of the project that is `ItemRevealAllOverlay`, I opted for a break in `ItemObtainScreen` and `ItemDescriptionOverlay` and a one-week hiatus.
And they were easy! Easy and fun. It was blissful to be working with structured HTML and CSS again, and the animation pains there were *nothing* compared to the trauma of `ItemRevealScreen`.
In fact, I consider those two to be 100% done unless I can find a way to apply a border that looks exactly like the original game's that isn't an image.
But it looks great!
::image{src=itemdescriptionoverlay.png}
::
::image{src=itemobtainoverlay.png}
::
## Wrapping up
As it turns out, I'm not actually near good enough nor is web development interesting enough to be able to outline issues like the emulator and Asahi Linux developers.
In fact, this was less a progress report and more complaining like a development log.
As such, every future version will be called a development log since I'm far too lazy to change the title now.
Until next time!

View File

@ -0,0 +1,30 @@
---
title: "Using IWD with YRDSB Wi-Fi"
date: "2021-09-14"
tags:
- tech
---
The iNet Wireless Daemon (iwd) is a lightweight and stable Wi-Fi manager for Linux systems. However, some configuration is needed for it to work properly on WPA Enterprise (802.1X) networks. For YRDSB:
<!-- more -->
Create the file with the following contents at `/var/lib/iwd/<ssid>.8021x`
```ini
[Security]
EAP-Method=PEAP
EAP-Identity=<username>
EAP-PEAP-Phase2-Method=MSCHAPV2
EAP-PEAP-Phase2-Identity=<username>
EAP-PEAP-Phase2-Password=<password>
[Settings]
AutoConnect=true
```
…and then connect to the network normally.
```
iwctl station wlan0 connect YRDSB-S
```

View File

@ -0,0 +1,156 @@
---
title: "Primoprod Progress Report 2"
date: "2022-01-15"
tags:
- tech
- primoprod
---
Six months have passed since the [first progress report](/blog/2021/primoprod-progress-report/). Since then, a flood of changes have made it in, including hundredth-class Android support!
This report will cover from where the previous left off to the present day: 21 August 2021 - 15 January 2022.
<!-- more -->
## No more
::image{src="primoprod-itemrevealscreen.png"}
::
It's done. The pull screen is done. The element/weapon icon was added to the pull. Audio syncs up (well enough). The only thing missing is all of the fancy effects like glow and particles.
Nah, this is good enough.
## Take this!
::image{src="primoprod-questscreen.png"}
::
Until now, you had to manually edit the browser's `localStorage` to gain any currency. The quest screen makes primoprod finally usable as now you can make your own long-term "quests" that give 900 Primogems each as well as set four daily "tasks" that give 30 Primogems each plus 60 when all are done — if only the base game was this generous. These are editable and can have whatever title or description you want. The logic here went through several rewrites as the structure was finalised and an interface developed to the rest of primoprod. Dailies will automatically refresh themselves on the next day.
In addition, there are several more features hidden in the Quest interface, such as an expiry time that you can't currently set.
By the way, JavaScript really doesn't have a good way to set an exact date relative to today — in the end I had to resort to *this* to set an expiry time for dailies.
```js
expires: (() => {
const d = new Date();
d.setDate(d.getDate() + 1);
d.setHours(4, 0, 0, 0);
return d;
// somehow this way is actually nicer than the new Date() way
})(),
```
## First pre-release
The [very first pre-release](https://github.com/potatoeggy/primoprod/releases/tag/1.0-beta1) of primoprod, **1.0-beta1**, was published on 25 August 2021.
It would be a few more months before the first stable release would be sent out into the world.
## Look to the future
Up to now, you might have noticed a considerable lack of pain in this progress report compared to the previous one. And for good reason: I got smarter.
Sounds incredible, right? As it turns out, as you gain more experience with technologies, you make less mistakes and follow best practices more.
This is why [the shop](https://github.com/potatoeggy/primoprod/blob/master/src/components/ShopScreen.vue) and the [dialog to buy things from the shop](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemPurchaseOverlay.vue) are so nicely done! It reused most of my types and was admittedly much simpler than some of the other screens, but I only ran into one insurmountable problem: range styling.
::image{src="primoprod-itempurchaseoverlay.png"}
::
*I wish I was actually this rich in the base game.*
As you can see, the slider looks very out of place. Why? That's because [only Firefox](https://developer.mozilla.org/en-US/docs/Web/CSS/::-moz-range-progress) supports the needed CSS attribute to style the coloured bit before the "thumb" of the slider. I could make up something like Spotify and other services do using JavaScript, but that's a job for my future self.
## Pick your poison
Up until now, only one banner was supported. This was finally fixed in November with the addition of [banner headers](https://github.com/potatoeggy/primoprod/pull/25) to match the base game. Now, you can simultaneously roll for Qiqi on *both* banners!
::image{src="selected-wanderlust-invocation.webp"}
::
On a side note, did you know that Vue puts all of their reactive things into Proxies? This means you can't simply `console.log(obj)` without going through five more clicks to find what you actually want. No, no. To properly print out the actual object, you have to *copy its contents to a clean, non-reactive Object* for this to work. Why??
```js
console.log("Rolled:", this.lastRoll); // nope
```
```js
console.log("Rolled:", [...this.lastRoll.map((i) => Object.assign({}, i))]); // thank you vue love that
```
## First release
After 8 beta pre-releases, [version 1.0.0](https://github.com/potatoeggy/primoprod/releases/tag/1.0.0) of Primoprod was successfully launched on 15 December 2021! I'm happy to tell you that the release went perfectly, was completely stable, and had absolutely no bugs whatsoever.
None.
None at all.
## Imagine not properly structuring your programs
Thanks to my astute design of primoprod, there were zero problems when the base game switched to having [double event banners](https://github.com/potatoeggy/primoprod/releases/tag/1.0-beta8) — they would simply share the same banner storage. Truly a stroke of genius. Practically a zero-line change.
The thing that *did* trip me up was the requirement for descriptions — this required all banners to be [updated](https://github.com/potatoeggy/primoprod/commit/12e7decdc6f5724afda467d6977d566b5c762e2e) to include a description as well as version upgrade code to migrate the pull data from older versions of primoprod into 1.0.1 without issues.
```js
switch (localStorage.version) {
case undefined: // pre-1.0.1: Add descriptions
if (localStorage.pullHistory) {
localStorage.pullHistory = JSON.stringify(
JSON.parse(localStorage.pullHistory).map((pull: Pull) => {
pull.description =
pull.bannerStorage === "event"
? "Character Event Wish"
: "Permanent Wish";
return pull;
})
);
}
console.log("Updated from pre-1.0.1 to 1.0.1");
}
localStorage.version = 1;
```
In hindsight, simply linking the banner they came from to this would have saved more data as well as have been more future-proof.
## Can't stop, won't stop
By this point, I'd become a little fed up with having to wait several minutes for my computer to build primoprod each release every two weeks, so I looked into using GitHub Actions for continuous integration.
Several days of tweaking later, I got GitHub to successfully [build and upload](https://github.com/potatoeggy/primoprod/blob/master/.github/workflows/build.yml) artifacts on each commit for all three operating systems — but they were zipped (even if they were just one file) and couldn't be attached to a release…
Back to laptop compilation we go!
## Pretty badges
Obviously, you aren't a *real* free and open source project if you don't have [pretty badges](https://github.com/potatoeggy/primoprod/blob/master/README.md) on your README. This was a major concern, so I copied Vue's style and now made primoprod a proper FOSS repo — build checkmark and badges and all!
::image{src="primoprod-badges.png"}
::
## Kids and their phones
Annoying as it might be, many people only have phones and browse most often on their phones, so this project has to be mobile compatible. If you saw the last progress report, you know that mobile support was…lacking.
To remedy that for the 1.1.0 release, I spent a few days grinding out and cursing CSS as I was forced to go back to
ItemRevealScreen? What's that? Now [WishBanners](https://github.com/potatoeggy/primoprod/blob/v1.1.0/src/components/WishBanners.vue) is my enemy.
At last, though, we have a [proper mobile UI](https://github.com/potatoeggy/primoprod/pull/33).
::image{src="mobile-primoprod.png"}
::
Still some niggles to work out, but it looks "good enough"! With the completion of proper mobile orientation came the merging of the [Android branch](https://github.com/potatoeggy/primoprod/pull/32) made with [Capacitor.js](https://capacitorjs.com/), basically the mobile equivalent of Electron. It has even more niggles than the web version does.
If you have any idea how to fix them, please do send a suggestion on the [issue tracker!](https://github.com/potatoeggy/primoprod/issues/34)
## Present day
And that's everything that changed since August! Primoprod has come a long way from being a mere personal project to a proper personal project with fancy badges! Hopefully in the future it'll be able to be played standalone (chibi sprite combat, anyone?) so that it'll be interesting enough that people can set their goals and stick to them.
In case you'd like to check it out, version 1.1.0 released just [yesterday](https://github.com/potatoeggy/primoprod/releases/tag/v1.1.0) and is available for download for Windows, macOS, Linux, and Android, with a web version available at https://primoprod.eggworld.tk!
Until next time!

View File

@ -19,7 +19,7 @@ export const tagInfo: Record<string, TagData> = {
primoprod: {
name: "Primoprod",
description:
'Reports following the development of <a href="https://github.com/potatoeggy/primoprod>Primoprod</a>.',
'Reports following the development of <a href="https://github.com/potatoeggy/primoprod">Primoprod</a>.',
},
tech: { name: "Technology" },
unstagnation: {

View File

@ -5,6 +5,9 @@ import { calcReadingTime, getPrettyDate } from "@/shared/metadata";
type GeneralParsedContent = BlogParsedContent | StoryParsedContent;
const route = useRoute();
definePageMeta({
layout: "withtop",
});
// we're not using ContentDoc because i need control
const doc = await queryContent<GeneralParsedContent>(route.path).findOne();

View File

@ -2,6 +2,7 @@
import type { BlogParsedContent } from "@/shared/types";
useTitle("Blog");
definePageMeta({ layout: "withtop" });
// TODO: paginate stories
const docs = await queryContent<BlogParsedContent>("/blog")

View File

@ -2,6 +2,7 @@
import type { StoryParsedContent } from "@/shared/types";
useTitle("Stories");
definePageMeta({ layout: "withtop" });
// TODO: paginate stories
const docs = await queryContent<StoryParsedContent>("/stories")

View File

@ -3,6 +3,7 @@ import { tagInfo, type TagData } from "@/data/tagInfo";
import type { BlogParsedContent } from "@/shared/types";
const route = useRoute();
definePageMeta({ layout: "withtop" });
const tag =
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];

View File

@ -3,6 +3,7 @@ import { tagInfo, type TagData } from "@/data/tagInfo";
import type { StoryParsedContent } from "@/shared/types";
const route = useRoute();
definePageMeta({ layout: "withtop" });
const tag =
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB