This adds support for the `Always On Top` setting in the Tauri version
of LiveSplit One. This setting allows you to keep the LiveSplit One
window on top of all other windows.
This introduces a Tauri based desktop version of LiveSplit One. For now
very few changes have been done. It's mostly global hotkeys that work
now. It's unclear if Tauri is the long term solution for LiveSplit One,
but it gets us global hotkeys and auto splitting with very little
effort, so we'll use it until something better comes along.
This moves the server protocol to `livesplit-core` and improves on it in
various ways:
- The protocol is based on JSON messages. This allows for example for
more structured commands where it's easier to provide multiple
arguments for a command and even have optional arguments.
- For each command, there is a corresponding response. It is either a
`success` response with possibly the value that you requested, or an
`error` response with an error `code`.
- On top of the responses you also get sent `event` messages that
indicate changes to the timer. These can either be changes triggered
via a command that you sent or by changes that happened through other
sources, such as the user directly interacting with the timer or an
auto splitter.
The protocol is still work in progress and we will evolve it into a
protocol that fully allows synchronizing timers over the network.
The event sink has now been renamed to command sink, because there is
now a clear distinction between incoming commands and events that are
the results of these commands.
Changelog: The protocol used for the server connection has been
significantly improved. There is a response for each command and clear
errors when something goes wrong. Additionally, there are now event
messages that indicate changes to the timer.
We introduce a new `indexDelayed.ts` file that bundles all the imports
that are used in the `index.ts` file. They are not imported directly
there, because we want to keep the size of the `index.html` as small as
possible, so it can focus on requesting the WASM file as soon as
possible. This should result in the `indexDelayed.ts` bundle containing
basically all the JS. This JS bundle and the WASM file should then be
requested in parallel. While it would be possible to request these all
individually over in the `index.ts`, browsers don't actually like doing
too many requests in parallel and it turns out that big JavaScript files
actually compress better than having many small ones. The WASM file also
is pretty big, so ideally we are not bottlenecked by the large JS file.
If necessary, we could do some more bundle splitting for stuff that
isn't used until much later, like the Markdown library.
The dialogs are now asynchronous as opposed to the browser built-in ones
that are synchronous, probably because they are incredibly old. The
problem with the new dialogs being asynchronous is that for example the
hotkeys, the server protocol and honestly any sort of async download
that's still running in the background can all mess with the timer while
the dialog is open. This could lead to all sorts of weird issues, like
the timer being in a different state than what the dialog expects, the
timer being reset twice and creating two dialogs that are open at the
same time and possibly even memory corruption in the Rust code.
This commit ensures that the timer can not be interacted with while it
is "locked for interaction". In fact we already had such a notion where
when you navigate to the settings, layout or run editor, the hotkeys
would be disabled. Now this is concept is extended so the event sink
also can prevent interaction with the timer. Navigating to these menus
and opening dialogs will now trigger this locking mechanism.
Additionally this improves various async race conditions in the splits
editor related to downloading resources from speedrun.com or splits.io.
This also touches the CSS of the dialogs again, adding some spacing
around the dialog, so it doesn't touch the edges of the window and
allowing the buttons to have more dynamic sizing. In particular I've
noticed that the text sometimes doesn't fit into the buttons on iOS, so
they are now allowed to grow a little to handle that situation.
File open dialogs also now know the file extensions of the files we are
looking for.
This introduces custom dialogs to LiveSplit One. Previously we've used
the builtin browser dialogs that are very limited in what they can do.
Additionally this introduces a bunch of smaller changes:
- The splits editor is now more optimized and only searches for the game
name whenever it or the list of games changes.
- The PWA badge now resets when the app is reloaded.
- Guests on the leaderboard can now have flags.
- If a user encounters a bug, there's now dedicated toast popups that
don't disappear and direct the user to the issue tracker.
- The Sum of Best cleaner now ensures the run can't be modified while
the cleaning is in progress, which would lead to memory corruption,
especially with the async nature of the new dialogs.
- Cancelling out of opening files doesn't lead to an error anymore.
The following changes are introduced by updating `livesplit-core`:
- When resizing images / reencoding them as PNG, we now use a higher
compression setting.
- Invalid timer states are not unrepresentable anymore.
Every browser's `performance.now()` implementation is not spec
compliant, unless the browser is running on Windows. On every other
operating system, `performance.now()` does not properly keep ticking
while the operating system is suspended / sleeping. There isn't much
that we can do. What we can do is we calculate the initial difference
between `performance.now()` and `Date.now()` and store it in a thread
local. Later, when the phone gets locked, `performance.now()` starts to
break. However, we can detect when the phone gets unlocked again by
listening to the `visibilitychange` event. This is where we can update
the difference again. This of course isn't ideal, as `Date.now()` gets
adjusted by NTP synchronizations, but it's the best we can do.
More information:
https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#ticking_during_sleephttps://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/
Instead of assuming each column has the width of the text `88:88:88`, we
now determine the largest string per column (based on unicode scalar
values) and adjust the column width accordingly. There is still a fixed
minimum width based on the string `88:88`, which ensures that the
columns don't switch sizes too often, especially if you start with empty
splits. We could've also chosen `8:88:88`, which would ensure there's no
column size change as you approach the hour mark. However, this would
increase the size of delta columns unnecessarily. So it's a little bit
of a trade-off.
This enables the multivalue feature when building with the nightly Rust
compiler. This is because the Rust standard library is not built with
the multivalue feature enabled by default. Also we need to specifically
use the spec compliant C ABI for it to work, which is also nightly only.
We now automatically store the current comparison and timing method into
the Indexed DB. This allows us to restore the comparison and timing
method when the user reloads the page.
Changelog: The comparison and timing method (Real Time / Game Time) that
you choose now stay chosen when you reload LiveSplit One.
This adds functionality to the run editor to allow copying comparisons.
This is not only useful for copying custom comparisons, but also for
"baking" the times of a generated comparison, such as the `Latest Run` or
the `Average Segments` to a custom comparison that you can keep around
as long as you want. This is somewhat also meant to replace the
functionality of storing the current run as a Personal Best. Instead you
can just store the `Latest Run` as a custom comparison after you are
done with it.
Changelog: The splits editor now allows copying comparisons. This can
also be used to keep the times of an automatic comparison around as a
custom comparison. One example for using this is keeping around the
latest run as a custom comparison, so that you can compare against it
later on, even if you have already done another run.
When it's the first install of the service worker, or when the user clears the site data, the controllerchange event gets triggered. However, in these situations, we want to avoid prompting the user about an update.
The APIs that are not supported by every browser or may not always be
allowed (Battery and Wake Lock) are requested in try catch blocks. The
problem was that both of them actually yield promises. If the promises
fail, then those exceptions would not be caught properly, because we
forgot to await them. So especially for the wake lock you would
sometimes see an error message when it gets hot reloaded. This should be
fixed now.
Additionally this changes the window title for when there are unsaved
changes. Apparently when LiveSplit One is used as a PWA, then
at least Chrome wants to always keep the app name in the title. If the
title itself is changed, then it gets shown as `LiveSplit One - The new
title`. By us putting an asterisk in the front of the title, the PWA
then shows `LiveSplit One - *LiveSplit One`, which is pretty bad. I
noticed that if the app name is also the prefix of the title, Chrome
recognizes that and does not end up showing the app name twice.
There also was a small bug in the event sink where the
`togglePauseOrStart` event did not update the splits modified state.
This changes it so the layout dimensions are not immediately saved when
resizing a layout. Instead they are saved when saving the layout.
Additionally, resizing marks the layout as unsaved, so you don't forget about
saving the new dimensions.
This adds indicators to various parts of the UI to indicate that there
are unsaved changes. The indicators are mainly shown as white dots in
the sidebar highlighting what is unsaved. The title is also updated to
reflect this. The Badging API is also used to show a badge on the "app
icon" if it's installed as a PWA. In addition to the splits, the layouts
are also now properly tracked when it comes to unsaved changes.
Changelog: There are now indicators in the user interface that remind
you about unsaved changes to your splits and layouts.
This changes the way the user chooses the comparisons. This affects both
the sidebar and the way you specify comparison overrides for the
components. In both cases you now choose the comparison from a list of
all the comparisons that are available.
Changelog: You can now choose the comparisons from a list in the
sidebar. Settings that allow you to specify a comparison now also
provide a list to choose from.
This adds a save on reset setting to the general settings. When enabled,
the splits are automatically saved when the timer is reset.
Changelog: You can now enable **Save On Reset** in the settings, which
will automatically save the splits when you reset the timer.
We will almost certainly not be using Electron in the future. Nowadays
there's Tauri, which is basically a more lightweight Electron that also
allows us to use Rust code on the native side, so it's the much better
solution, if we ever even decide to go down that road.
This is necessary for the changelog generation to work correctly.
Additionally this includes an improvement to sort the contributors
alphabetically, if they have the same amount of contributions.
This improves the contributors section in the About page. It now uses
flexbox to dynamically place the contributors. Additionally their avatar
is now being shown.
Also the ten most recent changes are now being shown in the About page.
The information is extracted from the git history. The idea is to mark
commit messages with a special changelog section at the end that's
specifically meant to be shown as a user facing change. It can be
arbitrary markdown and looks like this (which is also the first one):
Changelog: The About page now shows the most recent changes. The
contributors section has also been improved. It now shows the avatar of
each contributor.
This makes use of the new event system that got added to
`livesplit-core`. This allows the UI to react to events that happen.
Right now this is used for three things:
1. Resetting a run can now ask you for confirmation of whether to save
the current run if it contains new best segments or it's a new
Personal Best.
2. The side bar used to refresh the comparison and timing method 10
times per second. Now we can just react to those being changed,
resulting in better performance.
3. The control buttons underneath the timer now get disabled when they
don't apply to the current situation. The pause button also changes
its icon based on the state of the timer.
This moves the server connection button to the settings and cleans the
entire functionality up in various ways. The server connection no longer
gets closed by navigating around the application. Additionally the
server URL is now being remembered between sessions.
This also improves the design of the toast messages.