Merge remote-tracking branch 'template/main'
This commit is contained in:
commit
38e495fe01
40 changed files with 1038 additions and 11867 deletions
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"CurrentProjectSetting": null
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"ExpandedNodes": [
|
|
||||||
"",
|
|
||||||
"\\src",
|
|
||||||
"\\src\\typings",
|
|
||||||
"\\src\\util"
|
|
||||||
],
|
|
||||||
"SelectedNode": "\\src\\App.vue",
|
|
||||||
"PreviewInSolutionExplorer": false
|
|
||||||
}
|
|
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
23
.vscode/launch.json
vendored
Normal file
23
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug Current Test File",
|
||||||
|
"autoAttachChildProcesses": true,
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"${relativeFile}"
|
||||||
|
],
|
||||||
|
"smartStep": true,
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"vitest.commandLine": "npx vitest"
|
||||||
|
}
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.5.0] - 2022-06-27
|
||||||
|
### Added
|
||||||
|
- Projects now cache for offline play, and show notification when an update is available
|
||||||
|
- Projects can now be "installed" as a Progressive Web App
|
||||||
|
- Conversions can now be given a custom spend function, which defaults to setting the base resource amount to 0
|
||||||
|
- Components for displaying Floor and Square Root symbols
|
||||||
|
### Changed
|
||||||
|
- **BREAKING** Several projInfo properties now default to empty strings, to prevent things like reusing project IDs
|
||||||
|
- **BREAKING** Replaced vue-cli-service with vite (should not break most projects)
|
||||||
|
- Updated dependencies
|
||||||
|
- Made all type-only imports explicit
|
||||||
|
- setupPassiveGeneration now works properly on independent conversions
|
||||||
|
- setupPassiveGeneration now takes an option cap it can't go over
|
||||||
|
- Improved typing for PlayerData.layers
|
||||||
|
- Options Functions have an improved `this` type - it now includes the options themselves
|
||||||
|
- Removed v-show being used in data/common.tsx
|
||||||
|
### Tests
|
||||||
|
- Implement Jest, and running tests automatically on push
|
||||||
|
- Tests written for utils/common.ts
|
||||||
|
|
||||||
## [0.4.2] - 2022-05-23
|
## [0.4.2] - 2022-05-23
|
||||||
### Added
|
### Added
|
||||||
- costModifier to conversions
|
- costModifier to conversions
|
||||||
|
|
|
@ -13,7 +13,7 @@ npm install
|
||||||
|
|
||||||
### Hosts dev server and hot-reloads modules as they're changed
|
### Hosts dev server and hot-reloads modules as they're changed
|
||||||
```
|
```
|
||||||
npm starts
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and minifies for production
|
### Compiles and minifies for production
|
||||||
|
@ -30,3 +30,8 @@ npm run preview
|
||||||
```
|
```
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Runs the tests using vite-jest
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
preset: "vite-jest",
|
|
||||||
testEnvironment: "jest-environment-jsdom",
|
|
||||||
moduleNameMapper: {
|
|
||||||
"^./../([^.].*)$": "$1"
|
|
||||||
}
|
|
||||||
};
|
|
12178
package-lock.json
generated
12178
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.4.2",
|
"version": "0.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vite-jest --no-cache"
|
"test": "vitest run",
|
||||||
|
"testw": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/particle-emitter": "^5.0.4",
|
"@pixi/particle-emitter": "^5.0.4",
|
||||||
"@vitejs/plugin-vue": "^2.3.3",
|
"@vitejs/plugin-vue": "^2.3.3",
|
||||||
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||||
"is-plain-object": "^5.0.0",
|
"is-plain-object": "^5.0.0",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"nanoevents": "^6.0.2",
|
"nanoevents": "^6.0.2",
|
||||||
"pixi.js": "^6.3.0",
|
"pixi.js": "^6.3.0",
|
||||||
|
@ -32,25 +32,16 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ivanv/vue-collapse-transition": "^1.0.2",
|
"@ivanv/vue-collapse-transition": "^1.0.2",
|
||||||
"@rushstack/eslint-patch": "^1.1.0",
|
"@rushstack/eslint-patch": "^1.1.0",
|
||||||
"@types/jest": "^28.1.3",
|
|
||||||
"@types/lodash.clonedeep": "^4.5.6",
|
|
||||||
"@types/lz-string": "^1.3.34",
|
"@types/lz-string": "^1.3.34",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"@vue/eslint-config-typescript": "^10.0.0",
|
"@vue/eslint-config-typescript": "^10.0.0",
|
||||||
"babel-jest": "^28.1.1",
|
|
||||||
"eslint": "^8.6.0",
|
"eslint": "^8.6.0",
|
||||||
"jest": "^27.5.1",
|
"jsdom": "^20.0.0",
|
||||||
"jest-environment-jsdom": "^27.5.1",
|
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "~4.5.5",
|
"typescript": "^4.7.4",
|
||||||
"vite-jest": "^0.1.4",
|
"vitest": "^0.17.1",
|
||||||
"vue-tsc": "^0.38.1"
|
"vue-tsc": "^0.38.1"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
|
||||||
"> 1%",
|
|
||||||
"last 2 versions",
|
|
||||||
"not dead"
|
|
||||||
],
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.x"
|
"node": "16.x"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,30 +9,29 @@ import { computed, inject, onUnmounted, ref, toRefs, unref, watch } from "vue";
|
||||||
const _props = defineProps<{ id: string }>();
|
const _props = defineProps<{ id: string }>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
|
||||||
const register = inject(RegisterNodeInjectionKey);
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
const unregister = inject(UnregisterNodeInjectionKey);
|
const register = inject(RegisterNodeInjectionKey, () => {});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const unregister = inject(UnregisterNodeInjectionKey, () => {});
|
||||||
|
|
||||||
const node = ref<HTMLElement | null>(null);
|
const node = ref<HTMLElement | null>(null);
|
||||||
const parentNode = computed(() => node.value && node.value.parentElement);
|
const parentNode = computed(() => node.value && node.value.parentElement);
|
||||||
|
|
||||||
if (register && unregister) {
|
watch([parentNode, props.id], ([newNode, newID], [prevNode, prevID]) => {
|
||||||
watch([parentNode, props.id], ([newNode, newID], [prevNode, prevID]) => {
|
if (prevNode) {
|
||||||
if (prevNode) {
|
unregister(unref(prevID));
|
||||||
unregister(unref(prevID));
|
}
|
||||||
}
|
if (newNode) {
|
||||||
if (newNode) {
|
register(newID, newNode);
|
||||||
register(newID, newNode);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => unregister(unref(props.id)));
|
onUnmounted(() => unregister(unref(props.id)));
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.node {
|
.node {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: -10;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
42
src/components/Notif.vue
Normal file
42
src/components/Notif.vue
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<div class="notif">!</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.notif {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 5px;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--accent3);
|
||||||
|
font-size: x-large;
|
||||||
|
animation: 1s linear infinite bounce;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: var(--locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0% {
|
||||||
|
animation-timing-function: cubic-bezier(0.1361, 0.2514, 0.2175, 0.8786);
|
||||||
|
transform: translate(0, 0px) scaleY(1);
|
||||||
|
}
|
||||||
|
37% {
|
||||||
|
animation-timing-function: cubic-bezier(0.7674, 0.1844, 0.8382, 0.7157);
|
||||||
|
transform: translate(0, -20px) scaleY(1);
|
||||||
|
}
|
||||||
|
72% {
|
||||||
|
animation-timing-function: cubic-bezier(0.1118, 0.2149, 0.2172, 0.941);
|
||||||
|
transform: translate(0, 0px) scaleY(1);
|
||||||
|
}
|
||||||
|
87% {
|
||||||
|
animation-timing-function: cubic-bezier(0.7494, 0.2259, 0.8209, 0.6963);
|
||||||
|
transform: translate(0, 10px) scaleY(0.602);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0px) scaleY(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<span style="white-space: nowrap;">
|
<span style="white-space: nowrap">
|
||||||
<span style="font-size: larger; font-family: initial">√</span><span style="text-decoration: overline"> <slot /> </span>
|
<span style="font-size: larger; font-family: initial">√</span
|
||||||
|
><span style="text-decoration: overline"><slot /></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -25,18 +25,44 @@ import type { Ref } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import "./common.css";
|
import "./common.css";
|
||||||
|
|
||||||
|
/** An object that configures a {@link ResetButton} */
|
||||||
export interface ResetButtonOptions extends ClickableOptions {
|
export interface ResetButtonOptions extends ClickableOptions {
|
||||||
|
/** The conversion the button uses to calculate how much resources will be gained on click */
|
||||||
conversion: GenericConversion;
|
conversion: GenericConversion;
|
||||||
|
/** The tree this reset button is apart of */
|
||||||
tree: GenericTree;
|
tree: GenericTree;
|
||||||
|
/** The specific tree node associated with this reset button */
|
||||||
treeNode: GenericTreeNode;
|
treeNode: GenericTreeNode;
|
||||||
|
/**
|
||||||
|
* Text to display on low conversion amounts, describing what "resetting" is in this context.
|
||||||
|
* Defaults to "Reset for ".
|
||||||
|
*/
|
||||||
resetDescription?: Computable<string>;
|
resetDescription?: Computable<string>;
|
||||||
|
/** Whether or not to show how much currency would be required to make the gain amount increase. */
|
||||||
showNextAt?: Computable<boolean>;
|
showNextAt?: Computable<boolean>;
|
||||||
|
/**
|
||||||
|
* The content to display on the button.
|
||||||
|
* By default, this includes the reset description, and amount of currency to be gained.
|
||||||
|
*/
|
||||||
display?: Computable<CoercableComponent>;
|
display?: Computable<CoercableComponent>;
|
||||||
|
/**
|
||||||
|
* Whether or not this button can currently be clicked.
|
||||||
|
* Defaults to checking the current gain amount is greater than {@link minimumGain}
|
||||||
|
*/
|
||||||
canClick?: Computable<boolean>;
|
canClick?: Computable<boolean>;
|
||||||
|
/**
|
||||||
|
* When {@link canClick} is left to its default, minimumGain is used to only enable the reset button when a sufficient amount of currency to gain is available.
|
||||||
|
*/
|
||||||
minimumGain?: Computable<DecimalSource>;
|
minimumGain?: Computable<DecimalSource>;
|
||||||
|
/** A persistent ref to track how much time has passed since the last time this tree node was reset. */
|
||||||
resetTime?: Persistent<DecimalSource>;
|
resetTime?: Persistent<DecimalSource>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button that is used to control a conversion.
|
||||||
|
* It will show how much can be converted currently, and can show when that amount will go up, as well as handle only being clickable when a sufficient amount of currency can be gained.
|
||||||
|
* Assumes this button is associated with a specific node on a tree, and triggers that tree's reset propagation.
|
||||||
|
*/
|
||||||
export type ResetButton<T extends ResetButtonOptions> = Replace<
|
export type ResetButton<T extends ResetButtonOptions> = Replace<
|
||||||
Clickable<T>,
|
Clickable<T>,
|
||||||
{
|
{
|
||||||
|
@ -49,6 +75,7 @@ export type ResetButton<T extends ResetButtonOptions> = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/** A type that matches any valid {@link ResetButton} object. */
|
||||||
export type GenericResetButton = Replace<
|
export type GenericResetButton = Replace<
|
||||||
GenericClickable & ResetButton<ResetButtonOptions>,
|
GenericClickable & ResetButton<ResetButtonOptions>,
|
||||||
{
|
{
|
||||||
|
@ -60,6 +87,10 @@ export type GenericResetButton = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily creates a reset button with the given options.
|
||||||
|
* @param optionsFunc A function that returns the options object for this reset button.
|
||||||
|
*/
|
||||||
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
|
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
|
||||||
optionsFunc: OptionsFunc<T>
|
optionsFunc: OptionsFunc<T>
|
||||||
): ResetButton<T> {
|
): ResetButton<T> {
|
||||||
|
@ -136,12 +167,24 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
||||||
}) as unknown as ResetButton<T>;
|
}) as unknown as ResetButton<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An object that configures a {@link LayerTreeNode} */
|
||||||
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
||||||
|
/** The ID of the layer this tree node is associated with */
|
||||||
layerID: string;
|
layerID: string;
|
||||||
|
/** The color to display this tree node as */
|
||||||
color: Computable<string>; // marking as required
|
color: Computable<string>; // marking as required
|
||||||
|
/**
|
||||||
|
* The content to display in the tree node.
|
||||||
|
* Defaults to the layer's ID
|
||||||
|
*/
|
||||||
display?: Computable<CoercableComponent>;
|
display?: Computable<CoercableComponent>;
|
||||||
|
/** Whether or not to append the layer to the tabs list.
|
||||||
|
* If set to false, then the tree node will instead always remove all tabs to its right and then add the layer tab.
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
append?: Computable<boolean>;
|
append?: Computable<boolean>;
|
||||||
}
|
}
|
||||||
|
/** A tree node that is associated with a given layer, and which opens the layer when clicked. */
|
||||||
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
||||||
TreeNode<T>,
|
TreeNode<T>,
|
||||||
{
|
{
|
||||||
|
@ -149,6 +192,7 @@ export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
||||||
append: GetComputableType<T["append"]>;
|
append: GetComputableType<T["append"]>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
/** A type that matches any valid {@link LayerTreeNode} object. */
|
||||||
export type GenericLayerTreeNode = Replace<
|
export type GenericLayerTreeNode = Replace<
|
||||||
LayerTreeNode<LayerTreeNodeOptions>,
|
LayerTreeNode<LayerTreeNodeOptions>,
|
||||||
{
|
{
|
||||||
|
@ -157,6 +201,10 @@ export type GenericLayerTreeNode = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily creates a tree node that's associated with a specific layer, with the given options.
|
||||||
|
* @param optionsFunc A function that returns the options object for this tree node.
|
||||||
|
*/
|
||||||
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
||||||
optionsFunc: OptionsFunc<T>
|
optionsFunc: OptionsFunc<T>
|
||||||
): LayerTreeNode<T> {
|
): LayerTreeNode<T> {
|
||||||
|
@ -184,6 +232,18 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
||||||
}) as unknown as LayerTreeNode<T>;
|
}) as unknown as LayerTreeNode<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed.
|
||||||
|
* Also returns a list of persistent refs that are used to control which sections are currently collapsed.
|
||||||
|
* @param sections An array of options objects for each section to display.
|
||||||
|
* @param sections.title The header for this modifier.
|
||||||
|
* @param sections.subtitle A subtitle for this modifier, e.g. to explain the context for the modifier.
|
||||||
|
* @param sections.modifier The modifier to be displaying in this section.
|
||||||
|
* @param sections.base The base value being modified.
|
||||||
|
* @param sections.unit The unit of measurement for the base.
|
||||||
|
* @param sections.baseText The label to call the base amount.
|
||||||
|
* @param sections.visible Whether or not this section should be currently visible to the player.
|
||||||
|
*/
|
||||||
export function createCollapsibleModifierSections(
|
export function createCollapsibleModifierSections(
|
||||||
sections: {
|
sections: {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -248,3 +308,12 @@ export function createCollapsibleModifierSections(
|
||||||
});
|
});
|
||||||
return [jsxFunc, collapsed];
|
return [jsxFunc, collapsed];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an HTML string for a span that writes some given text in a given color.
|
||||||
|
* @param textToColor The content to change the color of
|
||||||
|
* @param color The color to change the content to look like. Defaults to the current theme's accent 2 variable.
|
||||||
|
*/
|
||||||
|
export function colorText(textToColor: string, color = "var(--accent2)"): string {
|
||||||
|
return `<span style="color: ${color}">${textToColor}</span>`;
|
||||||
|
}
|
||||||
|
|
|
@ -99,15 +99,27 @@ export const main = createLayer("main", () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a player save data object being loaded, return a list of layers that should currently be enabled.
|
||||||
|
* If your project does not use dynamic layers, this should just return all layers.
|
||||||
|
*/
|
||||||
export const getInitialLayers = (
|
export const getInitialLayers = (
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
player: Partial<PlayerData>
|
player: Partial<PlayerData>
|
||||||
): Array<GenericLayer> => [main, f, c, a];
|
): Array<GenericLayer> => [main, f, c, a];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A computed ref whose value is true whenever the game is over.
|
||||||
|
*/
|
||||||
export const hasWon = computed(() => {
|
export const hasWon = computed(() => {
|
||||||
return Decimal.gt(main.points.value, 25);
|
return Decimal.gt(main.points.value, 25);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a player save data object being loaded with a different version, update the save data object to match the structure of the current version.
|
||||||
|
* @param oldVersion The version of the save being loaded in
|
||||||
|
* @param player The save data being loaded in
|
||||||
|
*/
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
export function fixOldSave(
|
export function fixOldSave(
|
||||||
oldVersion: string | undefined,
|
oldVersion: string | undefined,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/** A object of all CSS variables determined by the current theme. */
|
||||||
export interface ThemeVars {
|
export interface ThemeVars {
|
||||||
"--foreground": string;
|
"--foreground": string;
|
||||||
"--background": string;
|
"--background": string;
|
||||||
|
@ -19,14 +20,20 @@ export interface ThemeVars {
|
||||||
"--feature-margin": string;
|
"--feature-margin": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An object representing a theme the player can use to change the look of the game. */
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
|
/** The values of the theme's CSS variables. */
|
||||||
variables: ThemeVars;
|
variables: ThemeVars;
|
||||||
|
/** Whether or not tabs should "float" in the center of their container. */
|
||||||
floatingTabs: boolean;
|
floatingTabs: boolean;
|
||||||
|
/** Whether or not adjacent features should merge together - removing the margin between them, and only applying the border radius to the first and last elements in the row or column. */
|
||||||
mergeAdjacent: boolean;
|
mergeAdjacent: boolean;
|
||||||
|
/** Whether or not to show a pin icon on pinned tooltips. */
|
||||||
showPin: boolean;
|
showPin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "@vue/runtime-dom" {
|
declare module "@vue/runtime-dom" {
|
||||||
|
/** Make CSS properties accept any CSS variables usually controlled by a theme. */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
interface CSSProperties extends Partial<ThemeVars> {}
|
interface CSSProperties extends Partial<ThemeVars> {}
|
||||||
|
|
||||||
|
@ -62,6 +69,7 @@ const defaultTheme: Theme = {
|
||||||
showPin: true
|
showPin: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** An enum of all available themes and their internal IDs. The keys are their display names. */
|
||||||
export enum Themes {
|
export enum Themes {
|
||||||
Classic = "classic",
|
Classic = "classic",
|
||||||
Paper = "paper",
|
Paper = "paper",
|
||||||
|
@ -69,6 +77,7 @@ export enum Themes {
|
||||||
Aqua = "aqua"
|
Aqua = "aqua"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A dictionary of all available themes. */
|
||||||
export default {
|
export default {
|
||||||
classic: defaultTheme,
|
classic: defaultTheme,
|
||||||
paper: {
|
paper: {
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import { persistent } from "game/persistence";
|
import { persistent } from "game/persistence";
|
||||||
|
import player from "game/player";
|
||||||
|
import settings from "game/settings";
|
||||||
import type {
|
import type {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -99,6 +101,7 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
if (achievement.shouldEarn) {
|
if (achievement.shouldEarn) {
|
||||||
const genericAchievement = achievement as GenericAchievement;
|
const genericAchievement = achievement as GenericAchievement;
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
if (settings.active !== player.id) return;
|
||||||
if (
|
if (
|
||||||
!genericAchievement.earned.value &&
|
!genericAchievement.earned.value &&
|
||||||
unref(genericAchievement.visibility) === Visibility.Visible &&
|
unref(genericAchievement.visibility) === Visibility.Visible &&
|
||||||
|
|
|
@ -21,12 +21,9 @@
|
||||||
unref(borderStyle) ?? {}
|
unref(borderStyle) ?? {}
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<component
|
<span v-if="component" class="overlayText" :style="unref(textStyle)">
|
||||||
v-if="component"
|
<component :is="component" />
|
||||||
class="overlayText"
|
</span>
|
||||||
:style="unref(textStyle)"
|
|
||||||
:is="component"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="border"
|
class="border"
|
||||||
|
|
|
@ -24,8 +24,9 @@ export type BuyableDisplay =
|
||||||
| CoercableComponent
|
| CoercableComponent
|
||||||
| {
|
| {
|
||||||
title?: CoercableComponent;
|
title?: CoercableComponent;
|
||||||
description: CoercableComponent;
|
description?: CoercableComponent;
|
||||||
effectDisplay?: CoercableComponent;
|
effectDisplay?: CoercableComponent;
|
||||||
|
showAmount?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BuyableOptions {
|
export interface BuyableOptions {
|
||||||
|
@ -168,17 +169,8 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
if (currDisplay != null && buyable.cost != null && buyable.resource != null) {
|
if (currDisplay != null && buyable.cost != null && buyable.resource != null) {
|
||||||
const genericBuyable = buyable as GenericBuyable;
|
const genericBuyable = buyable as GenericBuyable;
|
||||||
const Title = coerceComponent(currDisplay.title || "", "h3");
|
const Title = coerceComponent(currDisplay.title || "", "h3");
|
||||||
const Description = coerceComponent(currDisplay.description);
|
const Description = coerceComponent(currDisplay.description || "");
|
||||||
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
|
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
|
||||||
const amountDisplay =
|
|
||||||
unref(genericBuyable.purchaseLimit) === Decimal.dInf ? (
|
|
||||||
<>Amount: {formatWhole(genericBuyable.amount.value)}</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
Amount: {formatWhole(genericBuyable.amount.value)} /{" "}
|
|
||||||
{formatWhole(unref(genericBuyable.purchaseLimit))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
|
@ -187,11 +179,20 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
<Title />
|
<Title />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<Description />
|
{currDisplay.description ? <Description /> : null}
|
||||||
<div>
|
{currDisplay.showAmount === false ? null : (
|
||||||
<br />
|
<div>
|
||||||
{amountDisplay}
|
<br />
|
||||||
</div>
|
{unref(genericBuyable.purchaseLimit) === Decimal.dInf ? (
|
||||||
|
<>Amount: {formatWhole(genericBuyable.amount.value)}</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Amount: {formatWhole(genericBuyable.amount.value)} /{" "}
|
||||||
|
{formatWhole(unref(genericBuyable.purchaseLimit))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{currDisplay.effectDisplay ? (
|
{currDisplay.effectDisplay ? (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -12,12 +12,10 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
|
|
||||||
/**
|
/** An object that configures a {@link Conversion}. */
|
||||||
* An object that configures a {@link conversion}.
|
|
||||||
*/
|
|
||||||
export interface ConversionOptions {
|
export interface ConversionOptions {
|
||||||
/**
|
/**
|
||||||
* The scaling function that is used to determine the rate of conversion from one {@link resource} to the other.
|
* The scaling function that is used to determine the rate of conversion from one {@link features/resources/resource.Resource} to the other.
|
||||||
*/
|
*/
|
||||||
scaling: ScalingFunction;
|
scaling: ScalingFunction;
|
||||||
/**
|
/**
|
||||||
|
@ -43,11 +41,11 @@ export interface ConversionOptions {
|
||||||
*/
|
*/
|
||||||
nextAt?: Computable<DecimalSource>;
|
nextAt?: Computable<DecimalSource>;
|
||||||
/**
|
/**
|
||||||
* The input {@link resource} for this conversion.
|
* The input {@link features/resources/resource.Resource} for this conversion.
|
||||||
*/
|
*/
|
||||||
baseResource: Resource;
|
baseResource: Resource;
|
||||||
/**
|
/**
|
||||||
* The output {@link resource} for this conversion. i.e. the resource being generated.
|
* The output {@link features/resources/resource.Resource} for this conversion. i.e. the resource being generated.
|
||||||
*/
|
*/
|
||||||
gainResource: Resource;
|
gainResource: Resource;
|
||||||
/**
|
/**
|
||||||
|
@ -78,7 +76,7 @@ export interface ConversionOptions {
|
||||||
/**
|
/**
|
||||||
* An additional modifier that will be applied to the gain amounts.
|
* An additional modifier that will be applied to the gain amounts.
|
||||||
* Must be reversible in order to correctly calculate {@link nextAt}.
|
* Must be reversible in order to correctly calculate {@link nextAt}.
|
||||||
* @see {@link createSequentialModifier} if you want to apply multiple modifiers.
|
* @see {@link game/modifiers.createSequentialModifier} if you want to apply multiple modifiers.
|
||||||
*/
|
*/
|
||||||
gainModifier?: WithRequired<Modifier, "revert">;
|
gainModifier?: WithRequired<Modifier, "revert">;
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +84,7 @@ export interface ConversionOptions {
|
||||||
* That is to say, this modifier will be applied to the amount of baseResource before going into the scaling function.
|
* That is to say, this modifier will be applied to the amount of baseResource before going into the scaling function.
|
||||||
* A cost modifier of x0.5 would give gain amounts equal to the player having half the baseResource they actually have.
|
* A cost modifier of x0.5 would give gain amounts equal to the player having half the baseResource they actually have.
|
||||||
* Must be reversible in order to correctly calculate {@link nextAt}.
|
* Must be reversible in order to correctly calculate {@link nextAt}.
|
||||||
* @see {@link createSequentialModifier} if you want to apply multiple modifiers.
|
* @see {@link game/modifiers.createSequentialModifier} if you want to apply multiple modifiers.
|
||||||
*/
|
*/
|
||||||
costModifier?: WithRequired<Modifier, "revert">;
|
costModifier?: WithRequired<Modifier, "revert">;
|
||||||
}
|
}
|
||||||
|
@ -101,9 +99,7 @@ export interface BaseConversion {
|
||||||
convert: VoidFunction;
|
convert: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** An object that converts one {@link features/resources/resource.Resource} into another at a given rate. */
|
||||||
* An object that converts one {@link resource} into another at a given rate.
|
|
||||||
*/
|
|
||||||
export type Conversion<T extends ConversionOptions> = Replace<
|
export type Conversion<T extends ConversionOptions> = Replace<
|
||||||
T & BaseConversion,
|
T & BaseConversion,
|
||||||
{
|
{
|
||||||
|
@ -117,9 +113,7 @@ export type Conversion<T extends ConversionOptions> = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/** A type that matches any valid {@link Conversion} object. */
|
||||||
* A type that matches any {@link conversion} object.
|
|
||||||
*/
|
|
||||||
export type GenericConversion = Replace<
|
export type GenericConversion = Replace<
|
||||||
Conversion<ConversionOptions>,
|
Conversion<ConversionOptions>,
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,12 +5,12 @@ import { isRef } from "vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A symbol to use as a key for a vue component a feature can be rendered with
|
* A symbol to use as a key for a vue component a feature can be rendered with
|
||||||
* @see {@link VueFeature}
|
* @see {@link util/vue.VueFeature}
|
||||||
*/
|
*/
|
||||||
export const Component = Symbol("Component");
|
export const Component = Symbol("Component");
|
||||||
/**
|
/**
|
||||||
* A symbol to use as a key for a prop gathering function that a feature can use to send to its component
|
* A symbol to use as a key for a prop gathering function that a feature can use to send to its component
|
||||||
* @see {@link VueFeature}
|
* @see {@link util/vue.VueFeature}
|
||||||
*/
|
*/
|
||||||
export const GatherProps = Symbol("GatherProps");
|
export const GatherProps = Symbol("GatherProps");
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,9 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Link } from "features/links/links";
|
import type { Link } from "features/links/links";
|
||||||
|
import type { FeatureNode } from "game/layers";
|
||||||
import { BoundsInjectionKey, NodesInjectionKey } from "game/layers";
|
import { BoundsInjectionKey, NodesInjectionKey } from "game/layers";
|
||||||
import { computed, inject, ref, toRef, watch } from "vue";
|
import { computed, inject, onMounted, ref, toRef, watch } from "vue";
|
||||||
import LinkVue from "./Link.vue";
|
import LinkVue from "./Link.vue";
|
||||||
|
|
||||||
const _props = defineProps<{ links?: Link[] }>();
|
const _props = defineProps<{ links?: Link[] }>();
|
||||||
|
@ -23,15 +24,14 @@ const links = toRef(_props, "links");
|
||||||
|
|
||||||
const resizeListener = ref<Element | null>(null);
|
const resizeListener = ref<Element | null>(null);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
const nodes = inject(NodesInjectionKey, ref<Record<string, FeatureNode | undefined>>({}));
|
||||||
const nodes = inject(NodesInjectionKey)!;
|
const outerBoundingRect = inject(BoundsInjectionKey, ref<DOMRect | undefined>(undefined));
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
const boundingRect = ref<DOMRect | undefined>(resizeListener.value?.getBoundingClientRect());
|
||||||
const outerBoundingRect = inject(BoundsInjectionKey)!;
|
|
||||||
const boundingRect = ref<DOMRect | undefined>(undefined);
|
|
||||||
watch(
|
watch(
|
||||||
[outerBoundingRect],
|
outerBoundingRect,
|
||||||
() => (boundingRect.value = resizeListener.value?.getBoundingClientRect())
|
() => (boundingRect.value = resizeListener.value?.getBoundingClientRect())
|
||||||
);
|
);
|
||||||
|
onMounted(() => (boundingRect.value = resizeListener.value?.getBoundingClientRect()));
|
||||||
|
|
||||||
const validLinks = computed(() => {
|
const validLinks = computed(() => {
|
||||||
const n = nodes.value;
|
const n = nodes.value;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { globalBus } from "game/events";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import { persistent } from "game/persistence";
|
import { persistent } from "game/persistence";
|
||||||
|
import player from "game/player";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import { camelToTitle } from "util/common";
|
import { camelToTitle } from "util/common";
|
||||||
import type {
|
import type {
|
||||||
|
@ -132,6 +133,7 @@ export function createMilestone<T extends MilestoneOptions>(
|
||||||
if (milestone.shouldEarn) {
|
if (milestone.shouldEarn) {
|
||||||
const genericMilestone = milestone as GenericMilestone;
|
const genericMilestone = milestone as GenericMilestone;
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
if (settings.active !== player.id) return;
|
||||||
if (
|
if (
|
||||||
!genericMilestone.earned.value &&
|
!genericMilestone.earned.value &&
|
||||||
unref(genericMilestone.visibility) === Visibility.Visible &&
|
unref(genericMilestone.visibility) === Visibility.Visible &&
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default defineComponent({
|
||||||
backgroundAlpha: 0
|
backgroundAlpha: 0
|
||||||
});
|
});
|
||||||
resizeListener.value?.appendChild(app.value.view);
|
resizeListener.value?.appendChild(app.value.view);
|
||||||
props.onInit(app.value as Application);
|
props.onInit?.(app.value as Application);
|
||||||
}
|
}
|
||||||
updateBounds();
|
updateBounds();
|
||||||
if (props.onHotReload) {
|
if (props.onHotReload) {
|
||||||
|
|
|
@ -1,21 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<Sticky>
|
||||||
<span v-if="showPrefix">You have </span>
|
<div
|
||||||
<ResourceVue :resource="resource" :color="color || 'white'" />
|
class="main-display-container"
|
||||||
{{ resource.displayName
|
:style="{ height: `${(effectRef?.$el.clientHeight ?? 0) + 50}px` }"
|
||||||
}}<!-- remove whitespace -->
|
>
|
||||||
<span v-if="effectComponent">, <component :is="effectComponent" /></span>
|
<div class="main-display">
|
||||||
<br /><br />
|
<span v-if="showPrefix">You have </span>
|
||||||
</div>
|
<ResourceVue :resource="resource" :color="color || 'white'" />
|
||||||
|
{{ resource.displayName
|
||||||
|
}}<!-- remove whitespace -->
|
||||||
|
<span v-if="effectComponent"
|
||||||
|
>, <component :is="effectComponent" ref="effectRef"
|
||||||
|
/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Sticky>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Sticky from "components/layout/Sticky.vue";
|
||||||
import type { CoercableComponent } from "features/feature";
|
import type { CoercableComponent } from "features/feature";
|
||||||
import type { Resource } from "features/resources/resource";
|
import type { Resource } from "features/resources/resource";
|
||||||
import ResourceVue from "features/resources/Resource.vue";
|
import ResourceVue from "features/resources/Resource.vue";
|
||||||
import Decimal from "util/bignum";
|
import Decimal from "util/bignum";
|
||||||
import { computeOptionalComponent } from "util/vue";
|
import { computeOptionalComponent } from "util/vue";
|
||||||
import type { Ref, StyleValue } from "vue";
|
import { ComponentPublicInstance, ref, Ref, StyleValue } from "vue";
|
||||||
import { computed, toRefs } from "vue";
|
import { computed, toRefs } from "vue";
|
||||||
|
|
||||||
const _props = defineProps<{
|
const _props = defineProps<{
|
||||||
|
@ -27,6 +36,8 @@ const _props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
|
||||||
|
const effectRef = ref<ComponentPublicInstance | null>(null);
|
||||||
|
|
||||||
const effectComponent = computeOptionalComponent(
|
const effectComponent = computeOptionalComponent(
|
||||||
props.effectDisplay as Ref<CoercableComponent | undefined>
|
props.effectDisplay as Ref<CoercableComponent | undefined>
|
||||||
);
|
);
|
||||||
|
@ -35,3 +46,11 @@ const showPrefix = computed(() => {
|
||||||
return Decimal.lt(props.resource.value, "1e1000");
|
return Decimal.lt(props.resource.value, "1e1000");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.main-display-container {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -11,7 +11,11 @@
|
||||||
tabStyle ?? []
|
tabStyle ?? []
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<Sticky class="tab-buttons-container">
|
<Sticky
|
||||||
|
class="tab-buttons-container"
|
||||||
|
:class="unref(buttonContainerClasses)"
|
||||||
|
:style="unref(buttonContainerStyle)"
|
||||||
|
>
|
||||||
<div class="tab-buttons" :class="{ floating }">
|
<div class="tab-buttons" :class="{ floating }">
|
||||||
<TabButton
|
<TabButton
|
||||||
v-for="(button, id) in unref(tabs)"
|
v-for="(button, id) in unref(tabs)"
|
||||||
|
@ -61,7 +65,9 @@ export default defineComponent({
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
style: processedPropType<StyleValue>(String, Object, Array),
|
style: processedPropType<StyleValue>(String, Object, Array),
|
||||||
classes: processedPropType<Record<string, boolean>>(Object)
|
classes: processedPropType<Record<string, boolean>>(Object),
|
||||||
|
buttonContainerStyle: processedPropType<StyleValue>(String, Object, Array),
|
||||||
|
buttonContainerClasses: processedPropType<Record<string, boolean>>(Object)
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Sticky,
|
Sticky,
|
||||||
|
|
|
@ -56,6 +56,8 @@ export interface TabFamilyOptions {
|
||||||
visibility?: Computable<Visibility>;
|
visibility?: Computable<Visibility>;
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
style?: Computable<StyleValue>;
|
style?: Computable<StyleValue>;
|
||||||
|
buttonContainerClasses?: Computable<Record<string, boolean>>;
|
||||||
|
buttonContainerStyle?: Computable<StyleValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseTabFamily {
|
export interface BaseTabFamily {
|
||||||
|
@ -140,10 +142,30 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
setDefault(tabFamily, "visibility", Visibility.Visible);
|
setDefault(tabFamily, "visibility", Visibility.Visible);
|
||||||
processComputable(tabFamily as T, "classes");
|
processComputable(tabFamily as T, "classes");
|
||||||
processComputable(tabFamily as T, "style");
|
processComputable(tabFamily as T, "style");
|
||||||
|
processComputable(tabFamily as T, "buttonContainerClasses");
|
||||||
|
processComputable(tabFamily as T, "buttonContainerStyle");
|
||||||
|
|
||||||
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
||||||
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
const {
|
||||||
return { visibility, activeTab, selected, tabs, style: unref(style), classes };
|
visibility,
|
||||||
|
activeTab,
|
||||||
|
selected,
|
||||||
|
tabs,
|
||||||
|
style,
|
||||||
|
classes,
|
||||||
|
buttonContainerClasses,
|
||||||
|
buttonContainerStyle
|
||||||
|
} = this;
|
||||||
|
return {
|
||||||
|
visibility,
|
||||||
|
activeTab,
|
||||||
|
selected,
|
||||||
|
tabs,
|
||||||
|
style: unref(style),
|
||||||
|
classes,
|
||||||
|
buttonContainerClasses,
|
||||||
|
buttonContainerStyle
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is necessary because board.types is different from T and TabFamily
|
// This is necessary because board.types is different from T and TabFamily
|
||||||
|
|
|
@ -34,10 +34,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="tsx">
|
||||||
import themes from "data/themes";
|
import themes from "data/themes";
|
||||||
import type { CoercableComponent, StyleValue } from "features/feature";
|
import type { CoercableComponent } from "features/feature";
|
||||||
import { jsx } from "features/feature";
|
import { jsx, StyleValue } from "features/feature";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
import { Direction } from "util/common";
|
import { Direction } from "util/common";
|
||||||
|
@ -46,15 +46,15 @@ import {
|
||||||
coerceComponent,
|
coerceComponent,
|
||||||
computeOptionalComponent,
|
computeOptionalComponent,
|
||||||
processedPropType,
|
processedPropType,
|
||||||
render,
|
renderJSX,
|
||||||
unwrapRef
|
unwrapRef
|
||||||
} from "util/vue";
|
} from "util/vue";
|
||||||
import type { Component, PropType } from "vue";
|
import type { Component, PropType } from "vue";
|
||||||
import { computed, defineComponent, ref, shallowRef, toRefs, unref, watchEffect } from "vue";
|
import { computed, defineComponent, ref, shallowRef, toRefs, unref } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
element: processedPropType<VueFeature>(Object),
|
element: Object as PropType<VueFeature>,
|
||||||
display: {
|
display: {
|
||||||
type: processedPropType<CoercableComponent>(Object, String, Function),
|
type: processedPropType<CoercableComponent>(Object, String, Function),
|
||||||
required: true
|
required: true
|
||||||
|
@ -73,14 +73,14 @@ export default defineComponent({
|
||||||
const isShown = computed(() => (unwrapRef(pinned) || isHovered.value) && comp.value);
|
const isShown = computed(() => (unwrapRef(pinned) || isHovered.value) && comp.value);
|
||||||
const comp = computeOptionalComponent(display);
|
const comp = computeOptionalComponent(display);
|
||||||
|
|
||||||
const elementComp = shallowRef<Component | "" | null>(null);
|
const elementComp = shallowRef<Component | "" | null>(
|
||||||
watchEffect(() => {
|
coerceComponent(
|
||||||
const currComponent = unwrapRef(element);
|
jsx(() => {
|
||||||
elementComp.value =
|
const currComponent = unwrapRef(element);
|
||||||
currComponent == null
|
return currComponent == null ? "" : renderJSX(currComponent);
|
||||||
? null
|
})
|
||||||
: coerceComponent(jsx(() => render(currComponent) as JSX.Element));
|
)
|
||||||
});
|
);
|
||||||
|
|
||||||
function togglePinned(e: MouseEvent) {
|
function togglePinned(e: MouseEvent) {
|
||||||
const isPinned = pinned as unknown as Persistent<boolean> | undefined; // Vue typing :/
|
const isPinned = pinned as unknown as Persistent<boolean> | undefined; // Vue typing :/
|
||||||
|
|
|
@ -70,28 +70,28 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
processComputable(options as T, "xoffset");
|
processComputable(options as T, "xoffset");
|
||||||
processComputable(options as T, "yoffset");
|
processComputable(options as T, "yoffset");
|
||||||
|
|
||||||
nextTick(() => {
|
if (options.pinnable) {
|
||||||
if (options.pinnable) {
|
if ("pinned" in element) {
|
||||||
if ("pinned" in element) {
|
console.error(
|
||||||
console.error(
|
"Cannot add pinnable tooltip to element that already has a property called 'pinned'"
|
||||||
"Cannot add pinnable tooltip to element that already has a property called 'pinned'"
|
);
|
||||||
);
|
options.pinnable = false;
|
||||||
options.pinnable = false;
|
} else {
|
||||||
} else {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
(element as any).pinned = options.pinned = persistent<boolean>(false);
|
||||||
(element as any).pinned = options.pinned = persistent<boolean>(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
const elementComponent = element[Component];
|
const elementComponent = element[Component];
|
||||||
element[Component] = TooltipComponent;
|
element[Component] = TooltipComponent;
|
||||||
const elementGratherProps = element[GatherProps].bind(element);
|
const elementGatherProps = element[GatherProps].bind(element);
|
||||||
element[GatherProps] = function gatherTooltipProps(this: GenericTooltip) {
|
element[GatherProps] = function gatherTooltipProps(this: GenericTooltip) {
|
||||||
const { display, classes, style, direction, xoffset, yoffset, pinned } = this;
|
const { display, classes, style, direction, xoffset, yoffset, pinned } = this;
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
[Component]: elementComponent,
|
[Component]: elementComponent,
|
||||||
[GatherProps]: elementGratherProps
|
[GatherProps]: elementGatherProps
|
||||||
},
|
},
|
||||||
display,
|
display,
|
||||||
classes,
|
classes,
|
||||||
|
|
|
@ -24,63 +24,133 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import type { InjectionKey, Ref } from "vue";
|
import type { InjectionKey, Ref } from "vue";
|
||||||
import { ref, shallowReactive, unref } from "vue";
|
import { ref, shallowReactive, unref } from "vue";
|
||||||
|
|
||||||
|
/** A feature's node in the DOM that has its size tracked. */
|
||||||
export interface FeatureNode {
|
export interface FeatureNode {
|
||||||
rect: DOMRect;
|
rect: DOMRect;
|
||||||
observer: MutationObserver;
|
observer: MutationObserver;
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An injection key that a {@link ContextComponent} will use to provide a function that registers a {@link FeatureNode} with the given id and HTML element.
|
||||||
|
*/
|
||||||
export const RegisterNodeInjectionKey: InjectionKey<(id: string, element: HTMLElement) => void> =
|
export const RegisterNodeInjectionKey: InjectionKey<(id: string, element: HTMLElement) => void> =
|
||||||
Symbol("RegisterNode");
|
Symbol("RegisterNode");
|
||||||
|
/**
|
||||||
|
* An injection key that a {@link ContextComponent} will use to provide a function that unregisters a {@link FeatureNode} with the given id.
|
||||||
|
*/
|
||||||
export const UnregisterNodeInjectionKey: InjectionKey<(id: string) => void> =
|
export const UnregisterNodeInjectionKey: InjectionKey<(id: string) => void> =
|
||||||
Symbol("UnregisterNode");
|
Symbol("UnregisterNode");
|
||||||
|
/**
|
||||||
|
* An injection key that a {@link ContextComponent} will use to provide a ref to a map of all currently registered {@link FeatureNode}s.
|
||||||
|
*/
|
||||||
export const NodesInjectionKey: InjectionKey<Ref<Record<string, FeatureNode | undefined>>> =
|
export const NodesInjectionKey: InjectionKey<Ref<Record<string, FeatureNode | undefined>>> =
|
||||||
Symbol("Nodes");
|
Symbol("Nodes");
|
||||||
|
/**
|
||||||
|
* An injection key that a {@link ContextComponent} will use to provide a ref to a bounding rect of the Context.
|
||||||
|
*/
|
||||||
export const BoundsInjectionKey: InjectionKey<Ref<DOMRect | undefined>> = Symbol("Bounds");
|
export const BoundsInjectionKey: InjectionKey<Ref<DOMRect | undefined>> = Symbol("Bounds");
|
||||||
|
|
||||||
|
/** All types of events able to be sent or emitted from a layer's emitter. */
|
||||||
export interface LayerEvents {
|
export interface LayerEvents {
|
||||||
// Generation
|
/** Sent every game tick, before the update event. Intended for "generation" type actions. */
|
||||||
preUpdate: (diff: number) => void;
|
preUpdate: (diff: number) => void;
|
||||||
// Actions (e.g. automation)
|
/** Sent every game tick. Intended for "automation" type actions. */
|
||||||
update: (diff: number) => void;
|
update: (diff: number) => void;
|
||||||
// Effects (e.g. milestones)
|
/** Sent every game tick, after the update event. Intended for checking state. */
|
||||||
postUpdate: (diff: number) => void;
|
postUpdate: (diff: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to all the current layers.
|
||||||
|
* It is shallow reactive so it will update when layers are added or removed, but not interfere with the existing refs within each layer.
|
||||||
|
*/
|
||||||
export const layers: Record<string, Readonly<GenericLayer> | undefined> = shallowReactive({});
|
export const layers: Record<string, Readonly<GenericLayer> | undefined> = shallowReactive({});
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/** Augment the window object so the layers can be accessed from the console. */
|
||||||
|
interface Window {
|
||||||
|
layers: Record<string, Readonly<GenericLayer> | undefined>;
|
||||||
|
}
|
||||||
|
}
|
||||||
window.layers = layers;
|
window.layers = layers;
|
||||||
|
|
||||||
declare module "@vue/runtime-dom" {
|
declare module "@vue/runtime-dom" {
|
||||||
|
/** Augment CSS Properties to allow for setting the layer color CSS variable. */
|
||||||
interface CSSProperties {
|
interface CSSProperties {
|
||||||
"--layer-color"?: string;
|
"--layer-color"?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An object representing the position of some entity. */
|
||||||
export interface Position {
|
export interface Position {
|
||||||
|
/** The X component of the entity's position. */
|
||||||
x: number;
|
x: number;
|
||||||
|
/** The Y component of the entity's position. */
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that configures a {@link Layer}.
|
||||||
|
* Even moreso than features, the developer is expected to include extra properties in this object.
|
||||||
|
* All {@link game/persistence.Persistent} refs must be included somewhere within the layer object.
|
||||||
|
*/
|
||||||
export interface LayerOptions {
|
export interface LayerOptions {
|
||||||
|
/** The color of the layer, used to theme the entire layer's display. */
|
||||||
color?: Computable<string>;
|
color?: Computable<string>;
|
||||||
|
/**
|
||||||
|
* The layout of this layer's features.
|
||||||
|
* When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is display.
|
||||||
|
*/
|
||||||
display: Computable<CoercableComponent>;
|
display: Computable<CoercableComponent>;
|
||||||
|
/** An object of classes that should be applied to the display. */
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
|
/** Styles that should be applied to the display. */
|
||||||
style?: Computable<StyleValue>;
|
style?: Computable<StyleValue>;
|
||||||
|
/**
|
||||||
|
* The name of the layer, used on minimized tabs.
|
||||||
|
* Defaults to {@link BaseLayer.id}.
|
||||||
|
*/
|
||||||
name?: Computable<string>;
|
name?: Computable<string>;
|
||||||
|
/**
|
||||||
|
* Whether or not the layer can be minimized.
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
minimizable?: Computable<boolean>;
|
minimizable?: Computable<boolean>;
|
||||||
|
/**
|
||||||
|
* Whether or not to force the go back button to be hidden.
|
||||||
|
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
|
||||||
|
*/
|
||||||
forceHideGoBack?: Computable<boolean>;
|
forceHideGoBack?: Computable<boolean>;
|
||||||
|
/**
|
||||||
|
* A CSS min-width value that is applied to the layer.
|
||||||
|
* Can be a number, in which case the unit is assumed to be px.
|
||||||
|
* Defaults to 600px.
|
||||||
|
*/
|
||||||
minWidth?: Computable<number | string>;
|
minWidth?: Computable<number | string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The properties that are added onto a processed {@link LayerOptions} to create a {@link Layer} */
|
||||||
export interface BaseLayer {
|
export interface BaseLayer {
|
||||||
|
/**
|
||||||
|
* The ID of the layer.
|
||||||
|
* Populated from the {@link createLayer} parameters.
|
||||||
|
* Used for saving and tracking open tabs.
|
||||||
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
|
/** A persistent ref tracking if the tab is minimized or not. */
|
||||||
minimized: Persistent<boolean>;
|
minimized: Persistent<boolean>;
|
||||||
|
/** An emitter for sending {@link LayerEvents} events for this layer. */
|
||||||
emitter: Emitter<LayerEvents>;
|
emitter: Emitter<LayerEvents>;
|
||||||
|
/** A function to register an event listener on {@link emitter}. */
|
||||||
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
||||||
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
/** A function to emit a {@link LayerEvents} event to this layer. */
|
||||||
|
emit: <K extends keyof LayerEvents>(...args: [K, ...Parameters<LayerEvents[K]>]) => void;
|
||||||
|
/** A map of {@link FeatureNode}s present in this layer's {@link ContextComponent} component. */
|
||||||
nodes: Ref<Record<string, FeatureNode | undefined>>;
|
nodes: Ref<Record<string, FeatureNode | undefined>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An unit of game content. Displayed to the user as a tab or modal. */
|
||||||
export type Layer<T extends LayerOptions> = Replace<
|
export type Layer<T extends LayerOptions> = Replace<
|
||||||
T & BaseLayer,
|
T & BaseLayer,
|
||||||
{
|
{
|
||||||
|
@ -95,6 +165,7 @@ export type Layer<T extends LayerOptions> = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/** A type that matches any valid {@link Layer} object. */
|
||||||
export type GenericLayer = Replace<
|
export type GenericLayer = Replace<
|
||||||
Layer<LayerOptions>,
|
Layer<LayerOptions>,
|
||||||
{
|
{
|
||||||
|
@ -104,8 +175,19 @@ export type GenericLayer = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When creating layers, this object a map of layer ID to a set of any created persistent refs in order to check they're all included in the final layer object.
|
||||||
|
*/
|
||||||
export const persistentRefs: Record<string, Set<Persistent>> = {};
|
export const persistentRefs: Record<string, Set<Persistent>> = {};
|
||||||
|
/**
|
||||||
|
* When creating layers, this array stores the layers currently being created, as a stack.
|
||||||
|
*/
|
||||||
export const addingLayers: string[] = [];
|
export const addingLayers: string[] = [];
|
||||||
|
/**
|
||||||
|
* Lazily creates a layer with the given options.
|
||||||
|
* @param id The ID this layer will have. See {@link BaseLayer.id}.
|
||||||
|
* @param optionsFunc Layer options.
|
||||||
|
*/
|
||||||
export function createLayer<T extends LayerOptions>(
|
export function createLayer<T extends LayerOptions>(
|
||||||
id: string,
|
id: string,
|
||||||
optionsFunc: OptionsFunc<T, BaseLayer>
|
optionsFunc: OptionsFunc<T, BaseLayer>
|
||||||
|
@ -114,7 +196,9 @@ export function createLayer<T extends LayerOptions>(
|
||||||
const layer = {} as T & Partial<BaseLayer>;
|
const layer = {} as T & Partial<BaseLayer>;
|
||||||
const emitter = (layer.emitter = createNanoEvents<LayerEvents>());
|
const emitter = (layer.emitter = createNanoEvents<LayerEvents>());
|
||||||
layer.on = emitter.on.bind(emitter);
|
layer.on = emitter.on.bind(emitter);
|
||||||
layer.emit = emitter.emit.bind(emitter);
|
layer.emit = emitter.emit.bind(emitter) as <K extends keyof LayerEvents>(
|
||||||
|
...args: [K, ...Parameters<LayerEvents[K]>]
|
||||||
|
) => void;
|
||||||
layer.nodes = ref({});
|
layer.nodes = ref({});
|
||||||
layer.id = id;
|
layer.id = id;
|
||||||
|
|
||||||
|
@ -143,6 +227,14 @@ export function createLayer<T extends LayerOptions>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables a layer object, so it will be updated every tick.
|
||||||
|
* Note that accessing a layer/its properties does NOT require it to be enabled.
|
||||||
|
* For dynamic layers you can call this function and {@link removeLayer} as necessary. Just make sure {@link data/projEntry.getInitialLayers} will provide an accurate list of layers based on the player data object.
|
||||||
|
* For static layers just make {@link data/projEntry.getInitialLayers} return all the layers.
|
||||||
|
* @param layer The layer to add.
|
||||||
|
* @param player The player data object, which will have a data object for this layer.
|
||||||
|
*/
|
||||||
export function addLayer(
|
export function addLayer(
|
||||||
layer: GenericLayer,
|
layer: GenericLayer,
|
||||||
player: { layers?: Record<string, Record<string, unknown>> }
|
player: { layers?: Record<string, Record<string, unknown>> }
|
||||||
|
@ -166,10 +258,19 @@ export function addLayer(
|
||||||
globalBus.emit("addLayer", layer, player.layers[layer.id]);
|
globalBus.emit("addLayer", layer, player.layers[layer.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for getting a layer by its ID with correct typing.
|
||||||
|
* @param layerID The ID of the layer to get.
|
||||||
|
*/
|
||||||
export function getLayer<T extends GenericLayer>(layerID: string): T {
|
export function getLayer<T extends GenericLayer>(layerID: string): T {
|
||||||
return layers[layerID] as T;
|
return layers[layerID] as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables a layer, so it will no longer be updated every tick.
|
||||||
|
* Note that accessing a layer/its properties does NOT require it to be enabled.
|
||||||
|
* @param layer The layer to remove.
|
||||||
|
*/
|
||||||
export function removeLayer(layer: GenericLayer): void {
|
export function removeLayer(layer: GenericLayer): void {
|
||||||
console.info("Removing layer", layer.id);
|
console.info("Removing layer", layer.id);
|
||||||
globalBus.emit("removeLayer", layer);
|
globalBus.emit("removeLayer", layer);
|
||||||
|
@ -177,6 +278,11 @@ export function removeLayer(layer: GenericLayer): void {
|
||||||
layers[layer.id] = undefined;
|
layers[layer.id] = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for removing and immediately re-adding a layer.
|
||||||
|
* This is useful for layers with dynamic content, to ensure persistent refs are correctly configured.
|
||||||
|
* @param layer Layer to remove and then re-add
|
||||||
|
*/
|
||||||
export function reloadLayer(layer: GenericLayer): void {
|
export function reloadLayer(layer: GenericLayer): void {
|
||||||
removeLayer(layer);
|
removeLayer(layer);
|
||||||
|
|
||||||
|
@ -184,6 +290,11 @@ export function reloadLayer(layer: GenericLayer): void {
|
||||||
addLayer(layer, player);
|
addLayer(layer, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function for creating a modal that display's a {@link LayerOptions.display}.
|
||||||
|
* Returns the modal itself, which can be rendered anywhere you need, as well as a function to open the modal.
|
||||||
|
* @param layer The layer to display in the modal.
|
||||||
|
*/
|
||||||
export function setupLayerModal(layer: GenericLayer): {
|
export function setupLayerModal(layer: GenericLayer): {
|
||||||
openModal: VoidFunction;
|
openModal: VoidFunction;
|
||||||
modal: JSXFunction;
|
modal: JSXFunction;
|
||||||
|
|
|
@ -126,6 +126,13 @@ const playerHandler: ProxyHandler<Record<PropertyKey, any>> = {
|
||||||
return Object.getOwnPropertyDescriptor(target[ProxyState], key);
|
return Object.getOwnPropertyDescriptor(target[ProxyState], key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/** Augment the window object so the player can be accessed from the console. */
|
||||||
|
interface Window {
|
||||||
|
player: Player;
|
||||||
|
}
|
||||||
|
}
|
||||||
export default window.player = new Proxy(
|
export default window.player = new Proxy(
|
||||||
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
||||||
playerHandler
|
playerHandler
|
||||||
|
|
|
@ -30,7 +30,28 @@ watch(
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/**
|
||||||
|
* Augment the window object so the settings, and hard resetting the settings, can be accessed from the console.
|
||||||
|
*/
|
||||||
|
interface Window {
|
||||||
|
settings: Settings;
|
||||||
|
hardResetSettings: VoidFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
export default window.settings = state as Settings;
|
export default window.settings = state as Settings;
|
||||||
|
export const hardResetSettings = (window.hardResetSettings = () => {
|
||||||
|
const settings = {
|
||||||
|
active: "",
|
||||||
|
saves: [],
|
||||||
|
showTPS: true,
|
||||||
|
theme: Themes.Nordic
|
||||||
|
};
|
||||||
|
globalBus.emit("loadSettings", settings);
|
||||||
|
Object.assign(state, settings);
|
||||||
|
hardReset();
|
||||||
|
});
|
||||||
|
|
||||||
export function loadSettings(): void {
|
export function loadSettings(): void {
|
||||||
try {
|
try {
|
||||||
|
@ -59,18 +80,6 @@ export function loadSettings(): void {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hardResetSettings = (window.hardResetSettings = () => {
|
|
||||||
const settings = {
|
|
||||||
active: "",
|
|
||||||
saves: [],
|
|
||||||
showTPS: true,
|
|
||||||
theme: Themes.Nordic
|
|
||||||
};
|
|
||||||
globalBus.emit("loadSettings", settings);
|
|
||||||
Object.assign(state, settings);
|
|
||||||
hardReset();
|
|
||||||
});
|
|
||||||
|
|
||||||
export const settingFields: CoercableComponent[] = reactive([]);
|
export const settingFields: CoercableComponent[] = reactive([]);
|
||||||
export function registerSettingField(component: CoercableComponent) {
|
export function registerSettingField(component: CoercableComponent) {
|
||||||
settingFields.push(component);
|
settingFields.push(component);
|
||||||
|
|
|
@ -7,6 +7,12 @@ export interface Transient {
|
||||||
NaNReceiver?: Record<string, unknown>;
|
NaNReceiver?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/** Augment the window object so the transient state can be accessed from the console. */
|
||||||
|
interface Window {
|
||||||
|
state: Transient;
|
||||||
|
}
|
||||||
|
}
|
||||||
export default window.state = shallowReactive<Transient>({
|
export default window.state = shallowReactive<Transient>({
|
||||||
lastTenTicks: [],
|
lastTenTicks: [],
|
||||||
hasNaN: false,
|
hasNaN: false,
|
||||||
|
|
44
src/main.ts
44
src/main.ts
|
@ -1,45 +1,31 @@
|
||||||
import App from "App.vue";
|
import App from "App.vue";
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import type { GenericLayer } from "game/layers";
|
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import type { PlayerData } from "game/player";
|
|
||||||
import type { Settings } from "game/settings";
|
|
||||||
import type { Transient } from "game/state";
|
|
||||||
import type { DecimalSource } from "util/bignum";
|
|
||||||
import Decimal from "util/bignum";
|
|
||||||
import { load } from "util/save";
|
import { load } from "util/save";
|
||||||
import { useRegisterSW } from "virtual:pwa-register/vue";
|
import { useRegisterSW } from "virtual:pwa-register/vue";
|
||||||
import type { App as VueApp } from "vue";
|
import type { App as VueApp } from "vue";
|
||||||
import { createApp, nextTick } from "vue";
|
import { createApp, nextTick } from "vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
|
||||||
document.title = projInfo.title;
|
|
||||||
if (projInfo.id === "") {
|
|
||||||
throw "Project ID is empty! Please select a unique ID for this project in /src/data/projInfo.json";
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
/**
|
||||||
|
* Augment the window object so the vue app and project info can be accessed from the console.
|
||||||
|
*/
|
||||||
interface Window {
|
interface Window {
|
||||||
vue: VueApp;
|
vue: VueApp;
|
||||||
save: VoidFunction;
|
|
||||||
hardReset: VoidFunction;
|
|
||||||
hardResetSettings: VoidFunction;
|
|
||||||
layers: Record<string, Readonly<GenericLayer> | undefined>;
|
|
||||||
player: PlayerData;
|
|
||||||
state: Transient;
|
|
||||||
settings: Settings;
|
|
||||||
Decimal: typeof Decimal;
|
|
||||||
exponentialFormat: (num: DecimalSource, precision: number, mantissa: boolean) => string;
|
|
||||||
commaFormat: (num: DecimalSource, precision: number) => string;
|
|
||||||
regularFormat: (num: DecimalSource, precision: number) => string;
|
|
||||||
format: (num: DecimalSource, precision?: number, small?: boolean) => string;
|
|
||||||
formatWhole: (num: DecimalSource) => string;
|
|
||||||
formatTime: (s: number) => string;
|
|
||||||
toPlaces: (x: DecimalSource, precision: number, maxAccepted: DecimalSource) => string;
|
|
||||||
formatSmall: (x: DecimalSource, precision?: number) => string;
|
|
||||||
invertOOM: (x: DecimalSource) => Decimal;
|
|
||||||
projInfo: typeof projInfo;
|
projInfo: typeof projInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fix for typedoc treating import functions as taking AssertOptions instead of GlobOptions. */
|
||||||
|
interface AssertOptions {
|
||||||
|
as: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.title = projInfo.title;
|
||||||
|
window.projInfo = projInfo;
|
||||||
|
if (projInfo.id === "") {
|
||||||
|
throw "Project ID is empty! Please select a unique ID for this project in /src/data/projInfo.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(async () => {
|
requestAnimationFrame(async () => {
|
||||||
|
@ -90,5 +76,3 @@ requestAnimationFrame(async () => {
|
||||||
|
|
||||||
startGameLoop();
|
startGameLoop();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.projInfo = projInfo;
|
|
||||||
|
|
|
@ -17,6 +17,21 @@ export const {
|
||||||
|
|
||||||
export type DecimalSource = RawDecimalSource;
|
export type DecimalSource = RawDecimalSource;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/** Augment the window object so the big num functions can be access from the console. */
|
||||||
|
interface Window {
|
||||||
|
Decimal: typeof Decimal;
|
||||||
|
exponentialFormat: (num: DecimalSource, precision: number, mantissa: boolean) => string;
|
||||||
|
commaFormat: (num: DecimalSource, precision: number) => string;
|
||||||
|
regularFormat: (num: DecimalSource, precision: number) => string;
|
||||||
|
format: (num: DecimalSource, precision?: number, small?: boolean) => string;
|
||||||
|
formatWhole: (num: DecimalSource) => string;
|
||||||
|
formatTime: (s: number) => string;
|
||||||
|
toPlaces: (x: DecimalSource, precision: number, maxAccepted: DecimalSource) => string;
|
||||||
|
formatSmall: (x: DecimalSource, precision?: number) => string;
|
||||||
|
invertOOM: (x: DecimalSource) => Decimal;
|
||||||
|
}
|
||||||
|
}
|
||||||
window.Decimal = Decimal;
|
window.Decimal = Decimal;
|
||||||
window.exponentialFormat = exponentialFormat;
|
window.exponentialFormat = exponentialFormat;
|
||||||
window.commaFormat = commaFormat;
|
window.commaFormat = commaFormat;
|
||||||
|
|
|
@ -37,6 +37,10 @@ export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
obj[key] = computed(computable.bind(obj));
|
obj[key] = computed(computable.bind(obj));
|
||||||
|
} else if (isFunction(computable)) {
|
||||||
|
obj[key] = computable.bind(obj);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(obj[key] as any)[DoNotCache] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,16 @@ window.onbeforeunload = () => {
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
/**
|
||||||
|
* Augment the window object so the save function, and the hard reset function can be access from the console.
|
||||||
|
*/
|
||||||
|
interface Window {
|
||||||
|
save: VoidFunction;
|
||||||
|
hardReset: VoidFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
window.save = save;
|
window.save = save;
|
||||||
export const hardReset = (window.hardReset = async () => {
|
export const hardReset = (window.hardReset = async () => {
|
||||||
await loadSave(newSave());
|
await loadSave(newSave());
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { jest, describe, expect, test } from "@jest/globals";
|
|
||||||
import { camelToTitle, isFunction } from "util/common";
|
import { camelToTitle, isFunction } from "util/common";
|
||||||
|
import { describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
describe("camelToTitle", () => {
|
describe("camelToTitle", () => {
|
||||||
test("Capitalizes first letter in single word", () =>
|
test("Capitalizes first letter in single word", () =>
|
||||||
|
@ -10,7 +10,7 @@ describe("camelToTitle", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isFunction", () => {
|
describe("isFunction", () => {
|
||||||
test("Given function returns true", () => expect(isFunction(jest.fn())).toBe(true));
|
test("Given function returns true", () => expect(isFunction(vi.fn())).toBe(true));
|
||||||
|
|
||||||
// Go through all primitives and basic types
|
// Go through all primitives and basic types
|
||||||
test("Given a string returns false", () => expect(isFunction("test")).toBe(false));
|
test("Given a string returns false", () => expect(isFunction("test")).toBe(false));
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"types": [
|
"types": [
|
||||||
"jest",
|
"vite/client",
|
||||||
"vite/client"
|
"node"
|
||||||
],
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext",
|
"esnext",
|
||||||
|
|
|
@ -40,5 +40,8 @@ export default defineConfig({
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
],
|
||||||
|
test: {
|
||||||
|
environment: "jsdom"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue